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