Merge pull request #587 from NQNStudios:fix-ranged-anim
Fix some ranged attack animation bugs Fix #582 Fix #97
This commit is contained in:
@@ -251,6 +251,7 @@ void draw_combat_pc(cPlayer& who, location center, bool attacking) {
|
||||
|
||||
void frame_active_pc(location center) {
|
||||
if(monsters_going) return;
|
||||
if(!is_on_screen(univ.current_pc().combat_pos)) return;
|
||||
location where_draw(univ.current_pc().combat_pos.x - center.x + 4, univ.current_pc().combat_pos.y - center.y + 4);
|
||||
rectangle active_pc_rect;
|
||||
active_pc_rect.top = 18 + where_draw.y * 36;
|
||||
|
@@ -120,6 +120,22 @@ rectangle explode_place_rect[30];
|
||||
|
||||
char last_light_mask[13][13];
|
||||
|
||||
terrain_screen_rects_t terrain_screen_rects() {
|
||||
rectangle from = rectangle(terrain_screen_gworld);
|
||||
|
||||
location current_terrain_ul = win_to_rects[WINRECT_TERVIEW].topLeft();
|
||||
rectangle to = from;
|
||||
to.offset(current_terrain_ul);
|
||||
|
||||
rectangle in_frame = to;
|
||||
in_frame.top += 11;
|
||||
in_frame.left += 11;
|
||||
in_frame.bottom -= 11;
|
||||
in_frame.right -= 11;
|
||||
|
||||
return {from, to, in_frame};
|
||||
}
|
||||
|
||||
void apply_unseen_mask() {
|
||||
rectangle base_rect = {9,9,53,45},to_rect,big_to = {13,13,337,265};
|
||||
bool need_bother = false;
|
||||
@@ -345,26 +361,45 @@ void do_missile_anim(short num_steps,location missile_origin,short sound_num) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(std::all_of(store_missiles, store_missiles + 30, [](const store_missile_type& m) {
|
||||
return m.missile_type == 0;
|
||||
})) return;
|
||||
|
||||
// make terrain_template contain current terrain all nicely
|
||||
draw_terrain(1);
|
||||
to_rect = rectangle(terrain_screen_gworld);
|
||||
to_rect.bottom -= 10; // Adjust for pointing buttons
|
||||
rectangle oldBounds = to_rect;
|
||||
to_rect.offset(current_terrain_ul);
|
||||
rect_draw_some_item(terrain_screen_gworld.getTexture(),oldBounds,mainPtr,to_rect);
|
||||
|
||||
mainPtr.setActive(false);
|
||||
|
||||
|
||||
// init missile paths
|
||||
// Eliminate missiles traveling 0 distance
|
||||
for(short i = 0; i < 30; i++) {
|
||||
if((store_missiles[i].missile_type >= 0) && (missile_origin == store_missiles[i].dest))
|
||||
store_missiles[i].missile_type = -1;
|
||||
}
|
||||
|
||||
std::vector<location> missile_targets;
|
||||
std::vector<int> missile_indices;
|
||||
int tracking_missile = -1;
|
||||
location camera_dest;
|
||||
for(short i = 0; i < 30; i++)
|
||||
if(store_missiles[i].missile_type >= 0){
|
||||
missile_indices.push_back(i);
|
||||
missile_targets.push_back(store_missiles[i].dest);
|
||||
}
|
||||
|
||||
if(missile_targets.empty()) return;
|
||||
|
||||
if(missile_targets.size() == 1){
|
||||
tracking_missile = missile_indices[0];
|
||||
camera_dest = between_anchor_points(missile_targets[0], missile_origin);
|
||||
}else{
|
||||
std::vector<location> dest_candidates = points_containing_most(missile_targets);
|
||||
camera_dest = closest_point(dest_candidates, missile_origin);
|
||||
tracking_missile = missile_indices[closest_point_idx(missile_targets, camera_dest)];
|
||||
}
|
||||
|
||||
// Start the camera as close as possible to containing the origin and the camera destination
|
||||
// on the same screen
|
||||
center = between_anchor_points(missile_origin, camera_dest);
|
||||
|
||||
// make terrain_template contain current terrain all nicely
|
||||
draw_terrain(1);
|
||||
auto ter_rects = terrain_screen_rects();
|
||||
rect_draw_some_item(terrain_screen_gworld.getTexture(),ter_rects.from,mainPtr,ter_rects.to);
|
||||
|
||||
mainPtr.setActive(false);
|
||||
|
||||
// init missile paths
|
||||
screen_ul.x = center.x - 4; screen_ul.y = center.y - 4;
|
||||
start_point.x = 13 + 14 + 28 * (short) (missile_origin.x - screen_ul.x);
|
||||
start_point.y = 13 + 18 + 36 * (short) (missile_origin.y - screen_ul.y);
|
||||
@@ -395,6 +430,9 @@ void do_missile_anim(short num_steps,location missile_origin,short sound_num) {
|
||||
play_sound(-1 * sound_num);
|
||||
|
||||
sf::Texture& missiles_gworld = *ResMgr::graphics.get("missiles");
|
||||
bool recentered = false;
|
||||
int offset_x = 0;
|
||||
int offset_y = 0;
|
||||
// Now, at last, launch missile
|
||||
for(short t = 0; t < num_steps; t++) {
|
||||
draw_terrain();
|
||||
@@ -405,6 +443,7 @@ void do_missile_anim(short num_steps,location missile_origin,short sound_num) {
|
||||
temp_rect.offset(-8 + x2[i] + (x1[i] * t) / num_steps,
|
||||
-8 + y2[i] + (y1[i] * t) / num_steps);
|
||||
temp_rect.offset(current_terrain_ul);
|
||||
temp_rect.offset(offset_x, offset_y);
|
||||
|
||||
// now adjust for different paths
|
||||
if(store_missiles[i].path_type == 1)
|
||||
@@ -412,12 +451,34 @@ void do_missile_anim(short num_steps,location missile_origin,short sound_num) {
|
||||
|
||||
missile_place_rect[i] = temp_rect;
|
||||
|
||||
// Halfway through the missile's arc, or when the missile we're tracking goes off-screen, re-position the camera
|
||||
if(((t == num_steps / 2) || (tracking_missile == i && (missile_place_rect[i] & ter_rects.to) != missile_place_rect[i])) && !recentered){
|
||||
location old_center = center;
|
||||
|
||||
center = camera_dest;
|
||||
|
||||
// TODO why can't I make the text bar stay normal?
|
||||
|
||||
// Offset the missile trajectory for the new camera position
|
||||
int dx = center.x - old_center.x;
|
||||
int dy = center.y - old_center.y;
|
||||
offset_x = -dx * 28;
|
||||
offset_y = -dy * 36;
|
||||
draw_terrain();
|
||||
|
||||
recentered = true;
|
||||
|
||||
// Redo this frame
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now put in missile
|
||||
if(store_missiles[i].missile_type < 1000) {
|
||||
from_rect = missile_origin_rect[i];
|
||||
if(store_missiles[i].missile_type >= 7)
|
||||
from_rect.offset(18 * (t % 8),0);
|
||||
rect_draw_some_item(missiles_gworld,from_rect, mainPtr,temp_rect,sf::BlendAlpha);
|
||||
rect_draw_some_item(missiles_gworld,from_rect, mainPtr,temp_rect,ter_rects.in_frame,sf::BlendAlpha);
|
||||
} else {
|
||||
// Custom missile graphics
|
||||
// TODO: Test this!
|
||||
@@ -436,23 +497,19 @@ void do_missile_anim(short num_steps,location missile_origin,short sound_num) {
|
||||
from_rect.height() = 18;
|
||||
if(step >= 4)
|
||||
from_rect.offset(0,18);
|
||||
rect_draw_some_item(*from_gw,from_rect, mainPtr,temp_rect,sf::BlendAlpha);
|
||||
rect_draw_some_item(*from_gw,from_rect, mainPtr,temp_rect,ter_rects.in_frame,sf::BlendAlpha);
|
||||
}
|
||||
}
|
||||
mainPtr.setActive();
|
||||
mainPtr.display();
|
||||
sf::sleep(sf::milliseconds(2 + 5 * get_int_pref("GameSpeed")));
|
||||
}
|
||||
|
||||
|
||||
// Exit gracefully, and clean up screen
|
||||
for(short i = 0; i < 30; i++)
|
||||
store_missiles[i].missile_type = -1;
|
||||
|
||||
to_rect = rectangle(terrain_screen_gworld);
|
||||
to_rect.bottom -= 10; // Adjust for pointing buttons
|
||||
rectangle oldRect = to_rect;
|
||||
to_rect.offset(current_terrain_ul);
|
||||
rect_draw_some_item(terrain_screen_gworld.getTexture(),oldRect,mainPtr,to_rect);
|
||||
rect_draw_some_item(terrain_screen_gworld.getTexture(),ter_rects.from,mainPtr,ter_rects.to);
|
||||
}
|
||||
|
||||
short get_missile_direction(location origin_point,location the_point) {
|
||||
@@ -512,11 +569,8 @@ void do_explosion_anim(short /*sound_num*/,short special_draw, short snd) {
|
||||
// make terrain_template contain current terrain all nicely
|
||||
draw_terrain(1);
|
||||
if(special_draw != 2) {
|
||||
to_rect = rectangle(terrain_screen_gworld);
|
||||
to_rect.bottom -= 10; // Adjust for pointing buttons
|
||||
rectangle oldRect = to_rect;
|
||||
to_rect.offset(current_terrain_ul);
|
||||
rect_draw_some_item(terrain_screen_gworld.getTexture(),oldRect,mainPtr,to_rect);
|
||||
auto ter_rects = terrain_screen_rects();
|
||||
rect_draw_some_item(terrain_screen_gworld.getTexture(),ter_rects.from,mainPtr,ter_rects.to);
|
||||
}
|
||||
|
||||
TextStyle style;
|
||||
@@ -552,6 +606,7 @@ void do_explosion_anim(short /*sound_num*/,short special_draw, short snd) {
|
||||
}
|
||||
|
||||
sf::Texture& boom_gworld = *ResMgr::graphics.get("booms");
|
||||
auto ter_rects = terrain_screen_rects();
|
||||
// Now, at last, do explosion
|
||||
for(short t = (special_draw == 2) ? 6 : 0; t < ((special_draw == 1) ? 6 : 11); t++) { // t goes up to 10 to make sure screen gets cleaned up
|
||||
draw_terrain();
|
||||
@@ -563,14 +618,14 @@ void do_explosion_anim(short /*sound_num*/,short special_draw, short snd) {
|
||||
if(cur_boom_type >= 1000) {
|
||||
std::shared_ptr<const sf::Texture> src_gworld;
|
||||
graf_pos_ref(src_gworld, from_rect) = spec_scen_g.find_graphic(cur_boom_type - 1000 + t);
|
||||
rect_draw_some_item(*src_gworld, from_rect, mainPtr, explode_place_rect[i], sf::BlendAlpha);
|
||||
rect_draw_some_item(*src_gworld, from_rect, mainPtr, explode_place_rect[i], ter_rects.in_frame, sf::BlendAlpha);
|
||||
} else {
|
||||
from_rect = base_rect;
|
||||
from_rect.offset(28 * (t + store_booms[i].offset),36 * (1 + store_booms[i].boom_type));
|
||||
rect_draw_some_item(boom_gworld,from_rect,mainPtr,explode_place_rect[i],sf::BlendAlpha);
|
||||
rect_draw_some_item(boom_gworld,from_rect,mainPtr,explode_place_rect[i], ter_rects.in_frame, sf::BlendAlpha);
|
||||
}
|
||||
|
||||
if(store_booms[i].val_to_place > 0) {
|
||||
if(store_booms[i].val_to_place > 0 && (explode_place_rect[i] & ter_rects.in_frame) == explode_place_rect[i]) {
|
||||
text_rect = explode_place_rect[i];
|
||||
text_rect.top += 13;
|
||||
text_rect.height() = 10;
|
||||
|
@@ -17,6 +17,12 @@ struct word_rect_t {
|
||||
word_rect_t() {}
|
||||
};
|
||||
|
||||
struct terrain_screen_rects_t {
|
||||
rectangle from;
|
||||
rectangle to;
|
||||
rectangle in_frame;
|
||||
};
|
||||
|
||||
enum {
|
||||
TALK_DUNNO = -1,
|
||||
TALK_BUY = -2,
|
||||
@@ -67,5 +73,6 @@ void place_talk_str(std::string str_to_place,std::string str_to_place2,short col
|
||||
short scan_for_response(const char *str);
|
||||
void refresh_talking();
|
||||
graf_pos calc_item_rect(int num,rectangle& to_rect);
|
||||
terrain_screen_rects_t terrain_screen_rects();
|
||||
|
||||
#endif /* defined(BOE_NEWGRAPH_H) */
|
||||
|
@@ -77,6 +77,18 @@ void rect_draw_some_item(const sf::Texture& src_gworld,rectangle src_rect,sf::Re
|
||||
rect_draw_some_item(src_gworld, src_rect, targ_gworld, targ_rect, sf::RenderStates(mode));
|
||||
}
|
||||
|
||||
// I added this because I tried using clip_rect to fix missiles/booms and it didn't work.
|
||||
void rect_draw_some_item(const sf::Texture& src_gworld,rectangle src_rect,sf::RenderTarget& targ_gworld,rectangle targ_rect,rectangle in_frame,sf::BlendMode mode){
|
||||
rectangle targ_clipped = targ_rect & in_frame;
|
||||
if(targ_clipped.empty()) return;
|
||||
rectangle src_clipped = src_rect;
|
||||
src_clipped.top += (targ_clipped.top - targ_rect.top);
|
||||
src_clipped.left += (targ_clipped.left - targ_rect.left);
|
||||
src_clipped.bottom += (targ_clipped.bottom - targ_rect.bottom);
|
||||
src_clipped.right += (targ_clipped.right - targ_rect.right);
|
||||
rect_draw_some_item(src_gworld, src_rect, targ_gworld, targ_clipped, sf::RenderStates(mode));
|
||||
}
|
||||
|
||||
void rect_draw_some_item(const sf::Texture& src_gworld,rectangle src_rect,sf::RenderTarget& targ_gworld,rectangle targ_rect,sf::RenderStates mode) {
|
||||
rectangle src_gworld_rect(src_gworld), targ_gworld_rect(targ_gworld);
|
||||
src_rect &= src_gworld_rect;
|
||||
|
@@ -21,6 +21,7 @@
|
||||
void init_shaders();
|
||||
void rect_draw_some_item(sf::RenderTarget& targ_gworld,rectangle targ_rect);
|
||||
void rect_draw_some_item(const sf::Texture& src_gworld,rectangle src_rect,sf::RenderTarget& targ_gworld,rectangle targ_rect,sf::BlendMode mode = sf::BlendNone);
|
||||
void rect_draw_some_item(const sf::Texture& src_gworld,rectangle src_rect,sf::RenderTarget& targ_gworld,rectangle targ_rect,rectangle in_frame,sf::BlendMode mode = sf::BlendNone);
|
||||
void rect_draw_some_item(const sf::Texture& src_gworld,rectangle src_rect,const sf::Texture& mask_gworld,sf::RenderTarget& targ_gworld,rectangle targ_rect);
|
||||
void draw_splash(const sf::Texture& splash, sf::RenderWindow& targ, rectangle dest_rect);
|
||||
|
||||
|
112
src/location.cpp
112
src/location.cpp
@@ -11,6 +11,7 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
|
||||
eDirection& operator++ (eDirection& me, int) {
|
||||
if(me == DIR_HERE) return me = DIR_N;
|
||||
@@ -31,6 +32,10 @@ short dist(location p1,location p2){
|
||||
return hypot(p1.x - p2.x, p1.y - p2.y);
|
||||
}
|
||||
|
||||
float fdist(location p1,location p2){
|
||||
return hypot(p1.x - p2.x, p1.y - p2.y);
|
||||
}
|
||||
|
||||
short vdist(location p1,location p2) {
|
||||
short i,j;
|
||||
i = abs((long double)p1.x - p2.x);
|
||||
@@ -101,6 +106,7 @@ bool rectangle::contains(int x, int y) const {
|
||||
}
|
||||
|
||||
bool rectangle::empty() const {
|
||||
if(right < left || bottom < top) return true;
|
||||
return height() == 0 && width() == 0;
|
||||
}
|
||||
|
||||
@@ -306,3 +312,109 @@ std::ostream& operator<< (std::ostream& out, info_rect_t r) {
|
||||
out << " -- \"" << r.descr << '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
bool is_on_screen(location loc, location view_center, int radius) {
|
||||
return loc.x >= view_center.x - radius
|
||||
&& loc.x <= view_center.x + radius
|
||||
&& loc.y >= view_center.y - radius
|
||||
&& loc.y <= view_center.y + radius;
|
||||
}
|
||||
|
||||
location between_anchor_points(location anchor1, location anchor2, int padding){
|
||||
// First check the point directly between anchor1 and anchor2 (rounding towards anchor1)
|
||||
float center_x_f = (anchor1.x + anchor2.x) / 2.0;
|
||||
float center_y_f = (anchor1.y + anchor2.y) / 2.0;
|
||||
location point_between;
|
||||
if(anchor1.x < anchor2.x){
|
||||
point_between.x = floor(center_x_f);
|
||||
}else{
|
||||
point_between.x = ceil(center_x_f);
|
||||
}
|
||||
if(anchor1.y < anchor2.y){
|
||||
point_between.y = floor(center_y_f);
|
||||
}else{
|
||||
point_between.y = ceil(center_y_f);
|
||||
}
|
||||
|
||||
// Then if necessary, move back towards anchor1 until it is visible with the desired padding
|
||||
int padded_radius = SCREEN_RADIUS - padding;
|
||||
while(!is_on_screen(anchor1, point_between, padded_radius)){
|
||||
if(anchor1.x < point_between.x - padded_radius){
|
||||
--point_between.x;
|
||||
}else if(anchor1.x > point_between.x + padded_radius){
|
||||
++point_between.x;
|
||||
}
|
||||
if(anchor1.y < point_between.y - padded_radius){
|
||||
--point_between.y;
|
||||
}else if(anchor1.y > point_between.y + padded_radius){
|
||||
++point_between.y;
|
||||
}
|
||||
}
|
||||
return point_between;
|
||||
}
|
||||
|
||||
std::vector<location> points_containing_most(std::vector<location> points, int padding) {
|
||||
// Find the bounding box of the given points:
|
||||
int min_x = std::numeric_limits<int>::max();
|
||||
int min_y = min_x;
|
||||
int max_x = std::numeric_limits<int>::min();
|
||||
int max_y = max_x;
|
||||
|
||||
for(location p : points){
|
||||
if(p.x < min_x) min_x = p.x;
|
||||
if(p.y < min_y) min_y = p.y;
|
||||
if(p.x > max_x) max_x = p.x;
|
||||
if(p.y > max_y) max_y = p.y;
|
||||
}
|
||||
|
||||
// Expand the bounds by the view radius:
|
||||
int padded_radius = SCREEN_RADIUS - padding;
|
||||
min_x -= padded_radius; min_y -= padded_radius;
|
||||
max_x += padded_radius; max_y += padded_radius;
|
||||
// This function's output may include out-of-bounds points, but those will be eliminated
|
||||
// by calling closest_point() with an anchor point that is in-bounds.
|
||||
|
||||
// Calculate how many of the points can be seen from each point in the bounding box:
|
||||
std::vector<std::pair<location, int>> points_seen_from;
|
||||
location checking_point;
|
||||
for(checking_point.x = min_x; checking_point.x <= max_x; ++checking_point.x){
|
||||
for(checking_point.y = min_y; checking_point.y <= max_y; ++checking_point.y){
|
||||
int can_see = 0;
|
||||
for(location p : points){
|
||||
if(is_on_screen(p, checking_point, padded_radius)) ++can_see;
|
||||
}
|
||||
points_seen_from.push_back(std::make_pair(checking_point, can_see));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort candidates by how many of the points they see
|
||||
std::sort(points_seen_from.begin(), points_seen_from.end(), [](std::pair<location,int> pair1, std::pair<location,int> pair2) -> bool {
|
||||
return pair1.second > pair2.second;
|
||||
});
|
||||
int max_seen = points_seen_from[0].second;
|
||||
|
||||
// Return all of them that see the max number
|
||||
std::vector<location> return_points;
|
||||
for(auto pair : points_seen_from){
|
||||
if(pair.second == max_seen) return_points.push_back(pair.first);
|
||||
}
|
||||
|
||||
return return_points;
|
||||
}
|
||||
|
||||
int closest_point_idx(std::vector<location> points, location anchor) {
|
||||
float min_distance = std::numeric_limits<float>::max();
|
||||
int min_idx = -1;
|
||||
for(int idx = 0; idx < points.size(); ++idx){
|
||||
float distance = fdist(points[idx], anchor);
|
||||
if(distance < min_distance){
|
||||
min_distance = distance;
|
||||
min_idx = idx;
|
||||
}
|
||||
}
|
||||
return min_idx;
|
||||
}
|
||||
|
||||
location closest_point(std::vector<location> points, location anchor) {
|
||||
return points[closest_point_idx(points, anchor)];
|
||||
}
|
@@ -154,6 +154,7 @@ bool operator != (rectangle r1, rectangle r2);
|
||||
rectangle operator&(rectangle one, rectangle two);
|
||||
rectangle rectunion(rectangle one, rectangle two);
|
||||
short dist(location p1,location p2);
|
||||
float fdist(location p1,location p2);
|
||||
short vdist(location p1,location p2);
|
||||
|
||||
location loc(int a, int b);
|
||||
@@ -172,4 +173,30 @@ std::ostream& operator<< (std::ostream& out, rectangle r);
|
||||
std::istream& operator>> (std::istream& in, rectangle& r);
|
||||
std::ostream& operator<< (std::ostream& out, info_rect_t r);
|
||||
|
||||
const int SCREEN_RADIUS=4;
|
||||
|
||||
// CAMERA UTILITY FUNCTIONS
|
||||
|
||||
// Whether a tile is within a radius of a given view center.
|
||||
// 4 is the actual tile radius of the terrain screen.
|
||||
bool is_on_screen(location loc, location view_center, int radius=SCREEN_RADIUS);
|
||||
|
||||
// Default view center: the actual current view center
|
||||
extern location center;
|
||||
inline bool is_on_screen(location loc, int radius=SCREEN_RADIUS) {
|
||||
return is_on_screen(loc, center, radius);
|
||||
}
|
||||
|
||||
// Calculate the closest point to the center of two anchors that is
|
||||
// guaranteed to contain the first anchor with the given amount of padding
|
||||
location between_anchor_points(location anchor1, location anchor2, int padding=1);
|
||||
|
||||
// Get all view center points which contain the greatest possible number of the given points
|
||||
// with the given amount of padding
|
||||
std::vector<location> points_containing_most(std::vector<location> points, int padding=1);
|
||||
|
||||
// Find which of the given points is closest to the given anchor point.
|
||||
int closest_point_idx(std::vector<location> points, location anchor);
|
||||
location closest_point(std::vector<location> points, location anchor);
|
||||
|
||||
#endif
|
||||
|
@@ -80,6 +80,9 @@ extern bool cur_scen_is_mac;
|
||||
short specials_res_id;
|
||||
char start_name[256];
|
||||
|
||||
// This is just to make location.hpp compile, and represents nothing:
|
||||
location center;
|
||||
|
||||
static void process_args(int argc, char* argv[]) {
|
||||
preprocess_args(argc, argv);
|
||||
clara::Args args(argc, argv);
|
||||
|
Reference in New Issue
Block a user