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