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