1 #include "movement.h"
2 
3 #include "building/building.h"
4 #include "building/destruction.h"
5 #include "building/roadblock.h"
6 #include "core/calc.h"
7 #include "core/config.h"
8 #include "figure/combat.h"
9 #include "figure/route.h"
10 #include "figure/service.h"
11 #include "game/time.h"
12 #include "map/bridge.h"
13 #include "map/building.h"
14 #include "map/figure.h"
15 #include "map/grid.h"
16 #include "map/property.h"
17 #include "map/random.h"
18 #include "map/road_access.h"
19 #include "map/routing_terrain.h"
20 #include "map/terrain.h"
21 
22 #define PALISADE_HP   60
23 #define BUILDING_HP   10
24 #define WALL_HP      200
25 #define GATEHOUSE_HP 150
26 
advance_tick(figure * f)27 static void advance_tick(figure *f)
28 {
29     switch (f->direction) {
30         case DIR_0_TOP:
31             f->cross_country_y--;
32             break;
33         case DIR_1_TOP_RIGHT:
34             f->cross_country_x++;
35             f->cross_country_y--;
36             break;
37         case DIR_2_RIGHT:
38             f->cross_country_x++;
39             break;
40         case DIR_3_BOTTOM_RIGHT:
41             f->cross_country_x++;
42             f->cross_country_y++;
43             break;
44         case DIR_4_BOTTOM:
45             f->cross_country_y++;
46             break;
47         case DIR_5_BOTTOM_LEFT:
48             f->cross_country_x--;
49             f->cross_country_y++;
50             break;
51         case DIR_6_LEFT:
52             f->cross_country_x--;
53             break;
54         case DIR_7_TOP_LEFT:
55             f->cross_country_x--;
56             f->cross_country_y--;
57             break;
58         default:
59             break;
60     }
61     if (f->height_adjusted_ticks) {
62         f->height_adjusted_ticks--;
63         if (f->height_adjusted_ticks > 0) {
64             f->is_ghost = 1;
65             if (f->current_height < f->target_height) {
66                 f->current_height++;
67             }
68             if (f->current_height > f->target_height) {
69                 f->current_height--;
70             }
71         } else {
72             f->is_ghost = 0;
73         }
74     } else {
75         if (f->current_height) {
76             f->current_height--;
77         }
78     }
79 }
80 
set_target_height_bridge(figure * f)81 static void set_target_height_bridge(figure *f)
82 {
83     f->height_adjusted_ticks = 18;
84     f->target_height = map_bridge_height(f->grid_offset);
85 }
86 
get_permission_for_figure_type(figure * f)87 static roadblock_permission get_permission_for_figure_type(figure* f)
88 {
89     switch (f->type) {
90     case FIGURE_ENGINEER:
91     case FIGURE_PREFECT:
92         return PERMISSION_MAINTENANCE;
93         break;
94     case FIGURE_PRIEST:
95         return PERMISSION_PRIEST;
96         break;
97     case FIGURE_MARKET_TRADER:
98         return PERMISSION_MARKET;
99         break;
100     case FIGURE_GLADIATOR:
101     case FIGURE_CHARIOTEER:
102     case FIGURE_ACTOR:
103     case FIGURE_LION_TAMER:
104     case FIGURE_BARKEEP:
105         return PERMISSION_ENTERTAINER;
106         break;
107     case FIGURE_SURGEON:
108     case FIGURE_DOCTOR:
109     case FIGURE_BARBER:
110     case FIGURE_BATHHOUSE_WORKER:
111         return PERMISSION_MEDICINE;
112     case FIGURE_SCHOOL_CHILD:
113     case FIGURE_TEACHER:
114     case FIGURE_LIBRARIAN:
115         return PERMISSION_EDUCATION;
116     case FIGURE_TAX_COLLECTOR:
117         return PERMISSION_TAX_COLLECTOR;
118     case FIGURE_LABOR_SEEKER:
119         return PERMISSION_LABOR_SEEKER;
120     case FIGURE_MISSIONARY:
121         return PERMISSION_MISSIONARY;
122     case FIGURE_WATCHMAN:
123         return PERMISSION_WATCHMAN;
124     default:
125         return PERMISSION_NONE;
126         break;
127     }
128 }
129 
move_to_next_tile(figure * f)130 static void move_to_next_tile(figure *f)
131 {
132     int old_x = f->x;
133     int old_y = f->y;
134     map_figure_delete(f);
135     switch (f->direction) {
136         default:
137             return;
138         case DIR_0_TOP:
139             f->y--;
140             break;
141         case DIR_1_TOP_RIGHT:
142             f->x++; f->y--;
143             break;
144         case DIR_2_RIGHT:
145             f->x++;
146             break;
147         case DIR_3_BOTTOM_RIGHT:
148             f->x++; f->y++;
149             break;
150         case DIR_4_BOTTOM:
151             f->y++;
152             break;
153         case DIR_5_BOTTOM_LEFT:
154             f->x--; f->y++;
155             break;
156         case DIR_6_LEFT:
157             f->x--;
158             break;
159         case DIR_7_TOP_LEFT:
160             f->x--; f->y--;
161             break;
162     }
163     f->grid_offset += map_grid_direction_delta(f->direction);
164     map_figure_add(f);
165     if (map_terrain_is(f->grid_offset, TERRAIN_ROAD)) {
166         f->is_on_road = 1;
167         if (map_terrain_is(f->grid_offset, TERRAIN_WATER)) { // bridge
168             set_target_height_bridge(f);
169         }
170     } else {
171         f->is_on_road = 0;
172     }
173     figure_combat_attack_figure_at(f, f->grid_offset);
174     f->previous_tile_x = old_x;
175     f->previous_tile_y = old_y;
176 }
177 
set_next_route_tile_direction(figure * f)178 static void set_next_route_tile_direction(figure *f)
179 {
180     if (f->routing_path_id > 0) {
181         if (f->routing_path_current_tile < f->routing_path_length) {
182             f->direction = figure_route_get_direction(f->routing_path_id, f->routing_path_current_tile);
183         } else {
184             figure_route_remove(f);
185             f->direction = DIR_FIGURE_AT_DESTINATION;
186         }
187     } else { // should be at destination
188         f->direction = calc_general_direction(f->x, f->y, f->destination_x, f->destination_y);
189         if (f->direction != DIR_FIGURE_AT_DESTINATION) {
190             f->direction = DIR_FIGURE_LOST;
191         }
192     }
193 }
194 
advance_route_tile(figure * f,int roaming_enabled)195 static void advance_route_tile(figure *f, int roaming_enabled)
196 {
197     if (f->direction >= 8) {
198         return;
199     }
200     int target_grid_offset = f->grid_offset + map_grid_direction_delta(f->direction);
201     if (f->is_boat) {
202         if (!map_terrain_is(target_grid_offset, TERRAIN_WATER)) {
203             f->direction = DIR_FIGURE_REROUTE;
204         }
205     } else if (f->terrain_usage == TERRAIN_USAGE_ENEMY) {
206         if (!map_routing_noncitizen_is_passable(target_grid_offset)) {
207             f->direction = DIR_FIGURE_REROUTE;
208         } else if (map_routing_is_destroyable(target_grid_offset)) {
209             int cause_damage = 1;
210             int max_damage = 0;
211             switch (map_routing_get_destroyable(target_grid_offset)) {
212                 case DESTROYABLE_BUILDING:
213                 {
214                     building *b = building_get(map_building_at(target_grid_offset));
215                     switch (b->type) {
216                         case BUILDING_PALISADE:
217                             max_damage = PALISADE_HP;
218                             break;
219                         default:
220                             max_damage = BUILDING_HP;
221                             break;
222                     }
223                     break;
224                 }
225                 case DESTROYABLE_AQUEDUCT_GARDEN:
226                     if (map_terrain_is(target_grid_offset, TERRAIN_GARDEN | TERRAIN_ACCESS_RAMP | TERRAIN_RUBBLE)) {
227                         cause_damage = 0;
228                     } else {
229                         max_damage = BUILDING_HP;
230                     }
231                     break;
232                 case DESTROYABLE_WALL:
233                     max_damage = WALL_HP;
234                     break;
235                 case DESTROYABLE_GATEHOUSE:
236                     max_damage = GATEHOUSE_HP;
237                     break;
238             }
239             if (cause_damage) {
240                 f->attack_direction = f->direction;
241                 f->direction = DIR_FIGURE_ATTACK;
242                 if (!(game_time_tick() & 3)) {
243                     building_destroy_increase_enemy_damage(target_grid_offset, max_damage);
244                 }
245             }
246         }
247     } else if (f->terrain_usage == TERRAIN_USAGE_WALLS) {
248         if (!map_routing_is_wall_passable(target_grid_offset)) {
249             f->direction = DIR_FIGURE_REROUTE;
250         }
251     } else if (map_terrain_is(target_grid_offset, TERRAIN_ROAD | TERRAIN_ACCESS_RAMP)) {
252         if (roaming_enabled && map_terrain_is(target_grid_offset, TERRAIN_BUILDING)) {
253             building* b = building_get(map_building_at(target_grid_offset));
254             if (b->type == BUILDING_GATEHOUSE) {
255                 // do not allow roaming through gatehouse
256                 f->direction = DIR_FIGURE_REROUTE;
257             }
258             if (building_type_is_roadblock(b->type)) {
259                 // do not allow roaming through roadblock
260                 int permission = get_permission_for_figure_type(f);
261                 if (!building_roadblock_get_permission(permission, b)) {
262                     f->direction = DIR_FIGURE_REROUTE;
263                 }
264             }
265         }
266     } else if (map_terrain_is(target_grid_offset, TERRAIN_BUILDING)) {
267         int type = building_get(map_building_at(target_grid_offset))->type;
268         switch (type) {
269             case BUILDING_WAREHOUSE:
270             case BUILDING_GRANARY:
271             case BUILDING_TRIUMPHAL_ARCH:
272             case BUILDING_FORT_GROUND:
273                 break; // OK to walk
274             default:
275                 f->direction = DIR_FIGURE_REROUTE;
276         }
277     } else if (map_terrain_is(target_grid_offset, TERRAIN_IMPASSABLE)) {
278         f->direction = DIR_FIGURE_REROUTE;
279     }
280 }
281 
282 
walk_ticks(figure * f,int num_ticks,int roaming_enabled)283 static void walk_ticks(figure *f, int num_ticks, int roaming_enabled)
284 {
285     while (num_ticks > 0) {
286         num_ticks--;
287         f->progress_on_tile++;
288         if (f->progress_on_tile < 15) {
289             advance_tick(f);
290         } else {
291             figure_service_provide_coverage(f);
292             f->progress_on_tile = 15;
293             if (f->routing_path_id <= 0) {
294                 figure_route_add(f);
295             }
296             set_next_route_tile_direction(f);
297             advance_route_tile(f, roaming_enabled);
298             if (f->direction >= 8) {
299                 break;
300             }
301             f->routing_path_current_tile++;
302             f->previous_tile_direction = f->direction;
303             f->progress_on_tile = 0;
304             move_to_next_tile(f);
305             advance_tick(f);
306         }
307     }
308 }
309 
figure_movement_init_roaming(figure * f)310 void figure_movement_init_roaming(figure *f)
311 {
312     building *b = building_get(f->building_id);
313     f->progress_on_tile = 15;
314     f->roam_choose_destination = 0;
315     f->roam_ticks_until_next_turn = -1;
316     f->roam_turn_direction = 2;
317     if (config_get(CONFIG_GP_CH_ROAMERS_DONT_SKIP_CORNERS)) {
318         f->disallow_diagonal = 1;
319     }
320     int roam_dir = b->figure_roam_direction;
321     b->figure_roam_direction += 2;
322     if (b->figure_roam_direction > 6) {
323         b->figure_roam_direction = 0;
324     }
325     int x = b->x;
326     int y = b->y;
327     switch (roam_dir) {
328         case DIR_0_TOP: y -= 8; break;
329         case DIR_2_RIGHT: x += 8; break;
330         case DIR_4_BOTTOM: y += 8; break;
331         case DIR_6_LEFT: x -= 8; break;
332     }
333     map_grid_bound(&x, &y);
334     int x_road, y_road;
335     if (map_closest_road_within_radius(x, y, 1, 6, &x_road, &y_road)) {
336         f->destination_x = x_road;
337         f->destination_y = y_road;
338     } else {
339         f->roam_choose_destination = 1;
340     }
341 }
342 
roam_set_direction(figure * f)343 static void roam_set_direction(figure *f)
344 {
345     int grid_offset = map_grid_offset(f->x, f->y);
346     int direction = calc_general_direction(f->x, f->y, f->destination_x, f->destination_y);
347     if (direction >= 8) {
348         direction = 0;
349     }
350     int road_offset_dir1 = 0;
351     int road_dir1 = 0;
352     for (int i = 0, dir = direction; i < 8; i++) {
353         if (dir % 2 == 0 && map_terrain_is(grid_offset + map_grid_direction_delta(dir), TERRAIN_ROAD)) {
354             road_dir1 = dir;
355             break;
356         }
357         dir++;
358         if (dir > 7) {
359             dir = 0;
360         }
361         road_offset_dir1++;
362     }
363     int road_offset_dir2 = 0;
364     int road_dir2 = 0;
365     for (int i = 0, dir = direction; i < 8; i++) {
366         if (dir % 2 == 0 && map_terrain_is(grid_offset + map_grid_direction_delta(dir), TERRAIN_ROAD)) {
367             road_dir2 = dir;
368             break;
369         }
370         dir--;
371         if (dir < 0) {
372             dir = 7;
373         }
374         road_offset_dir2++;
375     }
376     if (road_offset_dir1 <= road_offset_dir2) {
377         f->direction = road_dir1;
378         f->roam_turn_direction = 2;
379     } else {
380         f->direction = road_dir2;
381         f->roam_turn_direction = -2;
382     }
383     f->roam_ticks_until_next_turn = 5;
384 }
385 
figure_movement_move_ticks(figure * f,int num_ticks)386 void figure_movement_move_ticks(figure *f, int num_ticks)
387 {
388     walk_ticks(f, num_ticks, 0);
389 }
390 
figure_movement_move_ticks_with_percentage(figure * f,int num_ticks,int tick_percentage)391 void figure_movement_move_ticks_with_percentage(figure* f, int num_ticks, int tick_percentage)
392 {
393     int progress = f->progress_to_next_tick + tick_percentage;
394 
395     if (progress >= 100) {
396         progress -= 100;
397         num_ticks++;
398     } else if (progress <= -100) {
399         progress += 100;
400         num_ticks--;
401     }
402     f->progress_to_next_tick = (char) progress;
403 
404     walk_ticks(f, num_ticks, 0);
405 }
406 
figure_movement_move_ticks_tower_sentry(figure * f,int num_ticks)407 void figure_movement_move_ticks_tower_sentry(figure *f, int num_ticks)
408 {
409     while (num_ticks > 0) {
410         num_ticks--;
411         f->progress_on_tile++;
412         if (f->progress_on_tile < 15) {
413             advance_tick(f);
414         } else {
415             f->progress_on_tile = 15;
416         }
417     }
418 }
419 
figure_movement_follow_ticks(figure * f,int num_ticks)420 void figure_movement_follow_ticks(figure *f, int num_ticks)
421 {
422     const figure *leader = figure_get(f->leading_figure_id);
423     if (f->x == f->source_x && f->y == f->source_y) {
424         f->is_ghost = 1;
425     }
426     while (num_ticks > 0) {
427         num_ticks--;
428         f->progress_on_tile++;
429         if (f->progress_on_tile < 15) {
430             advance_tick(f);
431         } else {
432             f->progress_on_tile = 15;
433             f->direction = calc_general_direction(f->x, f->y,
434                 leader->previous_tile_x, leader->previous_tile_y);
435             if (f->direction >= 8) {
436                 break;
437             }
438             f->previous_tile_direction = f->direction;
439             f->progress_on_tile = 0;
440             move_to_next_tile(f);
441             advance_tick(f);
442         }
443     }
444 }
445 
figure_movement_follow_ticks_with_percentage(figure * f,int num_ticks,int tick_percentage)446 void figure_movement_follow_ticks_with_percentage(figure* f, int num_ticks, int tick_percentage)
447 {
448     int progress = f->progress_to_next_tick + tick_percentage;
449 
450     if (progress >= 100) {
451         progress -= 100;
452         num_ticks++;
453     } else if (progress <= -100) {
454         progress += 100;
455         num_ticks--;
456     }
457     f->progress_to_next_tick = (char) progress;
458 
459     const figure* leader = figure_get(f->leading_figure_id);
460     if (f->x == f->source_x && f->y == f->source_y) {
461         f->is_ghost = 1;
462     }
463     while (num_ticks > 0) {
464         num_ticks--;
465         f->progress_on_tile++;
466         if (f->progress_on_tile < 15) {
467             advance_tick(f);
468         }
469         else {
470             f->progress_on_tile = 15;
471             f->direction = calc_general_direction(f->x, f->y,
472                 leader->previous_tile_x, leader->previous_tile_y);
473             if (f->direction >= 8) {
474                 break;
475             }
476             f->previous_tile_direction = f->direction;
477             f->progress_on_tile = 0;
478             move_to_next_tile(f);
479             advance_tick(f);
480         }
481     }
482 }
483 
figure_movement_roam_ticks(figure * f,int num_ticks)484 void figure_movement_roam_ticks(figure *f, int num_ticks)
485 {
486     if (f->roam_choose_destination == 0) {
487         walk_ticks(f, num_ticks, 1);
488         if (f->direction == DIR_FIGURE_AT_DESTINATION) {
489             f->roam_choose_destination = 1;
490             f->roam_length = 0;
491         } else if (f->direction == DIR_FIGURE_REROUTE || f->direction == DIR_FIGURE_LOST) {
492             f->roam_choose_destination = 1;
493         }
494         if (f->roam_choose_destination) {
495             f->roam_ticks_until_next_turn = 100;
496             f->direction = f->previous_tile_direction;
497         } else {
498             return;
499         }
500     }
501     // no destination: walk to end of tile and pick a direction
502     while (num_ticks > 0) {
503         num_ticks--;
504         f->progress_on_tile++;
505         if (f->progress_on_tile < 15) {
506             advance_tick(f);
507         } else {
508             f->progress_on_tile = 15;
509             f->roam_random_counter++;
510             int came_from_direction = (f->previous_tile_direction + 4) % 8;
511             if (figure_service_provide_coverage(f)) {
512                 return;
513             }
514             int road_tiles[8];
515             int permission = get_permission_for_figure_type(f);
516             int adjacent_road_tiles = map_get_adjacent_road_tiles_for_roaming(f->grid_offset, road_tiles, permission);
517             if (adjacent_road_tiles == 3 && map_get_diagonal_road_tiles_for_roaming(f->grid_offset, road_tiles) >= 5) {
518                 // go in the straight direction of a double-wide road
519                 adjacent_road_tiles = 2;
520                 if (came_from_direction == DIR_0_TOP || came_from_direction == DIR_4_BOTTOM) {
521                     if (road_tiles[0] && road_tiles[4]) {
522                         road_tiles[2] = road_tiles[6] = 0;
523                     } else {
524                         road_tiles[0] = road_tiles[4] = 0;
525                     }
526                 } else {
527                     if (road_tiles[2] && road_tiles[6]) {
528                         road_tiles[0] = road_tiles[4] = 0;
529                     } else {
530                         road_tiles[2] = road_tiles[6] = 0;
531                     }
532                 }
533             }
534             if (adjacent_road_tiles == 4 && map_get_diagonal_road_tiles_for_roaming(f->grid_offset, road_tiles) >= 8) {
535                 // go straight on when all surrounding tiles are road
536                 adjacent_road_tiles = 2;
537                 if (came_from_direction == DIR_0_TOP || came_from_direction == DIR_4_BOTTOM) {
538                     road_tiles[2] = road_tiles[6] = 0;
539                 } else {
540                     road_tiles[0] = road_tiles[4] = 0;
541                 }
542             }
543             if (adjacent_road_tiles <= 0) {
544                 f->roam_length = f->max_roam_length; // end roaming walk
545                 return;
546             }
547             if (adjacent_road_tiles == 1) {
548                 int dir = 0;
549                 do {
550                     f->direction = 2 * dir;
551                 } while (!road_tiles[f->direction] && dir++ < 4);
552             } else if (adjacent_road_tiles == 2) {
553                 if (f->roam_ticks_until_next_turn == -1) {
554                     roam_set_direction(f);
555                     came_from_direction = -1;
556                 }
557                 // 1. continue in the same direction
558                 // 2. turn in the direction given by roam_turn_direction
559                 int dir = 0;
560                 do {
561                     if (road_tiles[f->direction] && f->direction != came_from_direction) {
562                         break;
563                     }
564                     f->direction += f->roam_turn_direction;
565                     if (f->direction > 6) {
566                         f->direction = 0;
567                     } else if (f->direction < 0) {
568                         f->direction = 6;
569                     }
570                 } while (dir++ < 4);
571             } else { // > 2 road tiles
572                 f->direction = (f->roam_random_counter + map_random_get(f->grid_offset)) & 6;
573                 if (!road_tiles[f->direction] || f->direction == came_from_direction) {
574                     f->roam_ticks_until_next_turn--;
575                     if (f->roam_ticks_until_next_turn <= 0) {
576                         roam_set_direction(f);
577                         came_from_direction = -1;
578                     }
579                     int dir = 0;
580                     do {
581                         if (road_tiles[f->direction] && f->direction != came_from_direction) {
582                             break;
583                         }
584                         f->direction += f->roam_turn_direction;
585                         if (f->direction > 6) {
586                             f->direction = 0;
587                         } else if (f->direction < 0) {
588                             f->direction = 6;
589                         }
590                     } while (dir++ < 4);
591                 }
592             }
593             f->routing_path_current_tile++;
594             f->previous_tile_direction = f->direction;
595             f->progress_on_tile = 0;
596             move_to_next_tile(f);
597             advance_tick(f);
598         }
599     }
600 }
601 
figure_movement_advance_attack(figure * f)602 void figure_movement_advance_attack(figure *f)
603 {
604     if (f->progress_on_tile <= 5) {
605         f->progress_on_tile++;
606         advance_tick(f);
607     }
608 }
609 
figure_movement_set_cross_country_direction(figure * f,int x_src,int y_src,int x_dst,int y_dst,int is_missile)610 void figure_movement_set_cross_country_direction(figure *f, int x_src, int y_src, int x_dst, int y_dst, int is_missile)
611 {
612     // all x/y are in 1/15th of a tile
613     f->cc_destination_x = x_dst;
614     f->cc_destination_y = y_dst;
615     f->cc_delta_x = (x_src > x_dst) ? (x_src - x_dst) : (x_dst - x_src);
616     f->cc_delta_y = (y_src > y_dst) ? (y_src - y_dst) : (y_dst - y_src);
617     if (f->cc_delta_x < f->cc_delta_y) {
618         f->cc_delta_xy = 2 * f->cc_delta_x - f->cc_delta_y;
619     } else if (f->cc_delta_y < f->cc_delta_x) {
620         f->cc_delta_xy = 2 * f->cc_delta_y - f->cc_delta_x;
621     } else { // equal
622         f->cc_delta_xy = 0;
623     }
624     if (is_missile) {
625         f->direction = calc_missile_direction(x_src, y_src, x_dst, y_dst);
626     } else {
627         f->direction = calc_general_direction(x_src, y_src, x_dst, y_dst);
628         if (f->cc_delta_y > 2 * f->cc_delta_x) {
629             switch (f->direction) {
630                 case DIR_1_TOP_RIGHT: case DIR_7_TOP_LEFT: f->direction = DIR_0_TOP; break;
631                 case DIR_3_BOTTOM_RIGHT: case DIR_5_BOTTOM_LEFT: f->direction = DIR_4_BOTTOM; break;
632             }
633         }
634         if (f->cc_delta_x > 2 * f->cc_delta_y) {
635             switch (f->direction) {
636                 case DIR_1_TOP_RIGHT: case DIR_3_BOTTOM_RIGHT: f->direction = DIR_2_RIGHT; break;
637                 case DIR_5_BOTTOM_LEFT: case DIR_7_TOP_LEFT: f->direction = DIR_6_LEFT; break;
638             }
639         }
640     }
641     if (f->cc_delta_x >= f->cc_delta_y) {
642         f->cc_direction = 1;
643     } else {
644         f->cc_direction = 2;
645     }
646 }
647 
figure_movement_set_cross_country_destination(figure * f,int x_dst,int y_dst)648 void figure_movement_set_cross_country_destination(figure *f, int x_dst, int y_dst)
649 {
650     f->destination_x = x_dst;
651     f->destination_y = y_dst;
652     figure_movement_set_cross_country_direction(
653         f, f->cross_country_x, f->cross_country_y,
654         15 * x_dst, 15 * y_dst, 0);
655 }
656 
cross_country_update_delta(figure * f)657 static void cross_country_update_delta(figure *f)
658 {
659     if (f->cc_direction == 1) { // x
660         if (f->cc_delta_xy >= 0) {
661             f->cc_delta_xy += 2 * (f->cc_delta_y - f->cc_delta_x);
662         } else {
663             f->cc_delta_xy += 2 * f->cc_delta_y;
664         }
665         f->cc_delta_x--;
666     } else { // y
667         if (f->cc_delta_xy >= 0) {
668             f->cc_delta_xy += 2 * (f->cc_delta_x - f->cc_delta_y);
669         } else {
670             f->cc_delta_xy += 2 * f->cc_delta_x;
671         }
672         f->cc_delta_y--;
673     }
674 }
675 
cross_country_advance_x(figure * f)676 static void cross_country_advance_x(figure *f)
677 {
678     if (f->cross_country_x < f->cc_destination_x) {
679         f->cross_country_x++;
680     } else if (f->cross_country_x > f->cc_destination_x) {
681         f->cross_country_x--;
682     }
683 }
684 
cross_country_advance_y(figure * f)685 static void cross_country_advance_y(figure *f)
686 {
687     if (f->cross_country_y < f->cc_destination_y) {
688         f->cross_country_y++;
689     } else if (f->cross_country_y > f->cc_destination_y) {
690         f->cross_country_y--;
691     }
692 }
693 
cross_country_advance(figure * f)694 static void cross_country_advance(figure *f)
695 {
696     cross_country_update_delta(f);
697     if (f->cc_direction == 2) { // y
698         cross_country_advance_y(f);
699         if (f->cc_delta_xy >= 0) {
700             f->cc_delta_x--;
701             cross_country_advance_x(f);
702         }
703     } else {
704         cross_country_advance_x(f);
705         if (f->cc_delta_xy >= 0) {
706             f->cc_delta_y--;
707             cross_country_advance_y(f);
708         }
709     }
710 }
711 
figure_movement_move_ticks_cross_country(figure * f,int num_ticks)712 int figure_movement_move_ticks_cross_country(figure *f, int num_ticks)
713 {
714     map_figure_delete(f);
715     int is_at_destination = 0;
716     while (num_ticks > 0) {
717         num_ticks--;
718         if (f->missile_damage > 0) {
719             f->missile_damage--;
720         } else {
721             f->missile_damage = 0;
722         }
723         if (f->cc_delta_x + f->cc_delta_y <= 0) {
724             is_at_destination = 1;
725             break;
726         }
727         cross_country_advance(f);
728     }
729     f->x = f->cross_country_x / 15;
730     f->y = f->cross_country_y / 15;
731     f->grid_offset = map_grid_offset(f->x, f->y);
732     if (map_terrain_is(f->grid_offset, TERRAIN_BUILDING)) {
733         f->in_building_wait_ticks = 8;
734     } else if (f->in_building_wait_ticks) {
735         f->in_building_wait_ticks--;
736     }
737     map_figure_add(f);
738     return is_at_destination;
739 }
740 
figure_movement_can_launch_cross_country_missile(int x_src,int y_src,int x_dst,int y_dst)741 int figure_movement_can_launch_cross_country_missile(int x_src, int y_src, int x_dst, int y_dst)
742 {
743     int height = 0;
744     figure *f = figure_get(0); // abuse unused figure 0 as scratch
745     f->cross_country_x = 15 * x_src;
746     f->cross_country_y = 15 * y_src;
747     if (map_terrain_is(map_grid_offset(x_src, y_src), TERRAIN_WALL_OR_GATEHOUSE) || building_get(map_building_at(map_grid_offset(x_src, y_src)))->type == BUILDING_WATCHTOWER) {
748         height = 6;
749     }
750     figure_movement_set_cross_country_direction(f, 15 * x_src, 15 * y_src, 15 * x_dst, 15 * y_dst, 0);
751 
752     for (int guard = 0; guard < 1000; guard++) {
753         for (int i = 0; i < 8; i++) {
754             if (f->cc_delta_x + f->cc_delta_y <= 0) {
755                 return 1;
756             }
757             cross_country_advance(f);
758         }
759         f->x = f->cross_country_x / 15;
760         f->y = f->cross_country_y / 15;
761         if (height) {
762             height--;
763         } else {
764             int grid_offset = map_grid_offset(f->x, f->y);
765             if (map_terrain_is(grid_offset, TERRAIN_WALL | TERRAIN_GATEHOUSE | TERRAIN_TREE)) {
766                 break;
767             }
768             if (map_terrain_is(grid_offset, TERRAIN_BUILDING) && map_property_multi_tile_size(grid_offset) > 1)  {
769                 break;
770             }
771         }
772     }
773     return 0;
774 }
775