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