Stop sharing requirement_unit_state_ereq().
[freeciv.git] / client / overview_common.c
blobff37f9a5d0953ede1f2286c2aca4c03401f21b82
1 /***********************************************************************
2 Freeciv - Copyright (C) 1996-2005 - Freeciv Development Team
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <math.h> /* floor */
20 /* utility */
21 #include "log.h"
23 /* client */
24 #include "client_main.h" /* can_client_change_view() */
25 #include "climap.h"
26 #include "control.h"
27 #include "mapview_g.h"
28 #include "options.h"
29 #include "zoom.h"
31 #include "overview_common.h"
33 int OVERVIEW_TILE_SIZE = 2;
34 #if 0
35 struct overview overview = {
36 /* These are the default values. All others are zeroed automatically. */
37 .fog = TRUE,
38 .layers = {[OLAYER_BACKGROUND] = TRUE,
39 [OLAYER_UNITS] = TRUE,
40 [OLAYER_CITIES] = TRUE,
41 [OLAYER_BORDERS_ON_OCEAN] = TRUE}
43 #endif
46 * Set to TRUE if the backing store is more recent than the version
47 * drawn into overview.window.
49 static bool overview_dirty = FALSE;
51 /****************************************************************************
52 Translate from gui to natural coordinate systems. This provides natural
53 coordinates as a floating-point value so there is no loss of information
54 in the resulting values.
55 ****************************************************************************/
56 static void gui_to_natural_pos(const struct tileset *t,
57 double *ntl_x, double *ntl_y,
58 int gui_x, int gui_y)
60 const double gui_xd = gui_x, gui_yd = gui_y;
61 const double W = tileset_tile_width(t) * map_zoom;
62 const double H = tileset_tile_height(t) * map_zoom;
63 double map_x, map_y;
65 /* First convert to map positions. This ignores hex conversions; we're
66 * not looking for an exact tile. */
67 if (tileset_is_isometric(t)) {
68 /* Includes hex cases. */
69 map_x = (gui_xd * H + gui_yd * W) / (W * H);
70 map_y = (gui_yd * W - gui_xd * H) / (W * H);
71 } else {
72 map_x = gui_xd / W;
73 map_y = gui_yd / H;
76 /* Now convert to natural positions. Note this assumes the macro form
77 * of the conversion will work with floating-point values. */
78 MAP_TO_NATURAL_POS(ntl_x, ntl_y, map_x, map_y);
82 /****************************************************************************
83 Translate from gui to overview coordinate systems.
84 ****************************************************************************/
85 static void gui_to_overview_pos(const struct tileset *t,
86 int *ovr_x, int *ovr_y,
87 int gui_x, int gui_y)
89 double ntl_x, ntl_y;
91 gui_to_natural_pos(t, &ntl_x, &ntl_y, gui_x, gui_y);
93 /* Now convert straight to overview coordinates. */
94 *ovr_x = floor((ntl_x - (double)gui_options.overview.map_x0) * OVERVIEW_TILE_SIZE);
95 *ovr_y = floor((ntl_y - (double)gui_options.overview.map_y0) * OVERVIEW_TILE_SIZE);
97 /* Now do additional adjustments. See map_to_overview_pos(). */
98 if (current_topo_has_flag(TF_WRAPX)) {
99 *ovr_x = FC_WRAP(*ovr_x, NATURAL_WIDTH * OVERVIEW_TILE_SIZE);
100 } else {
101 if (MAP_IS_ISOMETRIC) {
102 /* HACK: For iso-maps that don't wrap in the X direction we clip
103 * a half-tile off of the left and right of the overview. This
104 * means some tiles only are halfway shown. However it means we
105 * don't show any unreal tiles, which we'd otherwise be doing. The
106 * rest of the code can't handle unreal tiles in the overview. */
107 *ovr_x -= OVERVIEW_TILE_SIZE;
110 if (current_topo_has_flag(TF_WRAPY)) {
111 *ovr_y = FC_WRAP(*ovr_y, NATURAL_HEIGHT * OVERVIEW_TILE_SIZE);
115 /****************************************************************************
116 Return color for overview map tile.
117 ****************************************************************************/
118 static struct color *overview_tile_color(struct tile *ptile)
120 if (gui_options.overview.layers[OLAYER_CITIES]) {
121 struct city *pcity = tile_city(ptile);
123 if (pcity) {
124 if (NULL == client.conn.playing
125 || city_owner(pcity) == client.conn.playing) {
126 return get_color(tileset, COLOR_OVERVIEW_MY_CITY);
127 } else if (pplayers_allied(city_owner(pcity), client.conn.playing)) {
128 /* Includes teams. */
129 return get_color(tileset, COLOR_OVERVIEW_ALLIED_CITY);
130 } else {
131 return get_color(tileset, COLOR_OVERVIEW_ENEMY_CITY);
135 if (gui_options.overview.layers[OLAYER_UNITS]) {
136 struct unit *punit = find_visible_unit(ptile);
138 if (punit) {
139 if (NULL == client.conn.playing
140 || unit_owner(punit) == client.conn.playing) {
141 return get_color(tileset, COLOR_OVERVIEW_MY_UNIT);
142 } else if (pplayers_allied(unit_owner(punit), client.conn.playing)) {
143 /* Includes teams. */
144 return get_color(tileset, COLOR_OVERVIEW_ALLIED_UNIT);
145 } else {
146 return get_color(tileset, COLOR_OVERVIEW_ENEMY_UNIT);
150 if (gui_options.overview.layers[OLAYER_BORDERS]) {
151 struct player *owner = tile_owner(ptile);
153 if (owner) {
154 if (gui_options.overview.layers[OLAYER_BORDERS_ON_OCEAN]) {
155 return get_player_color(tileset, owner);
156 } else if (!is_ocean_tile(ptile)) {
157 return get_player_color(tileset, owner);
161 if (gui_options.overview.layers[OLAYER_RELIEF]
162 && tile_terrain(ptile) != T_UNKNOWN) {
163 return get_terrain_color(tileset, tile_terrain(ptile));
165 if (gui_options.overview.layers[OLAYER_BACKGROUND]
166 && tile_terrain(ptile) != T_UNKNOWN) {
167 if (terrain_has_flag(tile_terrain(ptile), TER_FROZEN)) {
168 return get_color(tileset, COLOR_OVERVIEW_FROZEN);
169 } else {
170 if (is_ocean_tile(ptile)) {
171 return get_color(tileset, COLOR_OVERVIEW_OCEAN);
172 } else {
173 return get_color(tileset, COLOR_OVERVIEW_LAND);
178 return get_color(tileset, COLOR_OVERVIEW_UNKNOWN);
181 /**************************************************************************
182 Copies the current centred image + viewrect unchanged to the client's
183 overview window (for expose events etc).
184 **************************************************************************/
185 void refresh_overview_from_canvas(void)
187 struct canvas *dest = get_overview_window();
188 if (!dest) {
189 return;
191 canvas_copy(dest, gui_options.overview.window,
192 0, 0, 0, 0,
193 gui_options.overview.width, gui_options.overview.height);
196 /**************************************************************************
197 Copies the overview image from the backing store to the window and
198 draws the viewrect on top of it.
199 **************************************************************************/
200 static void redraw_overview(void)
202 int i, x[4], y[4];
204 if (!gui_options.overview.map) {
205 return;
209 struct canvas *src = gui_options.overview.map;
210 struct canvas *dst = gui_options.overview.window;
211 int x_left = gui_options.overview.map_x0 * OVERVIEW_TILE_SIZE;
212 int y_top = gui_options.overview.map_y0 * OVERVIEW_TILE_SIZE;
213 int ix = gui_options.overview.width - x_left;
214 int iy = gui_options.overview.height - y_top;
216 canvas_copy(dst, src, 0, 0, ix, iy, x_left, y_top);
217 canvas_copy(dst, src, 0, y_top, ix, 0, x_left, iy);
218 canvas_copy(dst, src, x_left, 0, 0, iy, ix, y_top);
219 canvas_copy(dst, src, x_left, y_top, 0, 0, ix, iy);
222 gui_to_overview_pos(tileset, &x[0], &y[0],
223 mapview.gui_x0, mapview.gui_y0);
224 gui_to_overview_pos(tileset, &x[1], &y[1],
225 mapview.gui_x0 + mapview.width, mapview.gui_y0);
226 gui_to_overview_pos(tileset, &x[2], &y[2],
227 mapview.gui_x0 + mapview.width,
228 mapview.gui_y0 + mapview.height);
229 gui_to_overview_pos(tileset, &x[3], &y[3],
230 mapview.gui_x0, mapview.gui_y0 + mapview.height);
232 for (i = 0; i < 4; i++) {
233 int src_x = x[i];
234 int src_y = y[i];
235 int dst_x = x[(i + 1) % 4];
236 int dst_y = y[(i + 1) % 4];
238 canvas_put_line(gui_options.overview.window,
239 get_color(tileset, COLOR_OVERVIEW_VIEWRECT),
240 LINE_NORMAL,
241 src_x, src_y, dst_x - src_x, dst_y - src_y);
244 refresh_overview_from_canvas();
246 overview_dirty = FALSE;
249 /****************************************************************************
250 Mark the overview as "dirty" so that it will be redrawn soon.
251 ****************************************************************************/
252 static void dirty_overview(void)
254 overview_dirty = TRUE;
257 /****************************************************************************
258 Redraw the overview if it is "dirty".
259 ****************************************************************************/
260 void flush_dirty_overview(void)
262 /* Currently this function is called from mapview_common. However it
263 * should be made static eventually. */
264 if (overview_dirty) {
265 redraw_overview();
269 /****************************************************************************
270 Equivalent to FC_WRAP, but it works for doubles.
271 ****************************************************************************/
272 static double wrap_double(double value, double wrap)
274 while (value < 0) {
275 value += wrap;
277 while (value >= wrap) {
278 value -= wrap;
280 return value;
283 /**************************************************************************
284 Center the overview around the mapview.
285 **************************************************************************/
286 void center_tile_overviewcanvas(void)
288 double ntl_x, ntl_y;
289 int ox, oy;
291 gui_to_natural_pos(tileset, &ntl_x, &ntl_y,
292 mapview.gui_x0 + mapview.width / 2,
293 mapview.gui_y0 + mapview.height / 2);
295 /* NOTE: this embeds the map wrapping in the overview code. This is
296 * basically necessary for the overview to be efficiently
297 * updated. */
298 if (current_topo_has_flag(TF_WRAPX)) {
299 gui_options.overview.map_x0 = wrap_double(ntl_x - (double)NATURAL_WIDTH / 2.0,
300 NATURAL_WIDTH);
301 } else {
302 gui_options.overview.map_x0 = 0;
304 if (current_topo_has_flag(TF_WRAPY)) {
305 gui_options.overview.map_y0 = wrap_double(ntl_y - (double)NATURAL_HEIGHT / 2.0,
306 NATURAL_HEIGHT);
307 } else {
308 gui_options.overview.map_y0 = 0;
311 redraw_overview();
313 gui_to_overview_pos(tileset, &ox, &oy,
314 mapview.gui_x0, mapview.gui_y0);
315 update_overview_scroll_window_pos(ox, oy);
318 /**************************************************************************
319 Finds the overview (canvas) coordinates for a given map position.
320 **************************************************************************/
321 void map_to_overview_pos(int *overview_x, int *overview_y,
322 int map_x, int map_y)
324 /* The map position may not be normal, for instance when the mapview
325 * origin is not a normal position.
327 * NOTE: this embeds the map wrapping in the overview code. */
328 do_in_natural_pos(ntl_x, ntl_y, map_x, map_y) {
329 int ovr_x = ntl_x - gui_options.overview.map_x0;
330 int ovr_y = ntl_y - gui_options.overview.map_y0;
332 if (current_topo_has_flag(TF_WRAPX)) {
333 ovr_x = FC_WRAP(ovr_x, NATURAL_WIDTH);
334 } else {
335 if (MAP_IS_ISOMETRIC) {
336 /* HACK: For iso-maps that don't wrap in the X direction we clip
337 * a half-tile off of the left and right of the overview. This
338 * means some tiles only are halfway shown. However it means we
339 * don't show any unreal tiles, which we'd otherwise be doing. The
340 * rest of the code can't handle unreal tiles in the overview. */
341 ovr_x--;
344 if (current_topo_has_flag(TF_WRAPY)) {
345 ovr_y = FC_WRAP(ovr_y, NATURAL_HEIGHT);
347 *overview_x = OVERVIEW_TILE_SIZE * ovr_x;
348 *overview_y = OVERVIEW_TILE_SIZE * ovr_y;
349 } do_in_natural_pos_end;
352 /**************************************************************************
353 Finds the map coordinates for a given overview (canvas) position.
354 **************************************************************************/
355 void overview_to_map_pos(int *map_x, int *map_y,
356 int overview_x, int overview_y)
358 int ntl_x = overview_x / OVERVIEW_TILE_SIZE + gui_options.overview.map_x0;
359 int ntl_y = overview_y / OVERVIEW_TILE_SIZE + gui_options.overview.map_y0;
361 if (MAP_IS_ISOMETRIC && !current_topo_has_flag(TF_WRAPX)) {
362 /* Clip half tile left and right. See comment in map_to_overview_pos. */
363 ntl_x++;
366 NATURAL_TO_MAP_POS(map_x, map_y, ntl_x, ntl_y);
367 /* All positions on the overview should be valid. */
368 fc_assert(normalize_map_pos(map_x, map_y));
371 /**************************************************************************
372 Redraw the entire backing store for the overview minimap.
373 **************************************************************************/
374 void refresh_overview_canvas(void)
376 if (!can_client_change_view()) {
377 return;
379 whole_map_iterate(&(wld.map), ptile) {
380 overview_update_tile(ptile);
381 } whole_map_iterate_end;
382 redraw_overview();
385 /****************************************************************************
386 Draws the color for this tile onto the given rectangle of the canvas.
388 This is just a simple helper function for overview_update_tile, since
389 sometimes a tile may cover more than one rectangle.
390 ****************************************************************************/
391 static void put_overview_tile_area(struct canvas *pcanvas,
392 struct tile *ptile,
393 int x, int y, int w, int h)
395 canvas_put_rectangle(pcanvas,
396 overview_tile_color(ptile),
397 x, y, w, h);
398 if (gui_options.overview.fog
399 && TILE_KNOWN_UNSEEN == client_tile_get_known(ptile)) {
400 canvas_put_sprite(pcanvas, x, y, get_basic_fog_sprite(tileset),
401 0, 0, w, h);
405 /**************************************************************************
406 Redraw the given map position in the overview canvas.
407 **************************************************************************/
408 void overview_update_tile(struct tile *ptile)
410 int tile_x, tile_y;
412 /* Base overview positions are just like natural positions, but scaled to
413 * the overview tile dimensions. */
414 index_to_map_pos(&tile_x, &tile_y, tile_index(ptile));
415 do_in_natural_pos(ntl_x, ntl_y, tile_x, tile_y) {
416 int overview_y = ntl_y * OVERVIEW_TILE_SIZE;
417 int overview_x = ntl_x * OVERVIEW_TILE_SIZE;
419 if (MAP_IS_ISOMETRIC) {
420 if (current_topo_has_flag(TF_WRAPX)) {
421 if (overview_x > gui_options.overview.width - OVERVIEW_TILE_WIDTH) {
422 /* This tile is shown half on the left and half on the right
423 * side of the overview. So we have to draw it in two parts. */
424 put_overview_tile_area(gui_options.overview.map, ptile,
425 overview_x - gui_options.overview.width,
426 overview_y,
427 OVERVIEW_TILE_WIDTH, OVERVIEW_TILE_HEIGHT);
429 } else {
430 /* Clip half tile left and right.
431 * See comment in map_to_overview_pos. */
432 overview_x -= OVERVIEW_TILE_SIZE;
436 put_overview_tile_area(gui_options.overview.map, ptile,
437 overview_x, overview_y,
438 OVERVIEW_TILE_WIDTH, OVERVIEW_TILE_HEIGHT);
440 dirty_overview();
441 } do_in_natural_pos_end;
444 /**************************************************************************
445 Called if the map size is know or changes.
446 **************************************************************************/
447 void calculate_overview_dimensions(void)
449 int w, h;
450 int xfact = MAP_IS_ISOMETRIC ? 2 : 1;
452 static int recursion = 0; /* Just to be safe. */
454 /* Clip half tile left and right. See comment in map_to_overview_pos. */
455 int shift = (MAP_IS_ISOMETRIC && !current_topo_has_flag(TF_WRAPX)) ? -1 : 0;
457 if (recursion > 0 || wld.map.xsize <= 0 || wld.map.ysize <= 0) {
458 return;
460 recursion++;
462 get_overview_area_dimensions(&w, &h);
463 get_overview_area_dimensions(&w, &h);
465 /* Set the scale of the overview map. This attempts to limit the
466 * overview to the size of the area available.
468 * It rounds up since this gives good results with the default settings.
469 * It may need tweaking if the panel resizes itself. */
470 OVERVIEW_TILE_SIZE = MIN((w - 1) / (wld.map.xsize * xfact) + 1,
471 (h - 1) / wld.map.ysize + 1);
472 OVERVIEW_TILE_SIZE = MAX(OVERVIEW_TILE_SIZE, 1);
474 log_debug("Map size %d,%d - area size %d,%d - scale: %d", wld.map.xsize,
475 wld.map.ysize, w, h, OVERVIEW_TILE_SIZE);
477 gui_options.overview.width
478 = OVERVIEW_TILE_WIDTH * wld.map.xsize + shift * OVERVIEW_TILE_SIZE;
479 gui_options.overview.height = OVERVIEW_TILE_HEIGHT * wld.map.ysize;
481 if (gui_options.overview.map) {
482 canvas_free(gui_options.overview.map);
483 canvas_free(gui_options.overview.window);
485 gui_options.overview.map = canvas_create(gui_options.overview.width,
486 gui_options.overview.height);
487 gui_options.overview.window = canvas_create(gui_options.overview.width,
488 gui_options.overview.height);
489 canvas_put_rectangle(gui_options.overview.map,
490 get_color(tileset, COLOR_OVERVIEW_UNKNOWN),
491 0, 0,
492 gui_options.overview.width, gui_options.overview.height);
493 update_map_canvas_scrollbars_size();
495 /* Call gui specific function. */
496 overview_size_changed();
498 if (can_client_change_view()) {
499 refresh_overview_canvas();
502 recursion--;
505 /****************************************************************************
506 Free overview resources.
507 ****************************************************************************/
508 void overview_free(void)
510 if (gui_options.overview.map) {
511 canvas_free(gui_options.overview.map);
512 canvas_free(gui_options.overview.window);
513 gui_options.overview.map = NULL;
514 gui_options.overview.window = NULL;
518 /****************************************************************************
519 Callback to be called when an overview option is changed.
520 ****************************************************************************/
521 void overview_redraw_callback(struct option *option)
523 /* This is called once for each option changed so it is slower than
524 * necessary. If this becomes a problem it could be switched to use a
525 * queue system like the mapview drawing code does. */
526 refresh_overview_canvas();