1 #include "view.h"
2 
3 #include "core/calc.h"
4 #include "core/config.h"
5 #include "core/direction.h"
6 #include "graphics/menu.h"
7 #include "map/grid.h"
8 #include "map/image.h"
9 #include "widget/minimap.h"
10 
11 #define TILE_WIDTH_PIXELS 60
12 #define TILE_HEIGHT_PIXELS 30
13 #define HALF_TILE_WIDTH_PIXELS 30
14 #define HALF_TILE_HEIGHT_PIXELS 15
15 
16 static const int X_DIRECTION_FOR_ORIENTATION[] = {1,  1, -1, -1};
17 static const int Y_DIRECTION_FOR_ORIENTATION[] = {1, -1, -1,  1};
18 
19 static struct {
20     int screen_width;
21     int screen_height;
22     int sidebar_collapsed;
23     int orientation;
24     int scale;
25     int max_scale;
26     struct {
27         view_tile tile;
28         pixel_offset pixel;
29     } camera;
30     struct {
31         int x;
32         int y;
33         int width_pixels;
34         int height_pixels;
35         int width_tiles;
36         int height_tiles;
37     } viewport;
38     struct {
39         int x_pixels;
40         int y_pixels;
41     } selected_tile;
42 } data;
43 
44 static int view_to_grid_offset_lookup[VIEW_X_MAX][VIEW_Y_MAX];
45 
check_camera_boundaries(void)46 static void check_camera_boundaries(void)
47 {
48     int x_min = (VIEW_X_MAX - map_grid_width()) / 2;
49     int y_min = (VIEW_Y_MAX - 2 * map_grid_height()) / 2;
50     if (data.camera.tile.x < x_min - 1) {
51         data.camera.tile.x = x_min - 1;
52         data.camera.pixel.x = 0;
53     }
54     if (data.camera.tile.x >= VIEW_X_MAX - x_min - data.viewport.width_tiles) {
55         data.camera.tile.x = VIEW_X_MAX - x_min - data.viewport.width_tiles;
56         data.camera.pixel.x = 0;
57     }
58     if (data.camera.tile.y < y_min - 2) {
59         data.camera.tile.y = y_min - 1;
60         data.camera.pixel.y = 0;
61     }
62     if (data.camera.tile.y >= ((VIEW_Y_MAX - y_min - data.viewport.height_tiles) & ~1)) {
63         data.camera.tile.y = VIEW_Y_MAX - y_min - data.viewport.height_tiles;
64         data.camera.pixel.y = 0;
65     }
66     data.camera.tile.y &= ~1;
67 }
68 
reset_lookup(void)69 static void reset_lookup(void)
70 {
71     for (int y = 0; y < VIEW_Y_MAX; y++) {
72         for (int x = 0; x < VIEW_X_MAX; x++) {
73             view_to_grid_offset_lookup[x][y] = -1;
74         }
75     }
76 }
77 
calculate_lookup(void)78 static void calculate_lookup(void)
79 {
80     reset_lookup();
81     int y_view_start;
82     int y_view_skip;
83     int y_view_step;
84     int x_view_start;
85     int x_view_skip;
86     int x_view_step;
87     switch (data.orientation) {
88         default:
89         case DIR_0_TOP:
90             x_view_start = VIEW_X_MAX - 1;
91             x_view_skip = -1;
92             x_view_step = 1;
93             y_view_start = 1;
94             y_view_skip = 1;
95             y_view_step = 1;
96             break;
97         case DIR_2_RIGHT:
98             x_view_start = 3;
99             x_view_skip = 1;
100             x_view_step = 1;
101             y_view_start = VIEW_X_MAX - 3;
102             y_view_skip = 1;
103             y_view_step = -1;
104             break;
105         case DIR_4_BOTTOM:
106             x_view_start = VIEW_X_MAX - 1;
107             x_view_skip = 1;
108             x_view_step = -1;
109             y_view_start = VIEW_Y_MAX - 2;
110             y_view_skip = -1;
111             y_view_step = -1;
112             break;
113         case DIR_6_LEFT:
114             x_view_start = VIEW_Y_MAX;
115             x_view_skip = -1;
116             x_view_step = -1;
117             y_view_start = VIEW_X_MAX - 3;
118             y_view_skip = -1;
119             y_view_step = 1;
120             break;
121     }
122 
123     for (int y = 0; y < GRID_SIZE; y++) {
124         int x_view = x_view_start;
125         int y_view = y_view_start;
126         for (int x = 0; x < GRID_SIZE; x++) {
127             int grid_offset = x + GRID_SIZE * y;
128             if (map_image_at(grid_offset) < 6) {
129                 view_to_grid_offset_lookup[x_view/2][y_view] = -1;
130             } else {
131                 view_to_grid_offset_lookup[x_view/2][y_view] = grid_offset;
132             }
133             x_view += x_view_step;
134             y_view += y_view_step;
135         }
136         x_view_start += x_view_skip;
137         y_view_start += y_view_skip;
138     }
139 }
140 
adjust_camera_position_for_pixels(void)141 static void adjust_camera_position_for_pixels(void)
142 {
143     while (data.camera.pixel.x < 0) {
144         data.camera.tile.x--;
145         data.camera.pixel.x += TILE_WIDTH_PIXELS;
146     }
147     while (data.camera.pixel.y < 0) {
148         data.camera.tile.y -= 2;
149         data.camera.pixel.y += TILE_HEIGHT_PIXELS;
150     }
151     while (data.camera.pixel.x >= TILE_WIDTH_PIXELS) {
152         data.camera.tile.x++;
153         data.camera.pixel.x -= TILE_WIDTH_PIXELS;
154     }
155     while (data.camera.pixel.y >= TILE_HEIGHT_PIXELS) {
156         data.camera.tile.y += 2;
157         data.camera.pixel.y -= TILE_HEIGHT_PIXELS;
158     }
159 }
160 
city_view_init(void)161 void city_view_init(void)
162 {
163     calculate_lookup();
164     city_view_set_scale(100);
165     widget_minimap_invalidate();
166 }
167 
city_view_orientation(void)168 int city_view_orientation(void)
169 {
170     return data.orientation;
171 }
172 
city_view_reset_orientation(void)173 void city_view_reset_orientation(void)
174 {
175     data.orientation = 0;
176     calculate_lookup();
177 }
178 
city_view_get_scale(void)179 int city_view_get_scale(void)
180 {
181     return data.scale;
182 }
183 
city_view_get_max_scale(void)184 int city_view_get_max_scale(void)
185 {
186     return data.max_scale;
187 }
188 
city_view_get_camera(int * x,int * y)189 void city_view_get_camera(int *x, int *y)
190 {
191     *x = data.camera.tile.x;
192     *y = data.camera.tile.y;
193 }
194 
city_view_get_pixel_offset(int * x,int * y)195 void city_view_get_pixel_offset(int *x, int *y)
196 {
197     *x = data.camera.pixel.x;
198     *y = data.camera.pixel.y;
199 }
200 
city_view_get_camera_in_pixels(int * x,int * y)201 void city_view_get_camera_in_pixels(int *x, int *y)
202 {
203     *x = data.camera.tile.x * TILE_WIDTH_PIXELS + data.camera.pixel.x;
204     *y = data.camera.tile.y * HALF_TILE_HEIGHT_PIXELS + data.camera.pixel.y;
205 }
206 
city_view_set_camera(int x,int y)207 void city_view_set_camera(int x, int y)
208 {
209     data.camera.tile.x = x;
210     data.camera.tile.y = y;
211     check_camera_boundaries();
212 }
213 
city_view_set_camera_from_pixel_position(int x,int y)214 void city_view_set_camera_from_pixel_position(int x, int y)
215 {
216     x = x < 0 ? 0 : x;
217     y = y < 0 ? 0 : y;
218 
219     data.camera.tile.x = x / TILE_WIDTH_PIXELS;
220     data.camera.tile.y = y / HALF_TILE_HEIGHT_PIXELS;
221     data.camera.pixel.x = x % TILE_WIDTH_PIXELS;
222     data.camera.pixel.y = y % TILE_HEIGHT_PIXELS;
223     check_camera_boundaries();
224 }
225 
city_view_scroll(int x,int y)226 void city_view_scroll(int x, int y)
227 {
228     data.camera.pixel.x += x;
229     data.camera.pixel.y += y;
230     adjust_camera_position_for_pixels();
231     check_camera_boundaries();
232 }
233 
city_view_grid_offset_to_xy_view(int grid_offset,int * x_view,int * y_view)234 void city_view_grid_offset_to_xy_view(int grid_offset, int *x_view, int *y_view)
235 {
236     *x_view = *y_view = 0;
237     for (int y = 0; y < VIEW_Y_MAX; y++) {
238         for (int x = 0; x < VIEW_X_MAX; x++) {
239             if (view_to_grid_offset_lookup[x][y] == grid_offset) {
240                 *x_view = x;
241                 *y_view = y;
242                 return;
243             }
244         }
245     }
246 }
247 
city_view_get_selected_tile_pixels(int * x_pixels,int * y_pixels)248 void city_view_get_selected_tile_pixels(int *x_pixels, int *y_pixels)
249 {
250     *x_pixels = data.selected_tile.x_pixels;
251     *y_pixels = data.selected_tile.y_pixels;
252 }
253 
city_view_pixels_to_view_tile(int x_pixels,int y_pixels,view_tile * tile)254 int city_view_pixels_to_view_tile(int x_pixels, int y_pixels, view_tile *tile)
255 {
256     if (config_get(CONFIG_UI_ZOOM)) {
257         y_pixels -= TOP_MENU_HEIGHT;
258     }
259 
260     x_pixels = calc_adjust_with_percentage(x_pixels, data.scale);
261     y_pixels = calc_adjust_with_percentage(y_pixels, data.scale);
262 
263     if (x_pixels < data.viewport.x ||
264             x_pixels >= data.viewport.x + data.viewport.width_pixels ||
265             y_pixels < data.viewport.y ||
266             y_pixels >= data.viewport.y + data.viewport.height_pixels) {
267         return 0;
268     }
269 
270     x_pixels += data.camera.pixel.x;
271     y_pixels += data.camera.pixel.y;
272     int odd = ((x_pixels - data.viewport.x) / HALF_TILE_WIDTH_PIXELS +
273         (y_pixels - data.viewport.y) / HALF_TILE_HEIGHT_PIXELS) & 1;
274     int x_is_odd = ((x_pixels - data.viewport.x) / HALF_TILE_WIDTH_PIXELS) & 1;
275     int y_is_odd = ((y_pixels - data.viewport.y) / HALF_TILE_HEIGHT_PIXELS) & 1;
276     int x_mod = ((x_pixels - data.viewport.x) % HALF_TILE_WIDTH_PIXELS) / 2;
277     int y_mod = (y_pixels - data.viewport.y) % HALF_TILE_HEIGHT_PIXELS;
278     int x_view_offset = (x_pixels - data.viewport.x) / TILE_WIDTH_PIXELS;
279     int y_view_offset = (y_pixels - data.viewport.y) / HALF_TILE_HEIGHT_PIXELS;
280     if (odd) {
281         if (x_mod + y_mod >= HALF_TILE_HEIGHT_PIXELS - 1) {
282             y_view_offset++;
283             if (x_is_odd && !y_is_odd) {
284                 x_view_offset++;
285             }
286         }
287     } else {
288         if (y_mod > x_mod) {
289             y_view_offset++;
290         } else if (x_is_odd && y_is_odd) {
291             x_view_offset++;
292         }
293     }
294     tile->x = data.camera.tile.x + x_view_offset;
295     tile->y = data.camera.tile.y + y_view_offset;
296     return 1;
297 }
298 
city_view_set_selected_view_tile(const view_tile * tile)299 void city_view_set_selected_view_tile(const view_tile *tile)
300 {
301     int x_view_offset = tile->x - data.camera.tile.x;
302     int y_view_offset = tile->y - data.camera.tile.y;
303     data.selected_tile.x_pixels = data.viewport.x + TILE_WIDTH_PIXELS * x_view_offset - data.camera.pixel.x;
304     if (y_view_offset & 1) {
305         data.selected_tile.x_pixels -= HALF_TILE_WIDTH_PIXELS;
306     }
307     data.selected_tile.y_pixels = data.viewport.y + HALF_TILE_HEIGHT_PIXELS * y_view_offset
308         - HALF_TILE_HEIGHT_PIXELS - data.camera.pixel.y;
309 }
310 
city_view_tile_to_grid_offset(const view_tile * tile)311 int city_view_tile_to_grid_offset(const view_tile *tile)
312 {
313     int grid_offset = view_to_grid_offset_lookup[tile->x][tile->y];
314     return grid_offset < 0 ? 0 : grid_offset;
315 }
316 
city_view_go_to_grid_offset(int grid_offset)317 void city_view_go_to_grid_offset(int grid_offset)
318 {
319     int x, y;
320     city_view_grid_offset_to_xy_view(grid_offset, &x, &y);
321     data.camera.tile.x = x - data.viewport.width_tiles / 2;
322     data.camera.tile.y = y - data.viewport.height_tiles / 2;
323     data.camera.tile.y &= ~1;
324     check_camera_boundaries();
325 }
326 
get_center_grid_offset(void)327 static int get_center_grid_offset(void)
328 {
329     int x_center = data.camera.tile.x + data.viewport.width_tiles / 2;
330     int y_center = data.camera.tile.y + data.viewport.height_tiles / 2;
331     return view_to_grid_offset_lookup[x_center][y_center];
332 }
333 
city_view_rotate_left(void)334 void city_view_rotate_left(void)
335 {
336     int center_grid_offset = get_center_grid_offset();
337 
338     data.orientation += 2;
339     if (data.orientation > 6) {
340         data.orientation = DIR_0_TOP;
341     }
342     calculate_lookup();
343     if (center_grid_offset >= 0) {
344         int x, y;
345         city_view_grid_offset_to_xy_view(center_grid_offset, &x, &y);
346         data.camera.tile.x = x - data.viewport.width_tiles / 2;
347         data.camera.tile.y = y - data.viewport.height_tiles / 2;
348     }
349     check_camera_boundaries();
350 }
351 
city_view_rotate_right(void)352 void city_view_rotate_right(void)
353 {
354     int center_grid_offset = get_center_grid_offset();
355 
356     data.orientation -= 2;
357     if (data.orientation < 0) {
358         data.orientation = DIR_6_LEFT;
359     }
360     calculate_lookup();
361     if (center_grid_offset >= 0) {
362         int x, y;
363         city_view_grid_offset_to_xy_view(center_grid_offset, &x, &y);
364         data.camera.tile.x = x - data.viewport.width_tiles / 2;
365         data.camera.tile.y = y - data.viewport.height_tiles / 2;
366     }
367     check_camera_boundaries();
368 }
369 
set_viewport(int x_offset,int y_offset,int width,int height)370 static void set_viewport(int x_offset, int y_offset, int width, int height)
371 {
372     width = calc_adjust_with_percentage(width, data.scale);
373     height = calc_adjust_with_percentage(height, data.scale);
374     data.viewport.x = x_offset;
375     data.viewport.y = y_offset;
376     data.viewport.width_pixels = width - calc_adjust_with_percentage(2, data.scale);
377     data.viewport.height_pixels = height;
378     data.viewport.width_tiles = width / TILE_WIDTH_PIXELS;
379     data.viewport.height_tiles = height / HALF_TILE_HEIGHT_PIXELS;
380 }
381 
set_viewport_with_sidebar(void)382 static void set_viewport_with_sidebar(void)
383 {
384     set_viewport(0, config_get(CONFIG_UI_ZOOM) ? 0 : TOP_MENU_HEIGHT, data.screen_width - 160, data.screen_height - TOP_MENU_HEIGHT);
385 }
386 
set_viewport_without_sidebar(void)387 static void set_viewport_without_sidebar(void)
388 {
389     set_viewport(0, config_get(CONFIG_UI_ZOOM) ? 0 : TOP_MENU_HEIGHT, data.screen_width - 40, data.screen_height - TOP_MENU_HEIGHT);
390 }
391 
city_view_set_scale(int scale)392 void city_view_set_scale(int scale)
393 {
394     if (config_get(CONFIG_UI_ZOOM)) {
395         scale = calc_bound(scale, 50, data.max_scale);
396     } else {
397         scale = 100;
398     }
399     data.scale = scale;
400     if (data.sidebar_collapsed) {
401         set_viewport_without_sidebar();
402     } else {
403         set_viewport_with_sidebar();
404     }
405     check_camera_boundaries();
406 }
407 
city_view_set_max_scale(int scale)408 void city_view_set_max_scale(int scale)
409 {
410     data.max_scale = scale;
411     if (data.scale > scale) {
412         city_view_set_scale(scale);
413     }
414 }
415 
city_view_set_viewport(int screen_width,int screen_height)416 void city_view_set_viewport(int screen_width, int screen_height)
417 {
418     data.screen_width = screen_width;
419     data.screen_height = screen_height;
420     if (data.sidebar_collapsed) {
421         set_viewport_without_sidebar();
422     } else {
423         set_viewport_with_sidebar();
424     }
425     check_camera_boundaries();
426 }
427 
city_view_get_scaled_viewport(int * x,int * y,int * width,int * height)428 void city_view_get_scaled_viewport(int *x, int *y, int *width, int *height)
429 {
430     *x = data.viewport.x;
431     *y = data.viewport.y;
432     *width = data.viewport.width_pixels;
433     *height = data.viewport.height_pixels;
434 }
435 
city_view_get_unscaled_viewport(int * x,int * y,int * width,int * height)436 void city_view_get_unscaled_viewport(int *x, int *y, int *width, int *height)
437 {
438     *x = data.viewport.x;
439     *y = data.viewport.y;
440     if (!data.scale) {
441         data.scale = 100;
442     }
443     *width = (int) ((data.viewport.width_pixels / (float) data.scale) * 100);
444     *height = (int) ((data.viewport.height_pixels / (float) data.scale) * 100);
445 }
446 
city_view_get_viewport_size_tiles(int * width,int * height)447 void city_view_get_viewport_size_tiles(int *width, int *height)
448 {
449     *width = data.viewport.width_tiles;
450     *height = data.viewport.height_tiles;
451 }
452 
city_view_is_sidebar_collapsed(void)453 int city_view_is_sidebar_collapsed(void)
454 {
455     return data.sidebar_collapsed;
456 }
457 
city_view_start_sidebar_toggle(void)458 void city_view_start_sidebar_toggle(void)
459 {
460     set_viewport_without_sidebar();
461     check_camera_boundaries();
462 }
463 
city_view_toggle_sidebar(void)464 void city_view_toggle_sidebar(void)
465 {
466     if (data.sidebar_collapsed) {
467         data.sidebar_collapsed = 0;
468         set_viewport_with_sidebar();
469     } else {
470         data.sidebar_collapsed = 1;
471         set_viewport_without_sidebar();
472     }
473     check_camera_boundaries();
474 }
475 
city_view_save_state(buffer * orientation,buffer * camera)476 void city_view_save_state(buffer *orientation, buffer *camera)
477 {
478     buffer_write_i32(orientation, data.orientation);
479 
480     buffer_write_i32(camera, data.camera.tile.x);
481     buffer_write_i32(camera, data.camera.tile.y);
482 }
483 
city_view_load_state(buffer * orientation,buffer * camera)484 void city_view_load_state(buffer *orientation, buffer *camera)
485 {
486     data.orientation = buffer_read_i32(orientation);
487     city_view_load_scenario_state(camera);
488 
489     if (data.orientation >= 0 && data.orientation <= 6) {
490         // ensure even number
491         data.orientation = 2 * (data.orientation / 2);
492     } else {
493         data.orientation = 0;
494     }
495 }
496 
city_view_save_scenario_state(buffer * camera)497 void city_view_save_scenario_state(buffer *camera)
498 {
499     buffer_write_i32(camera, data.camera.tile.x);
500     buffer_write_i32(camera, data.camera.tile.y);
501 }
502 
city_view_load_scenario_state(buffer * camera)503 void city_view_load_scenario_state(buffer *camera)
504 {
505     data.camera.tile.x = buffer_read_i32(camera);
506     data.camera.tile.y = buffer_read_i32(camera);
507 }
508 
city_view_foreach_map_tile(map_callback * callback)509 void city_view_foreach_map_tile(map_callback *callback)
510 {
511     int odd = 0;
512     int y_view = data.camera.tile.y - 8;
513     int y_graphic = data.viewport.y - 9 * HALF_TILE_HEIGHT_PIXELS - data.camera.pixel.y;
514     for (int y = 0; y < data.viewport.height_tiles + 21; y++) {
515         if (y_view >= 0 && y_view < VIEW_Y_MAX) {
516             int x_graphic = -(6 * TILE_WIDTH_PIXELS) - data.camera.pixel.x;
517             if (odd) {
518                 x_graphic += data.viewport.x - HALF_TILE_WIDTH_PIXELS;
519             } else {
520                 x_graphic += data.viewport.x;
521             }
522             int x_view = data.camera.tile.x - 6;
523             for (int x = 0; x < data.viewport.width_tiles + 9; x++) {
524                 if (x_view >= 0 && x_view < VIEW_X_MAX) {
525                     int grid_offset = view_to_grid_offset_lookup[x_view][y_view];
526                     callback(x_graphic, y_graphic, grid_offset);
527                 }
528                 x_graphic += TILE_WIDTH_PIXELS;
529                 x_view++;
530             }
531         }
532         odd = 1 - odd;
533         y_graphic += HALF_TILE_HEIGHT_PIXELS;
534         y_view++;
535     }
536 }
537 
city_view_foreach_valid_map_tile(map_callback * callback1,map_callback * callback2,map_callback * callback3)538 void city_view_foreach_valid_map_tile(map_callback *callback1, map_callback *callback2, map_callback *callback3)
539 {
540     int odd = 0;
541     int y_view = data.camera.tile.y - 8;
542     int y_graphic = data.viewport.y - 9 * HALF_TILE_HEIGHT_PIXELS - data.camera.pixel.y;
543     int x_graphic, x_view;
544     for (int y = 0; y < data.viewport.height_tiles + 21; y++) {
545         if (y_view >= 0 && y_view < VIEW_Y_MAX) {
546             if (callback1) {
547                 x_graphic = -(6 * TILE_WIDTH_PIXELS) - data.camera.pixel.x;
548                 if (odd) {
549                     x_graphic += data.viewport.x - HALF_TILE_WIDTH_PIXELS;
550                 } else {
551                     x_graphic += data.viewport.x;
552                 }
553                 x_view = data.camera.tile.x - 6;
554                 for (int x = 0; x < data.viewport.width_tiles + 9; x++) {
555                     if (x_view >= 0 && x_view < VIEW_X_MAX) {
556                         int grid_offset = view_to_grid_offset_lookup[x_view][y_view];
557                         if (grid_offset >= 0) {
558                             callback1(x_graphic, y_graphic, grid_offset);
559                         }
560                     }
561                     x_graphic += TILE_WIDTH_PIXELS;
562                     x_view++;
563                 }
564             }
565             if (callback2) {
566                 x_graphic = -(6 * TILE_WIDTH_PIXELS) - data.camera.pixel.x;
567                 if (odd) {
568                     x_graphic += data.viewport.x - HALF_TILE_WIDTH_PIXELS;
569                 } else {
570                     x_graphic += data.viewport.x;
571                 }
572                 x_view = data.camera.tile.x - 6;
573                 for (int x = 0; x < data.viewport.width_tiles + 9; x++) {
574                     if (x_view >= 0 && x_view < VIEW_X_MAX) {
575                         int grid_offset = view_to_grid_offset_lookup[x_view][y_view];
576                         if (grid_offset >= 0) {
577                             callback2(x_graphic, y_graphic, grid_offset);
578                         }
579                     }
580                     x_graphic += TILE_WIDTH_PIXELS;
581                     x_view++;
582                 }
583             }
584             if (callback3) {
585                 x_graphic = -(6 * TILE_WIDTH_PIXELS) - data.camera.pixel.x;
586                 if (odd) {
587                     x_graphic += data.viewport.x - HALF_TILE_WIDTH_PIXELS;
588                 } else {
589                     x_graphic += data.viewport.x;
590                 }
591                 x_view = data.camera.tile.x - 6;
592                 for (int x = 0; x < data.viewport.width_tiles + 9; x++) {
593                     if (x_view >= 0 && x_view < VIEW_X_MAX) {
594                         int grid_offset = view_to_grid_offset_lookup[x_view][y_view];
595                         if (grid_offset >= 0) {
596                             callback3(x_graphic, y_graphic, grid_offset);
597                         }
598                     }
599                     x_graphic += TILE_WIDTH_PIXELS;
600                     x_view++;
601                 }
602             }
603         }
604         odd = 1 - odd;
605         y_graphic += HALF_TILE_HEIGHT_PIXELS;
606         y_view++;
607     }
608 }
609 
do_valid_callback(int view_x,int view_y,int grid_offset,map_callback * callback)610 static void do_valid_callback(int view_x, int view_y, int grid_offset, map_callback *callback)
611 {
612     if (grid_offset >= 0 && map_image_at(grid_offset) >= 6) {
613         callback(view_x, view_y, grid_offset);
614     }
615 }
616 
city_view_foreach_tile_in_range(int grid_offset,int size,int radius,map_callback * callback)617 void city_view_foreach_tile_in_range(int grid_offset, int size, int radius, map_callback *callback)
618 {
619     int x, y;
620     city_view_grid_offset_to_xy_view(grid_offset, &x, &y);
621     x = (x - data.camera.tile.x) * TILE_WIDTH_PIXELS
622         - (y & 1) * HALF_TILE_WIDTH_PIXELS - data.camera.pixel.x + data.viewport.x;
623     y = (y - data.camera.tile.y - 1) * HALF_TILE_HEIGHT_PIXELS - data.camera.pixel.y + data.viewport.y;
624     int orientation_x = X_DIRECTION_FOR_ORIENTATION[data.orientation / 2];
625     int orientation_y = Y_DIRECTION_FOR_ORIENTATION[data.orientation / 2];
626 
627     // If we are rotated east or west, the pixel location needs to be rotated
628     // to match its corresponding grid_offset. Since for east and west
629     // only one of the orientations is negative, we can get a negative value
630     // which can then be used to properly offset the pixel positions
631     int pixel_rotation = orientation_x * orientation_y;
632 
633     int rotation_delta = pixel_rotation == -1 ? (2 - size) : 1;
634     grid_offset += map_grid_delta(rotation_delta * orientation_x, rotation_delta * orientation_y);
635     int x_delta = HALF_TILE_WIDTH_PIXELS;
636     int y_delta = HALF_TILE_HEIGHT_PIXELS;
637     int x_offset = HALF_TILE_WIDTH_PIXELS;
638     int y_offset = TILE_HEIGHT_PIXELS;
639     if (size) {
640         --size;
641         y += HALF_TILE_HEIGHT_PIXELS * size;
642         x_offset += HALF_TILE_WIDTH_PIXELS * size;
643         y_offset += HALF_TILE_HEIGHT_PIXELS * size;
644     } else {
645         do_valid_callback(x, y, grid_offset, callback);
646     }
647     // Basic algorithm: we cycle the radius as successive rings
648     // Starting at the innermost ring (determined by size), we first cycle
649     // the top, left, right and bottom corners of the ring.
650     // Then we stretch from each corner of the ring to reach the next one, closing the ring
651     for (int ring = 0; ring < radius; ++ring) {
652         int offset_north = -ring - 2;
653         int offset_south = ring + size;
654         do_valid_callback(
655             x, y + y_offset * pixel_rotation,
656             map_grid_add_delta(grid_offset, offset_south * orientation_x, offset_south * orientation_y),
657             callback);
658         do_valid_callback(
659             x, y - y_offset * pixel_rotation,
660             map_grid_add_delta(grid_offset, offset_north * orientation_x, offset_north * orientation_y),
661             callback);
662         do_valid_callback(
663             x - x_offset - x_delta, y,
664             map_grid_add_delta(grid_offset, offset_north * orientation_x, offset_south * orientation_y),
665             callback);
666         do_valid_callback(
667             x + x_offset + x_delta, y,
668             map_grid_add_delta(grid_offset, offset_south * orientation_x, offset_north * orientation_y),
669             callback);
670         for (int tile = 1; tile < ring * 2 + size + 2; ++tile) {
671             do_valid_callback(
672                 x + x_delta * tile, y - y_offset * pixel_rotation + y_delta * pixel_rotation * tile,
673                 map_grid_add_delta(grid_offset, (tile + offset_north) * orientation_x, offset_north * orientation_y),
674                 callback);
675             do_valid_callback(
676                 x - x_delta * tile, y - y_offset * pixel_rotation + y_delta * pixel_rotation * tile,
677                 map_grid_add_delta(grid_offset, offset_north * orientation_x, (tile + offset_north) * orientation_y),
678                 callback);
679             do_valid_callback(
680                 x + x_delta * tile, y + y_offset * pixel_rotation - y_delta * pixel_rotation * tile,
681                 map_grid_add_delta(grid_offset, offset_south * orientation_x, (offset_south - tile) * orientation_y),
682                 callback);
683             do_valid_callback(
684                 x - x_delta * tile, y + y_offset * pixel_rotation - y_delta * pixel_rotation * tile,
685                 map_grid_add_delta(grid_offset, (offset_south - tile) * orientation_x, offset_south * orientation_y),
686                 callback);
687         }
688         x_offset += TILE_WIDTH_PIXELS;
689         y_offset += TILE_HEIGHT_PIXELS;
690     }
691 }
692 
city_view_foreach_minimap_tile(int x_offset,int y_offset,int absolute_x,int absolute_y,int width_tiles,int height_tiles,map_callback * callback)693 void city_view_foreach_minimap_tile(
694     int x_offset, int y_offset,
695     int absolute_x, int absolute_y,
696     int width_tiles, int height_tiles,
697     map_callback *callback)
698 {
699     int odd = 0;
700     int y_abs = absolute_y - 4;
701     int y_view = y_offset - 4;
702     for (int y_rel = -4; y_rel < height_tiles + 4; y_rel++, y_abs++, y_view++) {
703         int x_view;
704         if (odd) {
705             x_view = x_offset - 9;
706             odd = 0;
707         } else {
708             x_view = x_offset - 8;
709             odd = 1;
710         }
711         int x_abs = absolute_x - 4;
712         for (int x_rel = -4; x_rel < width_tiles; x_rel++, x_abs++, x_view += 2) {
713             if (x_abs >= 0 && x_abs < VIEW_X_MAX && y_abs >= 0 && y_abs < VIEW_Y_MAX) {
714                 callback(x_view, y_view, view_to_grid_offset_lookup[x_abs][y_abs]);
715             }
716         }
717     }
718 }
719