1 #include "routing.h"
2 
3 #include "building/building.h"
4 #include "map/building.h"
5 #include "map/figure.h"
6 #include "map/grid.h"
7 #include "map/road_aqueduct.h"
8 #include "map/routing_data.h"
9 #include "map/terrain.h"
10 
11 #define MAX_QUEUE GRID_SIZE * GRID_SIZE
12 #define GUARD 50000
13 
14 #define UNTIL_STOP 0
15 #define UNTIL_CONTINUE 1
16 
17 static const int ROUTE_OFFSETS[] = {-162, 1, 162, -1, -161, 163, 161, -163};
18 
19 static grid_i16 routing_distance;
20 
21 static struct {
22     int total_routes_calculated;
23     int enemy_routes_calculated;
24 } stats = {0, 0};
25 
26 static struct {
27     int head;
28     int tail;
29     int items[MAX_QUEUE];
30 } queue;
31 
32 static grid_u8 water_drag;
33 
34 static struct {
35     int through_building_id;
36 } state;
37 
clear_distances(void)38 static void clear_distances(void)
39 {
40     map_grid_clear_i16(routing_distance.items);
41 }
42 
enqueue(int next_offset,int dist)43 static void enqueue(int next_offset, int dist)
44 {
45     routing_distance.items[next_offset] = dist;
46     queue.items[queue.tail++] = next_offset;
47     if (queue.tail >= MAX_QUEUE) {
48         queue.tail = 0;
49     }
50 }
51 
valid_offset(int grid_offset)52 static int valid_offset(int grid_offset)
53 {
54     return map_grid_is_valid_offset(grid_offset) && routing_distance.items[grid_offset] == 0;
55 }
56 
route_queue(int source,int dest,void (* callback)(int next_offset,int dist))57 static void route_queue(int source, int dest, void (*callback)(int next_offset, int dist))
58 {
59     clear_distances();
60     queue.head = queue.tail = 0;
61     enqueue(source, 1);
62     while (queue.head != queue.tail) {
63         int offset = queue.items[queue.head];
64         if (offset == dest) {
65             break;
66         }
67         int dist = 1 + routing_distance.items[offset];
68         for (int i = 0; i < 4; i++) {
69             if (valid_offset(offset + ROUTE_OFFSETS[i])) {
70                 callback(offset + ROUTE_OFFSETS[i], dist);
71             }
72         }
73         if (++queue.head >= MAX_QUEUE) {
74             queue.head = 0;
75         }
76     }
77 }
78 
route_queue_until(int source,int (* callback)(int next_offset,int dist))79 static void route_queue_until(int source, int (*callback)(int next_offset, int dist))
80 {
81     clear_distances();
82     queue.head = queue.tail = 0;
83     enqueue(source, 1);
84     while (queue.head != queue.tail) {
85         int offset = queue.items[queue.head];
86         int dist = 1 + routing_distance.items[offset];
87         for (int i = 0; i < 4; i++) {
88             if (valid_offset(offset + ROUTE_OFFSETS[i])) {
89                 if (callback(offset + ROUTE_OFFSETS[i], dist) == UNTIL_STOP) {
90                     break;
91                 }
92             }
93         }
94         if (++queue.head >= MAX_QUEUE) {
95             queue.head = 0;
96         }
97     }
98 }
99 
route_queue_max(int source,int dest,int max_tiles,void (* callback)(int,int))100 static void route_queue_max(int source, int dest, int max_tiles, void (*callback)(int, int))
101 {
102     clear_distances();
103     queue.head = queue.tail = 0;
104     enqueue(source, 1);
105     int tiles = 0;
106     while (queue.head != queue.tail) {
107         int offset = queue.items[queue.head];
108         if (offset == dest) break;
109         if (++tiles > max_tiles) break;
110         int dist = 1 + routing_distance.items[offset];
111         for (int i = 0; i < 4; i++) {
112             if (valid_offset(offset + ROUTE_OFFSETS[i])) {
113                 callback(offset + ROUTE_OFFSETS[i], dist);
114             }
115         }
116         if (++queue.head >= MAX_QUEUE) {
117             queue.head = 0;
118         }
119     }
120 }
121 
route_queue_boat(int source,void (* callback)(int,int))122 static void route_queue_boat(int source, void (*callback)(int, int))
123 {
124     clear_distances();
125     map_grid_clear_u8(water_drag.items);
126     queue.head = queue.tail = 0;
127     enqueue(source, 1);
128     int tiles = 0;
129     while (queue.head != queue.tail) {
130         int offset = queue.items[queue.head];
131         if (++tiles > GUARD) {
132             break;
133         }
134         int drag = terrain_water.items[offset] == WATER_N2_MAP_EDGE ? 4 : 0;
135         if (drag && water_drag.items[offset]++ < drag) {
136             queue.items[queue.tail++] = offset;
137             if (queue.tail >= MAX_QUEUE) {
138                 queue.tail = 0;
139             }
140         } else {
141             int dist = 1 + routing_distance.items[offset];
142             for (int i = 0; i < 4; i++) {
143                 if (valid_offset(offset + ROUTE_OFFSETS[i])) {
144                     callback(offset + ROUTE_OFFSETS[i], dist);
145                 }
146             }
147         }
148         if (++queue.head >= MAX_QUEUE) {
149             queue.head = 0;
150         }
151     }
152 }
153 
route_queue_dir8(int source,void (* callback)(int,int))154 static void route_queue_dir8(int source, void (*callback)(int, int))
155 {
156     clear_distances();
157     queue.head = queue.tail = 0;
158     enqueue(source, 1);
159     int tiles = 0;
160     while (queue.head != queue.tail) {
161         if (++tiles > GUARD) {
162             break;
163         }
164         int offset = queue.items[queue.head];
165         int dist = 1 + routing_distance.items[offset];
166         for (int i = 0; i < 8; i++) {
167             if (valid_offset(offset + ROUTE_OFFSETS[i])) {
168                 callback(offset + ROUTE_OFFSETS[i], dist);
169             }
170         }
171         if (++queue.head >= MAX_QUEUE) {
172             queue.head = 0;
173         }
174     }
175 }
176 
callback_calc_distance(int next_offset,int dist)177 static void callback_calc_distance(int next_offset, int dist)
178 {
179     if (terrain_land_citizen.items[next_offset] >= CITIZEN_0_ROAD) {
180         enqueue(next_offset, dist);
181     }
182 }
183 
map_routing_calculate_distances(int x,int y)184 void map_routing_calculate_distances(int x, int y)
185 {
186     ++stats.total_routes_calculated;
187     route_queue(map_grid_offset(x, y), -1, callback_calc_distance);
188 }
189 
callback_calc_distance_water_boat(int next_offset,int dist)190 static void callback_calc_distance_water_boat(int next_offset, int dist)
191 {
192     if (terrain_water.items[next_offset] != WATER_N1_BLOCKED &&
193         terrain_water.items[next_offset] != WATER_N3_LOW_BRIDGE) {
194         enqueue(next_offset, dist);
195         if (terrain_water.items[next_offset] == WATER_N2_MAP_EDGE) {
196             routing_distance.items[next_offset] += 4;
197         }
198     }
199 }
200 
map_routing_calculate_distances_water_boat(int x,int y)201 void map_routing_calculate_distances_water_boat(int x, int y)
202 {
203     int grid_offset = map_grid_offset(x, y);
204     if (terrain_water.items[grid_offset] == WATER_N1_BLOCKED) {
205         clear_distances();
206     } else {
207         route_queue_boat(grid_offset, callback_calc_distance_water_boat);
208     }
209 }
210 
callback_calc_distance_water_flotsam(int next_offset,int dist)211 static void callback_calc_distance_water_flotsam(int next_offset, int dist)
212 {
213     if (terrain_water.items[next_offset] != WATER_N1_BLOCKED) {
214         enqueue(next_offset, dist);
215     }
216 }
217 
map_routing_calculate_distances_water_flotsam(int x,int y)218 void map_routing_calculate_distances_water_flotsam(int x, int y)
219 {
220     int grid_offset = map_grid_offset(x, y);
221     if (terrain_water.items[grid_offset] == WATER_N1_BLOCKED) {
222         clear_distances();
223     } else {
224         route_queue_dir8(grid_offset, callback_calc_distance_water_flotsam);
225     }
226 }
227 
callback_calc_distance_build_wall(int next_offset,int dist)228 static void callback_calc_distance_build_wall(int next_offset, int dist)
229 {
230     if (terrain_land_citizen.items[next_offset] == CITIZEN_4_CLEAR_TERRAIN) {
231         enqueue(next_offset, dist);
232     }
233 }
234 
callback_calc_distance_build_road(int next_offset,int dist)235 static void callback_calc_distance_build_road(int next_offset, int dist)
236 {
237     int blocked = 0;
238     switch (terrain_land_citizen.items[next_offset]) {
239         case CITIZEN_N3_AQUEDUCT:
240             if (!map_can_place_road_under_aqueduct(next_offset)) {
241                 routing_distance.items[next_offset] = -1;
242                 blocked = 1;
243             }
244             break;
245         case CITIZEN_2_PASSABLE_TERRAIN: // rubble, garden, access ramp
246         case CITIZEN_N1_BLOCKED: // non-empty land
247             blocked = 1;
248             break;
249         default:
250             if (map_terrain_is(next_offset, TERRAIN_BUILDING)) {
251                 blocked = 1;
252             }
253             break;
254     }
255     if (!blocked) {
256         enqueue(next_offset, dist);
257     }
258 }
259 
callback_calc_distance_build_aqueduct(int next_offset,int dist)260 static void callback_calc_distance_build_aqueduct(int next_offset, int dist)
261 {
262     int blocked = 0;
263     switch (terrain_land_citizen.items[next_offset]) {
264         case CITIZEN_N3_AQUEDUCT:
265         case CITIZEN_2_PASSABLE_TERRAIN: // rubble, garden, access ramp
266         case CITIZEN_N1_BLOCKED: // non-empty land
267             blocked = 1;
268             break;
269         default:
270             if (map_terrain_is(next_offset, TERRAIN_BUILDING)) {
271                 if (terrain_land_citizen.items[next_offset] != CITIZEN_N4_RESERVOIR_CONNECTOR) {
272                     blocked = 1;
273                 }
274             }
275             break;
276     }
277     if (map_terrain_is(next_offset, TERRAIN_ROAD) && !map_can_place_aqueduct_on_road(next_offset)) {
278         routing_distance.items[next_offset] = -1;
279         blocked = 1;
280     }
281     if (!blocked) {
282         enqueue(next_offset, dist);
283     }
284 }
285 
map_can_place_initial_road_or_aqueduct(int grid_offset,int is_aqueduct)286 static int map_can_place_initial_road_or_aqueduct(int grid_offset, int is_aqueduct)
287 {
288     if (terrain_land_citizen.items[grid_offset] == CITIZEN_N1_BLOCKED) {
289         // not open land, can only if:
290         // - aqueduct should be placed, and:
291         // - land is a reservoir building OR an aqueduct
292         if (!is_aqueduct) {
293             return 0;
294         }
295         if (map_terrain_is(grid_offset, TERRAIN_AQUEDUCT)) {
296             return 1;
297         }
298         if (map_terrain_is(grid_offset, TERRAIN_BUILDING)) {
299             if (building_get(map_building_at(grid_offset))->type == BUILDING_RESERVOIR) {
300                 return 1;
301             }
302         }
303         return 0;
304     } else if (terrain_land_citizen.items[grid_offset] == CITIZEN_2_PASSABLE_TERRAIN) {
305         // rubble, access ramp, garden
306         return 0;
307     } else if (terrain_land_citizen.items[grid_offset] == CITIZEN_N3_AQUEDUCT) {
308         if (is_aqueduct) {
309             return 0;
310         }
311         if (map_can_place_road_under_aqueduct(grid_offset)) {
312             return 1;
313         }
314         return 0;
315     } else {
316         return 1;
317     }
318 }
319 
map_routing_calculate_distances_for_building(routed_building_type type,int x,int y)320 int map_routing_calculate_distances_for_building(routed_building_type type, int x, int y)
321 {
322     if (type == ROUTED_BUILDING_WALL) {
323         route_queue(map_grid_offset(x, y), -1, callback_calc_distance_build_wall);
324         return 1;
325     }
326     clear_distances();
327     int source_offset = map_grid_offset(x, y);
328     if (!map_can_place_initial_road_or_aqueduct(source_offset, type != ROUTED_BUILDING_ROAD)) {
329         return 0;
330     }
331     if (map_terrain_is(source_offset, TERRAIN_ROAD) &&
332         type != ROUTED_BUILDING_ROAD && !map_can_place_aqueduct_on_road(source_offset)) {
333         return 0;
334     }
335     ++stats.total_routes_calculated;
336     if (type == ROUTED_BUILDING_ROAD) {
337         route_queue(source_offset, -1, callback_calc_distance_build_road);
338     } else {
339         route_queue(source_offset, -1, callback_calc_distance_build_aqueduct);
340     }
341     return 1;
342 }
343 
callback_delete_wall_aqueduct(int next_offset,int dist)344 static int callback_delete_wall_aqueduct(int next_offset, int dist)
345 {
346     if (terrain_land_citizen.items[next_offset] < CITIZEN_0_ROAD) {
347         if (map_terrain_is(next_offset, TERRAIN_AQUEDUCT | TERRAIN_WALL)) {
348             map_terrain_remove(next_offset, TERRAIN_CLEARABLE);
349             return UNTIL_STOP;
350         }
351     } else {
352         enqueue(next_offset, dist);
353     }
354     return UNTIL_CONTINUE;
355 }
356 
map_routing_delete_first_wall_or_aqueduct(int x,int y)357 void map_routing_delete_first_wall_or_aqueduct(int x, int y)
358 {
359     ++stats.total_routes_calculated;
360     route_queue_until(map_grid_offset(x, y), callback_delete_wall_aqueduct);
361 }
362 
is_fighting_friendly(figure * f)363 static int is_fighting_friendly(figure *f)
364 {
365     return f->is_friendly && f->action_state == FIGURE_ACTION_150_ATTACK;
366 }
367 
has_fighting_friendly(int grid_offset)368 static int has_fighting_friendly(int grid_offset)
369 {
370     return map_figure_foreach_until(grid_offset, is_fighting_friendly);
371 }
372 
is_fighting_enemy(figure * f)373 static int is_fighting_enemy(figure *f)
374 {
375     return !f->is_friendly && f->action_state == FIGURE_ACTION_150_ATTACK;
376 }
377 
has_fighting_enemy(int grid_offset)378 static int has_fighting_enemy(int grid_offset)
379 {
380     return map_figure_foreach_until(grid_offset, is_fighting_enemy);
381 }
382 
callback_travel_citizen_land(int next_offset,int dist)383 static void callback_travel_citizen_land(int next_offset, int dist)
384 {
385     if (terrain_land_citizen.items[next_offset] >= 0 && !has_fighting_friendly(next_offset)) {
386         enqueue(next_offset, dist);
387     }
388 }
389 
map_routing_citizen_can_travel_over_land(int src_x,int src_y,int dst_x,int dst_y)390 int map_routing_citizen_can_travel_over_land(int src_x, int src_y, int dst_x, int dst_y)
391 {
392     int src_offset = map_grid_offset(src_x, src_y);
393     int dst_offset = map_grid_offset(dst_x, dst_y);
394     ++stats.total_routes_calculated;
395     route_queue(src_offset, dst_offset, callback_travel_citizen_land);
396     return routing_distance.items[dst_offset] != 0;
397 }
398 
callback_travel_citizen_road_garden(int next_offset,int dist)399 static void callback_travel_citizen_road_garden(int next_offset, int dist)
400 {
401     if (terrain_land_citizen.items[next_offset] >= CITIZEN_0_ROAD &&
402         terrain_land_citizen.items[next_offset] <= CITIZEN_2_PASSABLE_TERRAIN) {
403         enqueue(next_offset, dist);
404     }
405 }
406 
map_routing_citizen_can_travel_over_road_garden(int src_x,int src_y,int dst_x,int dst_y)407 int map_routing_citizen_can_travel_over_road_garden(int src_x, int src_y, int dst_x, int dst_y)
408 {
409     int src_offset = map_grid_offset(src_x, src_y);
410     int dst_offset = map_grid_offset(dst_x, dst_y);
411     ++stats.total_routes_calculated;
412     route_queue(src_offset, dst_offset, callback_travel_citizen_road_garden);
413     return routing_distance.items[dst_offset] != 0;
414 }
415 
callback_travel_walls(int next_offset,int dist)416 static void callback_travel_walls(int next_offset, int dist)
417 {
418     if (terrain_walls.items[next_offset] >= WALL_0_PASSABLE &&
419         terrain_walls.items[next_offset] <= 2) {
420         enqueue(next_offset, dist);
421     }
422 }
423 
map_routing_can_travel_over_walls(int src_x,int src_y,int dst_x,int dst_y)424 int map_routing_can_travel_over_walls(int src_x, int src_y, int dst_x, int dst_y)
425 {
426     int src_offset = map_grid_offset(src_x, src_y);
427     int dst_offset = map_grid_offset(dst_x, dst_y);
428     ++stats.total_routes_calculated;
429     route_queue(src_offset, dst_offset, callback_travel_walls);
430     return routing_distance.items[dst_offset] != 0;
431 }
432 
callback_travel_noncitizen_land_through_building(int next_offset,int dist)433 static void callback_travel_noncitizen_land_through_building(int next_offset, int dist)
434 {
435     if (!has_fighting_enemy(next_offset)) {
436         if (terrain_land_noncitizen.items[next_offset] == NONCITIZEN_0_PASSABLE ||
437             terrain_land_noncitizen.items[next_offset] == NONCITIZEN_2_CLEARABLE ||
438             (terrain_land_noncitizen.items[next_offset] == NONCITIZEN_1_BUILDING &&
439                 map_building_at(next_offset) == state.through_building_id)) {
440             enqueue(next_offset, dist);
441         }
442     }
443 }
444 
callback_travel_noncitizen_land(int next_offset,int dist)445 static void callback_travel_noncitizen_land(int next_offset, int dist)
446 {
447     if (!has_fighting_enemy(next_offset)) {
448         if (terrain_land_noncitizen.items[next_offset] >= NONCITIZEN_0_PASSABLE &&
449             terrain_land_noncitizen.items[next_offset] < NONCITIZEN_5_FORT) {
450             enqueue(next_offset, dist);
451         }
452     }
453 }
454 
map_routing_noncitizen_can_travel_over_land(int src_x,int src_y,int dst_x,int dst_y,int only_through_building_id,int max_tiles)455 int map_routing_noncitizen_can_travel_over_land(
456     int src_x, int src_y, int dst_x, int dst_y, int only_through_building_id, int max_tiles)
457 {
458     int src_offset = map_grid_offset(src_x, src_y);
459     int dst_offset = map_grid_offset(dst_x, dst_y);
460     ++stats.total_routes_calculated;
461     ++stats.enemy_routes_calculated;
462     if (only_through_building_id) {
463         state.through_building_id = only_through_building_id;
464         route_queue(src_offset, dst_offset, callback_travel_noncitizen_land_through_building);
465     } else {
466         route_queue_max(src_offset, dst_offset, max_tiles, callback_travel_noncitizen_land);
467     }
468     return routing_distance.items[dst_offset] != 0;
469 }
470 
callback_travel_noncitizen_through_everything(int next_offset,int dist)471 static void callback_travel_noncitizen_through_everything(int next_offset, int dist)
472 {
473     if (terrain_land_noncitizen.items[next_offset] >= NONCITIZEN_0_PASSABLE) {
474         enqueue(next_offset, dist);
475     }
476 }
477 
map_routing_noncitizen_can_travel_through_everything(int src_x,int src_y,int dst_x,int dst_y)478 int map_routing_noncitizen_can_travel_through_everything(int src_x, int src_y, int dst_x, int dst_y)
479 {
480     int src_offset = map_grid_offset(src_x, src_y);
481     int dst_offset = map_grid_offset(dst_x, dst_y);
482     ++stats.total_routes_calculated;
483     route_queue(src_offset, dst_offset, callback_travel_noncitizen_through_everything);
484     return routing_distance.items[dst_offset] != 0;
485 }
486 
map_routing_block(int x,int y,int size)487 void map_routing_block(int x, int y, int size)
488 {
489     if (!map_grid_is_inside(x, y, size)) {
490         return;
491     }
492     for (int dy = 0; dy < size; dy++) {
493         for (int dx = 0; dx < size; dx++) {
494             routing_distance.items[map_grid_offset(x+dx, y+dy)] = 0;
495         }
496     }
497 }
498 
map_routing_distance(int grid_offset)499 int map_routing_distance(int grid_offset)
500 {
501     return routing_distance.items[grid_offset];
502 }
503 
map_routing_save_state(buffer * buf)504 void map_routing_save_state(buffer *buf)
505 {
506     buffer_write_i32(buf, 0); // unused counter
507     buffer_write_i32(buf, stats.enemy_routes_calculated);
508     buffer_write_i32(buf, stats.total_routes_calculated);
509     buffer_write_i32(buf, 0); // unused counter
510 }
511 
map_routing_load_state(buffer * buf)512 void map_routing_load_state(buffer *buf)
513 {
514     buffer_skip(buf, 4); // unused counter
515     stats.enemy_routes_calculated = buffer_read_i32(buf);
516     stats.total_routes_calculated = buffer_read_i32(buf);
517     buffer_skip(buf, 4); // unused counter
518 }
519