/* Graphics support not specific to any Xconq interface. Copyright (C) 1992-2000 Stanley T. Shebs. Xconq is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. See the file COPYING. */ /* This file includes some very general graphics-related functionality that interfaces can (but are not required to) use. For instance, the size and shapes of hex cells have been precalculated to provide a reasonable appearance at several magnifications. Note that some of the algorithms in this file are abstracted from code that has been tuned and tweaked over many years, so it is strongly recommended that all new graphical interfaces use these. */ #include "conq.h" #include "kpublic.h" #include "imf.h" #include "ui.h" static void compute_q(void); static int module_name_compare(CONST void *a1, CONST void *a2); static int special_strcmp(char *str1, char *str2); static void calc_view_misc(VP *vp); static int blocking_utype(int u, int block); static ImageFamily *add_default_terrain_image(ImageFamily *imf, int t); static ImageFamily *add_default_material_image(ImageFamily *imf, int m); static ImageFamily *add_default_unit_image(ImageFamily *imf, int u); static ImageFamily *add_default_emblem_image(ImageFamily *imf, int s2); static UnitView *find_unit_or_occ_view(Side *side, VP *vp, UnitView *uview, int usx, int usy, int usw, int ush, int sx, int sy); static UnitView *find_unit_view_at(Side *side, VP *vp, int x, int y, int sx, int sy); /* The two games that should be always be available. */ char *first_game_name = INTRO_GAME; char *second_game_name = STANDARD_GAME; char *second_game_title; /* The following magical arrays set all the sizes at each magnification. */ /* This is the basic cell size. */ short mags[NUMPOWERS] = { 1, 2, 4, 8, 16, 32, 64, 128 }; /* These give the total dimensions of each hex cell, plus the vertical distance center-to-center. This is all carefully calculated to make the cells overlap perfectly at each different magnification, assuming that the icons have the right shape and size. */ short hws[NUMPOWERS] = { 1, 2, 4, 12, 24, 44, 88, 174 }; short hhs[NUMPOWERS] = { 1, 2, 4, 13, 26, 48, 96, 192 }; short hcs[NUMPOWERS] = { 1, 2, 4, 10, 20, 37, 74, 148 }; /* These numbers are half the length of a vertical side. */ short halfsides[NUMPOWERS] = { 1, 1, 2, 5, 7, 13, 26, 52 }; /* The sizes of the unit subcells. This is available drawing area, exact unit icon sizes depends on what's available. */ short uhs[NUMPOWERS] = { 1, 1, 3, 8, 16, 32, 64, 128 }; short uws[NUMPOWERS] = { 1, 1, 3, 8, 16, 32, 64, 128 }; /* Widths of borders and connections (0 implies don't draw at all). */ /* Full border width. */ short bwid[NUMPOWERS] = { 0, 0, 1, 2, 3, 5, 7, 9 }; /* Half-width, for narrower inset borders. */ short bwid2[NUMPOWERS] = { 0, 0, 1, 1, 2, 3, 4, 5 }; /* Full connection width. */ short cwid[NUMPOWERS] = { 0, 0, 1, 2, 3, 5, 7, 9 }; /* Coordinates of the hex borders. */ /* Note that array has extra column so don't need to wrap index. */ short bsx[NUMPOWERS][7] = { { 0 }, { 0 }, { 2, 4, 4, 2, 0, 0, 2 }, { 6, 12, 12, 6, 0, 0, 6 }, { 12, 24, 24, 12, 0, 0, 12 }, { 22, 44, 44, 22, 0, 0, 22 }, { 44, 88, 88, 44, 0, 0, 44 }, { 87, 174, 174, 87, 0, 0, 87 } }; short bsy[NUMPOWERS][7] = { { 0 }, { 0 }, { 0, 0, 4, 4, 4, 0, 0 }, { 0, 3, 10, 13, 10, 3, 0 }, { 0, 6, 20, 26, 20, 6, 0 }, { 0, 11, 37, 48, 37, 11, 0 }, { 0, 21, 75, 96, 75, 21, 0 }, { 0, 44, 148, 192, 148, 44, 0 } }; short ibsx[NUMPOWERS][7] = { { 0 }, { 0 }, { 1, 3, 4, 3, 1, 0, 1 }, { 3, 10, 13, 10, 3, 0, 3 }, { 6, 20, 26, 20, 6, 0, 6 }, { 11, 37, 48, 37, 11, 0, 11 }, { 22, 74, 96, 74, 22, 0, 22 }, { 0 } }; short ibsy[NUMPOWERS][7] = { { 0 }, { 0 }, { 0, 0, 1, 2, 2, 1, 0 }, { 0, 0, 3, 6, 6, 3, 0 }, { 0, 0, 6, 12, 12, 6, 0 }, { 0, 0, 11, 22, 22, 11, 0 }, { 0, 0, 22, 44, 44, 22, 0 }, { 0 } }; /* Coords of middles of each hex border (half a connection, basically). */ short lsx[NUMPOWERS][6] = { { 0 }, { 0 }, { 1, 2, 1, -1, -2, -1 }, { 3, 6, 3, -3, -6, -3 }, { 6, 12, 6, -6, -12, -6 }, { 11, 22, 11, -11, -22, -11 }, { 22, 44, 22, -22, -44, -22 }, { 44, 87, 44, -44, -87, -44 } }; short lsy[NUMPOWERS][6] = { { 0 }, { 0 }, { -2, 0, 2, 2, 0, -2 }, { -5, 0, 5, 5, 0, -5 }, { -9, 0, 9, 9, 0, -9 }, { -18, 0, 18, 18, 0, -18 }, { -36, 0, 36, 36, 0, -36 }, { -74, 0, 74, 74, 0, -74 } }; /* Isometric versions. */ short ilsx[NUMPOWERS][6] = { { 0 }, { 0 }, { 0, 2, 2, 0, -2, -2 }, { 0, 5, 5, 0, -5, -5 }, { 0, 10, 10, 0, -10, -10 }, { 0, 19, 19, 0, -19, -19 }, { 0, 38, 38, 0, -38, -38 }, { 0 } }; short ilsy[NUMPOWERS][6] = { { 0 }, { 0 }, { -1, -1, 1, 1, 1, -1 }, { -3, -2, 1, 3, 1, -2 }, { -6, -3, 3, 6, 3, -3 }, { -11, -5, 5, 11, 5, -5 }, { -22, -10, 10, 22, 10, -10 }, { 0 } }; short qx[NUMPOWERS][7], qy[NUMPOWERS][7]; static int q_computed; int extracells = 3; /* The traditional direction characters. */ char *dirchars = "ulnbhy"; /* The image family for regions that are not yet discovered. */ ImageFamily *unseen_image = NULL; char *unitchars = NULL; char *terrchars = NULL; char unseen_char_1; char unseen_char_2; ImageFamily **recorded_imfs; int num_recorded_imfs; int max_recorded_imfs; void (*imf_describe_hook)(Side *side, Image *img); /* Machinery to find and list all the games that should be listed as choices for the user. We don't actually scan library folders looking for all possible game designs therein, that would pick up experimental games and modules that are only for other modules' use. */ Module **possible_games = NULL; int numgames = 0; /* The comparison function for the game list puts un-formally-named modules at the end, plus the default sorting puts initial-lowercased names after uppercased ones. */ static int module_name_compare(CONST void *a1, CONST void *a2) { char *title1, *title2, *basetitle1, *basetitle2; Module *mp1, *mp2, *base1, *base2; int rslt; mp1 = *((Module **) a1); mp2 = *((Module **) a2); title1 = (!empty_string(mp1->title) ? mp1->title : mp1->name); title2 = (!empty_string(mp2->title) ? mp2->title : mp2->name); basetitle1 = title1; if ((base1 = find_game_module(mp1->basemodulename)) != NULL) basetitle1 = (!empty_string(base1->title) ? base1->title : base1->name); basetitle2 = title2; if ((base2 = find_game_module(mp2->basemodulename)) != NULL) basetitle2 = (!empty_string(base2->title) ? base2->title : base2->name); rslt = special_strcmp(basetitle1, basetitle2); if (rslt != 0) return rslt; if (mp1 == base2) return (-1); if (mp2 == base1) return 1; return special_strcmp(title1, title2); } static int special_strcmp(char *str1, char *str2) { if (strcmp(str1, second_game_title) == 0) { if (strcmp(str2, second_game_title) == 0) return 0; else return (-1); } else { if (strcmp(str2, second_game_title) == 0) return 1; else return strcmp(str1, str2); } } static int max_possible_games; void collect_possible_games(void) { int len; char *modulename = NULL, *modulecontents = NULL; Obj *lis; Module *module, *basemodule; FILE *fp; int startline = 0, endline = 0; if (numgames == 0 && numutypes == 0 /* !game_already_loaded() */) { len = 0; lis = lispnil; fp = open_library_file("game.dir"); if (fp != NULL) { lis = read_form(fp, &startline, &endline); if (consp(lis)) { len = length(lis); } else { init_warning("Game directory has bad format, no games found"); } fclose(fp); } max_possible_games = 2 + len * 2; /* Make enough room to record all the possible games. */ possible_games = (Module **) xmalloc(max_possible_games * sizeof(Module *)); /* Collect the intro and standard game modules and put at head of list. */ module = get_game_module(first_game_name); add_to_possible_games(module); module = get_game_module(second_game_name); add_to_possible_games(module); second_game_title = (!empty_string(module->title) ? module->title : module->name); for (; lis != lispnil; lis = cdr(lis)) { if (!(symbolp(car(lis)) || stringp(car(lis)))) { init_warning("Bad name in game dir list, ignoring"); continue; } modulename = c_string(car(lis)); if (modulename != NULL) { module = get_game_module(modulename); module->contents = modulecontents; add_to_possible_games(module); if (module->basemodulename != NULL) { basemodule = get_game_module(module->basemodulename); add_to_possible_games(basemodule); } } } if (numgames > 1) { /* Sort all but the first game into alphabetical order by displayed name. */ qsort(&(possible_games[1]), numgames - 1, sizeof(Module *), module_name_compare); } } } /* Load a game's description and add it to the list of games. */ void add_to_possible_games(Module *module) { int i; if (module != NULL) { if (load_game_description(module)) { /* It might be that the module description supplies the real name, and that the module already exists. (work on this) */ /* Don't add duplicate modules. */ for (i = 0; i < numgames; ++i) { if (possible_games[i] == module) return; } if (numgames < max_possible_games) { possible_games[numgames++] = module; } } } } /* Choose and return a reasonable location for map displays to start out centered on. */ void pick_a_focus(Side *side, int *xp, int *yp) { int tmpx, tmpy, dist, closest = area.maxdim; Unit *unit, *closestunit = NULL; /* Explicit setting overrides any guesses. */ if (in_area(side->init_center_x, side->init_center_y)) { *xp = side->init_center_x; *yp = side->init_center_y; return; } if (side->startx < 0 || side->starty < 0) calc_start_xy(side); if (side->startx < 0 || side->starty < 0) { *xp = area.width / 2 - area.height / 4; *yp = area.halfheight; } else { tmpx = side->startx; tmpy = side->starty; /* Rescan the units to find a closest one. */ for_all_side_units(side, unit) { if (in_play(unit)) { /* If already got one right there, just return. */ if (unit->x == tmpx && unit->y == tmpy) { *xp = tmpx; *yp = tmpy; return; } else { dist = distance(unit->x, unit->y, tmpx, tmpy); if (dist < closest) { closest = dist; closestunit = unit; } } } } if (closestunit != NULL) { /* Return the position of the unit closest to the avg position. */ *xp = closestunit->x; *yp = closestunit->y; } else { *xp = tmpx; *yp = tmpy; } } } int num_active_displays(void) { int n = 0; Side *side; for_all_sides(side) { if (active_display(side)) ++n; } return n; } /* Compute positions at each hex corner, slightly inset. */ static void compute_q(void) { int d, p, w; for (p = 0; p < NUMPOWERS; ++p) { if (p < 2) continue; w = bwid[p] + 1; for_all_directions(d) { qx[p][d] = bsx[p][d] + ((hws[p] - 2 * bsx[p][d]) * w) / (2 * mags[p]); qy[p][d] = bsy[p][d] + ((hhs[p] - 2 * bsy[p][d]) * w) / (2 * mags[p]); } qx[p][NUMDIRS] = qx[p][0]; qy[p][NUMDIRS] = qy[p][0]; } } /* Viewport handling. */ VP * new_vp(void) { int t, thickest; VP *vp; if (!q_computed) { compute_q(); q_computed = TRUE; } vp = (VP *) xmalloc(sizeof(VP)); vp->draw_aux_terrain = (short *) xmalloc(numttypes * sizeof(short)); /* View at a 90 degree angle by default. */ vp->angle = 90; /* No vertical exaggeration by default. */ vp->vertscale = 1; /* For isometric views, start out looking northeast. */ vp->isodir = NORTHEAST; vp->cellwidth = area.cellwidth; /* If the cellwidth is not reasonable for drawing elevations, use an approximation based on the range of elevations and terrain thicknesses. */ if (vp->cellwidth <= 1) { thickest = 0; for_all_terrain_types(t) { if (t_thickness(t) > thickest) thickest = t_thickness(t); } vp->cellwidth = ((area.maxelev + thickest) - area.avgelev) / 2; } if (vp->cellwidth < 1) vp->cellwidth = 1; set_contour_interval(vp, 0); return vp; } /* xform_cell has been separated into two functions, a core that does the xform calculations and a shell that adds on the vertical offset. This makes it possible to bypass the vertical offset by calling xform_cell_flat directly. */ /* Given a viewport and a cell, figure out where its UL corner will be. */ void xform_cell(VP *vp, int x, int y, int *sxp, int *syp) { int xw, elev, offset; /* First call the core function. */ xform_cell_flat(vp, x, y, sxp, syp); /* Then add vertical offset if drawing at an angle. */ if (vp->angle != 90 || vp->isometric) { xw = wrapx(x); elev = (elevations_defined() ? elev_at(xw, y) : 0) - area.avgelev; /* Exaggerate the vertical scale if requested. */ elev *= vp->vertscale; offset = (elev * vp->hh) / vp->cellwidth; *syp -= offset; } } void xform_cell_top(VP *vp, int x, int y, int *sxp, int *syp) { int xw, elev, offset; /* First call the core function. */ xform_cell_flat(vp, x, y, sxp, syp); /* Then add vertical offset if drawing at an angle. */ if (vp->angle != 90 || vp->isometric) { xw = wrapx(x); elev = (elevations_defined() ? elev_at(xw, y) : 0) - area.avgelev; /* We see the top of the terrain in the cell, not the bottom. */ elev += t_thickness(terrain_at(xw, y)); /* Exaggerate the vertical scale if requested. */ elev *= vp->vertscale; offset = (elev * vp->hh) / vp->cellwidth; *syp -= offset; } } /* Core calculation for xform_cell without vertical offset. */ void xform_cell_flat(VP *vp, int x, int y, int *sxp, int *syp) { int xnw; #if 0 /* Mac background redrawing uses out-of-area cell positions up to 8 cells east of the area edge! */ if (!(in_area(x, y) || (in_area(x - 8, y) && vp->wide_viewport))) { /* Always die on this, indicates bugs that must be fixed. */ run_error("attempting to xform %d,%d", x, y); return; } #endif if (vp->isometric) { /* The isometric case is funky in that each view direction seems to need a different formula. There may be some unifying form that works for all directions, but I don't know what it might look like. */ switch (vp->isodir) { case NORTHEAST: *sxp = x * vp->hch + (vp->hh - vp->hch); *syp = (((area.height - 1 - y) * vp->hw) / 2 + ((area.width - 1 - x) * vp->hw) / 4 - (area.halfheight * vp->hw) / 4); break; case EAST: *sxp = (area.height - 1 - y) * vp->hch; *syp = (((area.halfheight + area.width) * vp->hw) / 2 - ((x * vp->hw) / 2 + ((y + area.halfheight) * vp->hw) / 4) - vp->hw / 2); break; case SOUTHEAST: *sxp = ((area.width - 1 - x + area.height - 1 - y) * vp->hch + (vp->hh - vp->hch)); *syp = ((area.width - 1 - x + y) * vp->hw) / 4; break; case SOUTHWEST: *sxp = (area.width - 1 - x) * vp->hch + (vp->hh - vp->hch); *syp = (y * vp->hw) / 2 + (x * vp->hw) / 4; break; case WEST: *sxp = y * vp->hch; *syp = (x * vp->hw) / 2 + ((y - area.halfheight) * vp->hw) / 4; if (area.xwrap) *syp += (area.halfheight * vp->hw) / 4; break; case NORTHWEST: *sxp = (x + y) * vp->hch + (vp->hh - vp->hch); *syp = ((area.height - 1 - y + x) * vp->hw) / 4; break; default: case_panic("view direction", vp->isodir); break; } /* Make viewport-relative, works the same for every view direction. */ *sxp -= vp->sx; *syp -= vp->sy; return; } /* Traditional overhead transformation. */ if (vp->wide_viewport) { /* This version "unwraps" x, currently appropriate for Mac interface. */ xnw = x; if (area.xwrap && between(area.width - y / 2, xnw, area.width - 1) && vp->sx < (vp->totsh - vp->sy) / 2 ) xnw -= area.width; *sxp = xnw * vp->hw + (y * vp->hw) / 2 - vp->sx; } else { /* This calculation is currently appropriate for the Unix interface. */ /* Compute the scaled x. */ *sxp = x * vp->hw + (y * vp->hw) / 2 - vp->sx; /* If the world is cylindrical, then we want to come up with a transformed point that is in the viewport if possible; either add or subtract the scaled width of the world if that will help. */ if (area.xwrap) { if (*sxp < - vp->hw && between(0, *sxp + (area.width * vp->hw), vp->pxw)) *sxp += area.width * vp->hw; if (*sxp > vp->pxw && between(0, *sxp - (area.width * vp->hw), vp->pxw)) *sxp -= area.width * vp->hw; } } /* Compute the scaled y. Screen coords for y run downwards, so we subtract. */ *syp = (vp->totsh - (vp->hh + y * vp->hch)) - vp->sy; } /* Similarly, but allowing 1/1000ths of cells as input. Note that since .001 is at the "bottom" of the cell as it appears on the screen, we must subtract the fraction from 1000, similarly to how we do the main y value. */ void xform_cell_fractional(VP *vp, int x, int y, int xf, int yf, int *sxp, int *syp) { xform_cell(vp, x, y, sxp, syp); *sxp += (xf * vp->hw) / 1000; *syp += ((1000 - yf) * vp->hch) / 1000; } /* Same as xform_cell_fractional but bypasses vertical offset. */ void xform_cell_fractional_flat(VP *vp, int x, int y, int xf, int yf, int *sxp, int *syp) { xform_cell_flat(vp, x, y, sxp, syp); *sxp += (xf * vp->hw) / 1000; *syp += ((1000 - yf) * vp->hch) / 1000; } void xform_unit(VP *vp, Unit *unit, int *sxp, int *syp, int *swp, int *shp) { int num = 0, n = -1, sq, sx, sy, sx1, sy1, sw1, sh1; int x = unit->x, y = unit->y; Unit *unit2; if (unit->transport == NULL) { xform_cell(vp, x, y, &sx, &sy); /* Adjust to the unit box within the cell. */ sx += (vp->hw - vp->uw) / 2; if (vp->isometric) sy += (vp->hw / 2 - vp->uh); else sy += (vp->hh - vp->uh) / 2; /* Figure out our position in this cell's stack. */ for_all_stack(x, y, unit2) { /* (should only count units visible to a given side) */ if (unit == unit2) n = num; ++num; } if (n < 0) { run_warning("xform_unit weirdness with %s", unit_desig(unit)); *sxp = *syp = 0; *swp = *shp = 1; return; } if (num <= 1) { sq = 1; } else if (num <= 4) { sq = 2; } else if (num <= 16) { sq = 4; } else if (num <= 256) { sq = 8; } else { /* This is room for 65,536 units in a stack. */ sq = 16; } /* Tighten up positions if using big icons. */ if (g_bigicons()) { *swp = vp->hw / sq; *shp = vp->hw / sq; sx -= (vp->hw - vp->uw) / 2; sy -= (vp->hw - vp->uw) / (2 * sq); *sxp = sx + *swp * (n / sq) * 3 / 4; *syp = sy + *shp * (n % sq) * 3 / 4; } else { *swp = vp->uw / sq; *shp = vp->uh / sq; *sxp = sx + *swp * (n / sq); *syp = sy + *shp * (n % sq); } } else { /* Go up the transport chain to get the bounds for this unit. */ xform_unit(vp, unit->transport, &sx1, &sy1, &sw1, &sh1); xform_occupant(vp, unit->transport, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp); } } void xform_unit_view(Side *side, VP *vp, UnitView *uview, int *sxp, int *syp, int *swp, int *shp) { int num = 0, n = -1, sq, sx, sy, x, y; Unit *unit; UnitView *uview2; x = uview->x; y = uview->y; xform_cell(vp, x, y, &sx, &sy); /* Adjust to the unit box within the cell. */ sx += (vp->hw - vp->uw) / 2; if (vp->isometric) sy += (vp->hw / 2 - vp->uh); else sy += (vp->hh - vp->uh) / 2; /* Figure out our position in this cell's stack. */ for_all_view_stack(side, x, y, uview2) { unit = view_unit(uview2); /* Only count views of top-level units (occupants may have view objects at this location also). */ if (unit == NULL || unit->transport == NULL) { if (uview2 == uview) n = num; ++num; } } if (n < 0) { run_warning("xform_unit_view weirdness with"); *sxp = *syp = 0; *swp = *shp = 1; return; } if (num <= 1) { sq = 1; } else if (num <= 4) { sq = 2; } else if (num <= 16) { sq = 4; } else if (num <= 256) { sq = 8; } else { /* This is room for 65,536 unit views in a stack. */ sq = 16; } /* Tighten up positions if using big icons. */ if (g_bigicons()) { *swp = vp->hw / sq; *shp = vp->hw / sq; sx -= (vp->hw - vp->uw) / 2; sy -= (vp->hw - vp->uw) / (2 * sq); *sxp = sx + *swp * (n / sq) * 3 / 4; *syp = sy + *shp * (n % sq) * 3 / 4; } else { *swp = vp->uw / sq; *shp = vp->uh / sq; *sxp = sx + *swp * (n / sq); *syp = sy + *shp * (n % sq); } } void xform_unit_self(VP *vp, Unit *unit, int *sxp, int *syp, int *swp, int *shp) { int sx1, sy1, sw1, sh1; if (unit->transport == NULL) { if (unit->occupant == NULL) { xform_unit(vp, unit, sxp, syp, swp, shp); } else { xform_unit(vp, unit, &sx1, &sy1, &sw1, &sh1); xform_occupant(vp, unit, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp); } } else { xform_unit(vp, unit->transport, &sx1, &sy1, &sw1, &sh1); xform_occupant(vp, unit->transport, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp); } } /* Given a unit, compute its actual bounding box (as opposed to the box that includes both its and its occupants). */ int xform_unit_self_view(Side *side, VP *vp, Unit *unit, int *sxp, int *syp, int *swp, int *shp) { int sx1, sy1, sw1, sh1; UnitView *uview = NULL; if (unit->transport == NULL) { for_all_view_stack(side, unit->x, unit->y, uview) { if (view_unit(uview) == unit) break; } if (uview == NULL || view_unit(uview) != unit) return FALSE; if (unit->occupant == NULL) { xform_unit_view(side, vp, uview, sxp, syp, swp, shp); } else { xform_unit_view(side, vp, uview, &sx1, &sy1, &sw1, &sh1); xform_occupant(vp, unit, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp); } } else { for_all_view_stack(side, unit->transport->x, unit->transport->y, uview) { if (view_unit(uview) == unit->transport) break; } if (uview == NULL || view_unit(uview) != unit->transport) return FALSE; xform_unit_view(side, vp, uview, &sx1, &sy1, &sw1, &sh1); xform_occupant(vp, unit->transport, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp); } return TRUE; } void xform_occupant(VP *vp, Unit *transport, Unit *unit, int sx, int sy, int sw, int sh, int *sxp, int *syp, int *swp, int *shp) { int num = 0, n = -1, nmx, nmy; Unit *unit2; #ifdef MAC /* Until the tcltk version has closeups. */ /* Skip transformation if using big icons. */ if (g_bigicons()) { *sxp = sx; *syp = sy; *swp = sw; *shp = sh; return; } #endif /* Figure out the position of this unit amongst all the occupants. */ for_all_occupants(transport, unit2) { if (unit2 == unit) n = num; ++num; } if (unit == transport) { if (num > 0) { /* Transport image shrinks by half in each dimension. */ *swp = sw / 2; *shp = sh / 2; } /* Transport is always in the UL corner. */ *sxp = sx; *syp = sy; } else { if (n < 0) run_error("xform_occupant weirdness"); /* Compute how the half-box will be subdivided. Only use powers of two, so image scaling works better. */ if (num <= 2) { nmx = 2; } else if (num <= 8) { nmx = 4; } else if (num <= 128) { nmx = 8; } else { /* This is room for 32,768 units in a stack. */ nmx = 16; } nmy = nmx / 2; *swp = sw / nmx; *shp = (sh / 2) / nmy; *sxp = sx + *swp * (n / nmy); *syp = sy + sh / 2 + *shp * (n % nmy); } } /* Scale one viewport box to its position in another. */ void scale_vp(VP *vp, VP *vp2, int *sxp, int *syp, int *swp, int *shp) { *sxp = (vp2->sx * vp->hw) / vp2->hw - vp->sx; *syp = (vp2->sy * vp->hch) / vp2->hch - vp->sy; *swp = (vp2->pxw * vp->hw) / vp2->hw; *shp = (vp2->pxh * vp->hch) / vp2->hch; } #if 0 void scale_point(VP *vp, VP *vp2, int sx, int sy, int *sx2p, int *sy2p) { *sx2p = ((sx + vp2->sx) * vp->hw) / vp2->hw - vp->sx; *sy2p = ((sy + vp2->sy) * vp->hch) / vp2->hch - vp->sy; } #endif int nearest_cell(VP *vp, int sx, int sy, int *xp, int *yp, int *xfp, int *yfp) { int sxadj, syadj, sxfrac, syflipped, syfrac, tmp1; if (vp->isometric) { /* Convert to absolute scaled coordinates. */ sx += vp->sx; sy += vp->sy; switch (vp->isodir) { case NORTHEAST: sy += (area.halfheight * vp->hw) / 4; *xp = (sx - (vp->hh - vp->hch)) / vp->hch; syadj = sy - ((area.width - 1 - *xp) * vp->hw) / 4; *yp = (area.height - 1) - (2 * syadj) / vp->hw; break; case EAST: sy += vp->hw / 2; *yp = (area.height - 1) - sx / vp->hch; tmp1 = ((area.halfheight + area.width) * vp->hw) / 2; *xp = ((tmp1 - sy - ((*yp + area.halfheight) * vp->hw) / 4) * 2) / vp->hw; break; case SOUTHEAST: *xp = area.width - 1 - (((sx - (vp->hh - vp->hch)) / vp->hch) + ((sy * 4) / vp->hw) - (area.height - 1)) / 2; *yp = ((sy * 4) / vp->hw) - (area.width - 1 - *xp); break; case SOUTHWEST: *xp = (area.width - 1) - (sx - (vp->hh - vp->hch)) / vp->hch; *yp = ((sy - (*xp * vp->hw) / 4) * 2) / vp->hw; break; case WEST: if (area.xwrap) sy -= (area.halfheight * vp->hw) / 4; *yp = sx / vp->hch; *xp = ((sy - ((*yp - area.halfheight) * vp->hw) / 4) * 2) / vp->hw; break; case NORTHWEST: *xp = ((sx + (vp->hh - vp->hch)) / vp->hch - (area.height - 1) + ((sy * 4) / vp->hw)) / 2; *yp = *xp - ((sy * 4) / vp->hw) + (area.height - 1); break; default: case_panic("view direction", vp->isodir); break; } return (in_area(*xp, *yp)); } /* Flip the raw y and then scale to hex coords. */ syflipped = vp->totsh - (vp->sy + sy); *yp = syflipped / vp->hch; if (yfp) { syfrac = syflipped - (*yp * vp->hch) - (vp->hh - vp->hch); *yfp = (syfrac * 1000) / vp->hch; } /* Adjust scaled x. */ sxadj = (sx + vp->sx - (*yp * vp->hw) / 2); /* The division by hw below might round towards 0, so wrap negative numbers around to positive values. This should only ever happens for cylinder areas, but doesn't hurt to just adjust all negative values. */ /* Unfortunately, it does hurt. The mac-specific meridian drawing code must be allowed to use negative values for non-cylindrical worlds! */ if (area.xwrap && sxadj < 0) sxadj += (2 * area.width * vp->hw); *xp = sxadj / vp->hw; if (xfp) { sxfrac = sxadj - (*xp * vp->hw); *xfp = (sxfrac * 1000) / vp->hw; } /* If the magnification of the map is large enough that the top and bottom edges of a hex are visibly sloping, then we have to take those edges into account, and accurately. */ if ((vp->hh - vp->hch) / 2 > 1) { /* (should adjust according to hex boundaries correctly here) */ } /* Wrap coords as usual. */ if (area.xwrap) *xp = wrapx(*xp); DGprintf("Pixel %d,%d -> hex %d.%03d,%d.%03d\n", sx, sy, *xp, (xfp ? *xfp : 0), *yp, (yfp ? *yfp : 0)); return (in_area(*xp, *yp)); } int nearest_boundary(VP *vp, int sx, int sy, int *xp, int *yp, int *dirp) { int sx2, sy2, ydelta, hexslope; /* Get the nearest cell... */ if (nearest_cell(vp, sx, sy, xp, yp, NULL, NULL)) { /* ... and xform it back to get the pixel coords. */ xform_cell(vp, *xp, *yp, &sx2, &sy2); ydelta = sy - sy2; hexslope = (vp->hh - vp->hch) / 2; if (sx - sx2 > vp->hw / 2) { *dirp = ((ydelta < hexslope) ? NORTHEAST : (ydelta > vp->hch ? SOUTHEAST : EAST)); } else { *dirp = ((ydelta < hexslope) ? NORTHWEST : (ydelta > vp->hch ? SOUTHWEST : WEST)); } DGprintf("Pixel %d,%d -> hex %d,%d dir %d\n", sx, sy, *xp, *yp, *dirp); return TRUE; } else { return FALSE; } } Unit * find_unit_or_occ(Side *side, VP *vp, Unit *unit, int usx, int usy, int usw, int ush, int sx, int sy) { int usx1, usy1, usw1, ush1; Unit *occ, *rslt; /* See if the point might be over an occupant. */ if (unit->occupant != NULL #ifdef MAC && g_bigicons() != TRUE #endif && (side_controls_unit(side, unit) /* If side can examine unit in detail it can also see its occs. */ || side_sees_unit(side, unit) || vp->show_all || u_see_occupants(unit->type) || side_owns_occupant(side, unit))) { for_all_occupants(unit, occ) { xform_unit(vp, occ, &usx1, &usy1, &usw1, &ush1); rslt = find_unit_or_occ(side, vp, occ, usx1, usy1, usw1, ush1, sx, sy); if (rslt) return rslt; } } /* Otherwise see if it could be the unit itself. This has the effect of "giving" the transport everything in its box that is not in an occ. */ xform_unit(vp, unit, &usx1, &usy1, &usw1, &ush1); if (between(usx1, sx, usx1 + usw1) && between(usy1, sy, usy1 + ush1)) return unit; return NULL; } Unit * find_unit_at(Side *side, VP *vp, int x, int y, int sx, int sy) { int usx, usy, usw, ush; Unit *unit, *rslt; for_all_stack(x, y, unit) { xform_unit(vp, unit, &usx, &usy, &usw, &ush); rslt = find_unit_or_occ(side, vp, unit, usx, usy, usw, ush, sx, sy); if (rslt) return rslt; } return NULL; } int nearest_unit(Side *side, VP *vp, int sx, int sy, Unit **unitp) { int x, y; if (!nearest_cell(vp, sx, sy, &x, &y, NULL, NULL)) { *unitp = NULL; DGprintf("Pixel %d,%d -> outside area\n", sx, sy); return FALSE; } if (vp->power > 4) { *unitp = find_unit_at(side, vp, x, y, sx, sy); } else { *unitp = unit_at(x, y); } DGprintf("Pixel %d,%d -> unit %s\n", sx, sy, unit_desig(*unitp)); return TRUE; } UnitView * find_unit_or_occ_view(Side *side, VP *vp, UnitView *uview, int usx, int usy, int usw, int ush, int sx, int sy) { int usx1, usy1, usw1, ush1; #if 0 /* until we figure this out */ Unit *unit, *occ, *rslt; /* See if the point might be over an occupant. */ if (unit->occupant != NULL #ifdef MAC && g_bigicons() != TRUE #endif && (side_controls_unit(side, unit) /* If side can examine unit in detail it can also see its occs. */ || side_sees_unit(side, unit) || vp->show_all || u_see_occupants(unit->type) || side_owns_occupant(side, unit))) { for_all_occupants(unit, occ) { xform_unit(vp, occ, &usx1, &usy1, &usw1, &ush1); rslt = find_unit_or_occ(side, vp, occ, usx1, usy1, usw1, ush1, sx, sy); if (rslt) return rslt; } } #endif /* Otherwise see if it could be the unit itself. This has the effect of "giving" the transport everything in its box that is not in an occ. */ xform_unit_view(side, vp, uview, &usx1, &usy1, &usw1, &ush1); if (between(usx1, sx, usx1 + usw1) && between(usy1, sy, usy1 + ush1)) return uview; return NULL; } UnitView * find_unit_view_at(Side *side, VP *vp, int x, int y, int sx, int sy) { int usx, usy, usw, ush; UnitView *uview, *rslt; x = wrapx(x); for_all_view_stack(side, x, y, uview) { xform_unit_view(side, vp, uview, &usx, &usy, &usw, &ush); rslt = find_unit_or_occ_view(side, vp, uview, usx, usy, usw, ush, sx, sy); if (rslt) return rslt; } return NULL; } int nearest_unit_view(Side *side, VP *vp, int sx, int sy, UnitView **uviewp) { int x, y; if (!nearest_cell(vp, sx, sy, &x, &y, NULL, NULL)) { *uviewp = NULL; DGprintf("Pixel %d,%d -> outside area\n", sx, sy); return FALSE; } if (vp->power > 4) { *uviewp = find_unit_view_at(side, vp, wrapx(x), y, sx, sy); } else { *uviewp = unit_view_at(side, wrapx(x), y); } DGprintf("Pixel %d,%d -> ", sx, sy); if (DebugG) { if (*uviewp != NULL) { DGprintf("uview %d,%d,%d\n", (*uviewp)->id, (*uviewp)->type, (*uviewp)->side_id); } else { DGprintf("no uview\n"); } } return TRUE; } /* Return true if the cell is visible in the viewport. */ int cell_is_visible(VP *vp, int x, int y) { int sx, sy; if (!in_area(x, y)) return FALSE; xform_cell(vp, x, y, &sx, &sy); if (area.xwrap && sx > vp->totsw) sx -= vp->totsw; if (sx + vp->hw < 0) return FALSE; if (sx > vp->pxw) return FALSE; if (sy + vp->hh < 0) return FALSE; if (sy > vp->pxh) return FALSE; return TRUE; } /* Decide whether given location is away from the edge of the map's window. */ int cell_is_in_middle(VP *vp, int x, int y) { int sx, sy, insetx1, insety1, insetx2, insety2; if (!in_area(x, y)) return FALSE; xform_cell(vp, x, y, &sx, &sy); /* Adjust to be the center of the cell, more reasonable if large. */ sx += vp->hw / 2; sy += vp->hh / 2; insetx1 = min(vp->pxw / 4, 1 * vp->hw); insety1 = min(vp->pxh / 4, 1 * vp->hch); insetx2 = min(vp->pxw / 4, 2 * vp->hw); insety2 = min(vp->pxh / 4, 2 * vp->hch); if (sx < insetx2) return FALSE; if (sx > vp->pxw - insetx2) return FALSE; if (sy < (between(2, y, area.height-3) ? insety2 : insety1)) return FALSE; if (sy > vp->pxh - (between(2, y, area.height-3) ? insety2 : insety1)) return FALSE; return TRUE; } /* Set vcx/vcy to point to the center of the view. */ void focus_on_center(VP *vp) { vp->vcy = (vp->totsh - (vp->sy + vp->pxh / 2)) / vp->hch; vp->vcx = vp->sx / vp->hw - (vp->vcy / 2) + (vp->pxw / vp->hch) / 2; /* Restrict the focus to be *inside* the area. */ vp->vcy = limitn(1, vp->vcy, area.height - 2); if (area.xwrap) { vp->vcx = wrapx(vp->vcx); } else { vp->vcx = limitn(1, vp->vcx, area.width - 2); if (vp->vcx + vp->vcy < area.halfheight + 1) vp->vcx = area.halfheight + 1; if (vp->vcx + vp->vcy > area.width + area.halfheight - 1) vp->vcx = area.width + area.halfheight - 1; } } void center_on_focus(VP *vp) { int sx, sy, xnw = vp->vcx; if (vp->isometric) { xform_cell(vp, vp->vcx, vp->vcy, &sx, &sy); sx += vp->sx - vp->pxw / 2; sy += vp->sy - vp->pxh / 2; } else { if (area.xwrap && xnw >= (area.width - vp->vcy / 2)) xnw -= area.width; /* Scale, add hex offset adjustment, translate to get left edge. */ sx = xnw * vp->hw + (vp->vcy * vp->hw) / 2 - vp->pxw / 2 + vp->hw / 2; /* Scale, translate to top edge, flip. */ sy = vp->totsh - (vp->vcy * vp->hch + vp->pxh / 2 + vp->hh / 2); } set_view_position(vp, sx, sy); DGprintf("Viewport 0x%x at %d,%d, focused at %d,%d\n", vp, vp->sx, vp->sy, vp->vcx, vp->vcy); } int set_view_size(VP *vp, int w, int h) { if (w < 1 || h < 1) run_error("Bad viewport size %dx%d", w, h); vp->pxw = w; vp->pxh = h; calc_view_misc(vp); return TRUE; } int set_view_position(VP *vp, int sx, int sy) { vp->sx = sx; vp->sy = sy; /* Clip to rational limits. */ vp->sx = limitn(vp->sxmin, vp->sx, vp->sxmax); vp->sy = limitn(vp->symin, vp->sy, vp->symax); return TRUE; } /* Given a magnification power, calculate and cache the sizes within a cell, and the scaled size in pixels of the entire world. */ int set_view_power(VP *vp, int power) { vp->power = power; vp->mag = mags[power]; /* is this used?? */ vp->hw = hws[power]; vp->hh = hhs[power]; vp->hch = hcs[power]; vp->uw = uws[power]; vp->uh = uhs[power]; if (vp->angle == 30) { vp->hh /= 2; vp->hch /= 2; } else if (vp->angle == 15) { vp->hh /= 4; vp->hch /= 4; } calc_view_misc(vp); DGprintf("Viewport 0x%x power is now %d, total scaled area is %d x %d\n", vp, vp->power, vp->totsw, vp->totsh); return TRUE; } int set_view_focus(VP *vp, int x, int y) { if (!in_area(x, y)) run_error("View focus of %d,%d not in area", x, y); vp->vcx = x; vp->vcy = y; return TRUE; } int set_view_angle(VP *vp, int angle) { if (!(angle == 90 || angle == 30 || angle == 15)) { run_warning("Bad angle %d, setting to 90", angle); angle = 90; } vp->angle = angle; vp->hh = hhs[vp->power]; vp->hch = hcs[vp->power]; vp->uh = uhs[vp->power]; if (vp->angle == 30) { vp->hh /= 2; vp->hch /= 2; vp->uh /= 2; } else if (vp->angle == 15) { vp->hh /= 4; vp->hch /= 4; vp->uh /= 4; } calc_view_misc(vp); DGprintf("Angle is now %d, total scaled area is %d x %d\n", vp->angle, vp->totsw, vp->totsh); return TRUE; } int set_view_isometric(VP *vp, int flag, int scale) { vp->isometric = flag; vp->vertscale = scale; calc_view_misc(vp); return TRUE; } int set_view_direction(VP *vp, int dir) { vp->isodir = dir; calc_view_misc(vp); return TRUE; } static void calc_view_misc(VP *vp) { if (vp->isometric) { switch (vp->isodir) { case NORTHEAST: case SOUTHEAST: case SOUTHWEST: case NORTHWEST: vp->totsw = area.width * vp->hch + (vp->hh - vp->hch); vp->totsh = (area.width * vp->hw) / 4; if (area.xwrap) vp->totsh += (area.height * vp->hw) / 2; else vp->totsh += ((area.halfheight * vp->hw) / 4) * 2; break; case EAST: case WEST: vp->totsw = area.height * vp->hch + (vp->hh - vp->hch); vp->totsh = (area.width * vp->hw) / 2; break; default: case_panic("view direction", vp->isodir); break; } /* (should add any adjust due to elevated cells) */ vp->sxmin = vp->symin = 0; } else { /* Calculate and cache the width in pixels of the whole area, adding an an adjustment to account for the "bulge" of hexagon-shaped areas. */ /* (but it seems the adjustment is wrong for sizing - should figure out why this seems necessary) */ vp->totsw = area.width * vp->hw + hexagon_adjust(vp); /* I don't understand why this adjustment is done. It has no apparent effect on either mac, xtconq or tcltk drawing. HR. */ #if 0 if (area.xwrap && (vp->totsw > (vp->pxw - vp->hw))) vp->totsw += (vp->pxw - vp->hw); #endif /* Total scaled height is based on center-to-center height, plus an adjustment to include the bottom parts of the bottom row. */ vp->totsh = area.height * vp->hch + (vp->hh - vp->hch); vp->sxmin = hexagon_adjust(vp); vp->symin = 0; } /* Special-case wide viewports (a la Mac) for cylindrical worlds. */ if (vp->wide_viewport && area.xwrap) vp->sxmax = area.width * vp->hw; else vp->sxmax = max(vp->sxmin, vp->totsw - vp->pxw); vp->symax = max(vp->symin, vp->totsh - vp->pxh); /* Limiting sx to totsw instead of sxmax fixes the misaligned updates bug at 8x8 resultion. */ vp->sx = limitn(vp->sxmin, vp->sx, (vp->isometric ? vp->sxmax : vp->totsw)); vp->sy = limitn(vp->symin, vp->sy, vp->symax); } void free_vp(VP *vp) { free(vp); } int any_borders_in_dir(int x, int y, int dir) { int b; if (!any_aux_terrain_defined()) return FALSE; for_all_border_types(b) { /* Note that border_at tests for layer existence. */ if (border_at(x, y, dir, b)) return TRUE; } return FALSE; } /* Compute the transition between two cells with different terrain, returning the positioning of the transition and which of four subimages to use. */ int compute_transition(Side *side, VP *vp, int x, int y, int dir, int *sxp, int *syp, int *swp, int *shp, int *offsetp) { int t, x1, y1, t1, x2, y2, sx, sy, sw, sh, sx2, sy2; int trite, tleft, overrite, overleft, offset; int w = vp->hw, h = vp->hh, hch = vp->hch; if (!point_in_dir(x, y, dir, &x1, &y1)) return FALSE; if (!(vp->show_all || terrain_visible(side, x1, y1))) return FALSE; t = terrain_at(x, y); t1 = terrain_at(x1, y1); /* We want to overlap the adjacent terrain into this cell if the terrain type is lower-numbered (meaning that sea usually overlaps land for instance), but not if there is a border, because the border itself provides the transition. */ if (t1 >= t || any_borders_in_dir(x, y, dir)) return FALSE; if (point_in_dir(x, y, right_dir(dir), &x2, &y2)) trite = terrain_at(x2, y2); else trite = t; /* Overlap on the right-hand corner if the third cell's terrain is the same as the adjacent cell's terrain. */ overrite = (trite == t1); /* Border on one side suppresses overlap. */ if (any_borders_in_dir(x, y, right_dir(dir))) overrite = FALSE; if (point_in_dir(x, y, left_dir(dir), &x2, &y2)) tleft = terrain_at(x2, y2); else tleft = t; /* Overlap on the left-hand corner if the third cell's terrain is the same as the adjacent cell's terrain. */ overleft = (tleft == t1); /* Border on one side suppresses overlap. */ if (any_borders_in_dir(x, y, left_dir(dir))) overleft = FALSE; xform_cell(vp, x, y, &sx, &sy); /* Now, given the overlaps on each side, choose the right piece of transition bitmap to use. */ switch (dir) { case NORTHEAST: sx2 = sx + w / 2; sy2 = sy; sw = w / 2; sh = h - hch + 1; if (overrite) offset = 1; else if (overleft) offset = 2; break; case EAST: sx2 = sx + w / 2; sy2 = sy + (h - hch) + 1; sw = w / 2; sh = h - 2 * (h - hch) - 2; if (overrite) offset = 2; else if (overleft) offset = 1; break; case SOUTHEAST: sx2 = sx + w / 2; sy2 = sy + hch - 1; sw = w / 2; sh = h - hch + 1; if (overrite) offset = 1; else if (overleft) offset = 2; break; case SOUTHWEST: sx2 = sx; sy2 = sy + hch - 1; sw = w / 2; sh = h - hch + 1; if (overrite) offset = 2; else if (overleft) offset = 1; break; case WEST: sx2 = sx; sy2 = sy + (h - hch) + 1; sw = w / 2; sh = h - 2 * (h - hch) - 2; if (overrite) offset = 1; else if (overleft) offset = 2; break; case NORTHWEST: sx2 = sx; sy2 = sy; sw = w / 2; sh = h - hch + 1; if (overrite) offset = 2; else if (overleft) offset = 1; break; } if (overrite && overleft) offset = 0; else if (!overrite && !overleft) offset = 3; *sxp = sx2; *syp = sy2; *swp = sw; *shp = sh; *offsetp = offset; return TRUE; } void compute_fire_line_segment(int sx1, int sy1, int sx2, int sy2, int i, int n, int *xx, int *yy, int *dx, int *dy) { /* Position one segment of a line between the locations. */ *dx = (sx2 - sx1) / n; *dy = (sy2 - sy1) / n; *xx = sx1 + ((i / 2) % n) * *dx; *yy = sy1 + ((i / 2) % n) * *dy; } /* This routine can be used by the interface to place legends */ /* orient==0 : E (horizontal) only; orient==1 : E, SE, NE; orient==2 : E, SE, NE, ESE, ENE, N; */ /* block==0 : write over any unit; block==1 : don't write over "city-like" units; block==2 : don't write over visible units. */ void place_feature_legends(Legend *legend, int nf, Side *side, int orient, int block) { int x, y, x1, y1, dx, dy, f, i, i3, j, d, ndirstotry, d1, dc; double dist; static int numdirstotry[] = { 1, 3, 6 }; static int dirstotry[] = { EAST, SOUTHEAST, NORTHEAST, NORTHEAST, SOUTHEAST, EAST }; static int da[] = { 0, -60, 60, 90, -30, 30 }; unsigned char *auxf_layer, dmask; if (!features_defined()) return; orient = min(orient, 2); ndirstotry = numdirstotry[orient]; for (f = 1; f <= nf; f++) { legend[f-1].ox = 0; legend[f-1].oy = 0; legend[f-1].dx = 0; legend[f-1].dy = 0; legend[f-1].angle = 0; legend[f-1].dist = -1; } /* Speedup: in auxf_layer we keep this information: the cell is unseen or hosts a blocking unit (bit 7); the cell has already been reached from direction id (bit id) [this avoids repeating the same path over and over; note that directions 3,4,5 zig-zag with step 3, so this bit is set/checked only every 3 steps.] */ auxf_layer = (unsigned char *) malloc(area.height * area.width * sizeof(unsigned char)); if (auxf_layer == NULL) return; for_all_cells(x, y) { if (terrain_seen_at(side, x, y) == NONTTYPE || blocking_utype(utype_seen_at(side, x, y), block)) { aset(auxf_layer, x, y, '\200'); } else { aset(auxf_layer, x, y, '\0'); } } for_all_cells(x, y) { f = raw_feature_at(x, y); if (f < 1 || f > nf) continue; for (j = 0; j < ndirstotry; j++) { dmask = '\001' << j; d = dirstotry[j]; d1 = ((j < 3) ? d : left_dir(left_dir(d))); x1 = x; y1 = y; dx = dy = 0; i3 = i = 0; dist = 0; while (raw_feature_at(x1, y1) == f && !(aref(auxf_layer, x1, y1) & ((j < 3 || !i3) ? ('\200' | dmask) : '\200'))) { if (dist > legend[f-1].dist && (j < 3 || !i3)) { legend[f-1].ox = x; legend[f-1].oy = y; legend[f-1].dx = dx; legend[f-1].dy = dy; legend[f-1].angle = da[j]; legend[f-1].dist = dist; } if (j < 3 || !i3) { auxf_layer[area.width * y1 + x1] |= dmask; } dc = ((i3 == 1) ? d1 : d); dx += dirx[dc]; x1 = wrapx(x1 + dirx[dc]); dy += diry[dc]; y1 += diry[dc]; dist += ((j < 3) ? 1.0 : (i3 ? 0.5 * 1.73205080756888 : 0.0)); ++i; i3 = i % 3; } } } free(auxf_layer); } static int blocking_utype(int u, int block) { if (u == NONUTYPE || block == 0) return 0; if (block > 1) return 1; /* block==1: only visible see-always unmovable units */ return ((u_already_seen(u) > 99 || u_see_always(u)) && !mobile(u)); } /* Set the spacing of the meridians of latitude and longitude. */ void set_meridian_interval(VP *vp, int interval) { vp->meridian_interval = interval; /* Don't want to compute the lat/lon label spacing yet, we don't necessarily have the window's size recorded in the viewport. */ vp->lat_label_lon_interval = vp->lon_label_lat_interval = 0; } /* Compute the location of meridians desired for the given viewport and render the lines and labels using the two callback functions. */ /* (should attempt to draw the lines all the way to the edge of the area, but formulas more complicated) */ void vp_latlong(VP *vp, int *lat1p, int *lon1p, int *lat2p, int *lon2p); void plot_meridians(VP *vp, void (*line_callback)(int x1, int y1, int x1f, int y1f, int x2, int y2, int x2f, int y2f), void (*text_callback)(int x1, int y1, int x1f, int y1f, char *str)) { int lat1, lon1, lat2, lon2, latmin, latmax, lonmin, lonmax, incr, lat, lon; int latmid, lonmid, xmid, ymid, xmidf, ymidf; int x1, y1, x2, y2, x1f, y1f, x2f, y2f; int sx1, sy1, sx2, sy2; int latdeg, latminu, londeg, lonminu; char minbuf[10]; incr = vp->meridian_interval; /* Draw only if the interval is not too small. */ xy_to_latlong(area.halfwidth, area.height - 2, 0, 0, &lat, &lon); latlong_to_xy(lat, lon, &x1, &y1, &x1f, &y1f); latlong_to_xy(lat - incr, lon, &x2, &y2, &x2f, &y2f); /* If a single interval down from the top middle of the area is off the map, then there won't be any lines to plot anyway, so OK to escape. */ if (!in_area(x2, y2)) return; xform_cell_fractional(vp, x1, y1, x1f, y1f, &sx1, &sy1); xform_cell_fractional(vp, x2, y2, x2f, y2f, &sx2, &sy2); /* Don't draw if lines would be really closely spaced. */ if (sy2 - sy1 < 20) return; /* Find the closest meridians that will be visible in the viewport. */ vp_latlong(vp, &lat1, &lon1, &lat2, &lon2); /* Round to even multiples of the meridian interval. */ latmin = (lat1 / incr) * incr; latmax = (lat2 / incr) * incr + incr; lonmin = (lon1 / incr) * incr; lonmax = (lon2 / incr) * incr + incr; if (lonmax <= lonmin) lonmax += 21600; vp_latlong(vp, &lat1, &lon1, &lat2, &lon2); /* Determine the spacing of the text labels. They should be far enough apart so that only one set of each is visible at any time, although it's more OK for two sets to appear than for none to be visible. */ /* This algorithm assumes that we first do a draw of the whole viewport before using smaller viewports for incremental updates. */ if (vp->lon_label_lat_interval == 0) { vp->lon_label_lat_interval = (abs(lat2 - lat1) / incr) * incr; if (vp->lon_label_lat_interval == 0) vp->lon_label_lat_interval = 1; vp->lat_label_lon_interval = (abs(lon2 - lon1) / incr) * incr; if (vp->lat_label_lon_interval == 0) vp->lat_label_lon_interval = 1; } /* Draw each line as an individual segment, because meridians may not be straight lines. */ for (lat = latmax; lat >= latmin; lat -= incr) { for (lon = lonmin; lon <= lonmax; lon += incr) { latlong_to_xy(lat, lon, &x1, &y1, &x1f, &y1f); if (in_area(x1, y1)) { latlong_to_xy(lat - incr, lon, &x2, &y2, &x2f, &y2f); if (line_callback) { if (in_area(x2, y2)) { (*line_callback)(x1, y1, x1f, y1f, x2, y2, x2f, y2f); } latlong_to_xy(lat, lon + incr, &x2, &y2, &x2f, &y2f); if (in_area(x2, y2)) { (*line_callback)(x1, y1, x1f, y1f, x2, y2, x2f, y2f); } } if (text_callback) { if (lon % vp->lat_label_lon_interval == 0) { lonmid = lon + incr / 2; latlong_to_xy(lat, lonmid, &xmid, &ymid, &xmidf, &ymidf); /* If adding the incr goes off the world, subtract instead. */ if (!in_area(xmid, ymid)) { lonmid = lon - incr / 2; latlong_to_xy(lat, lonmid, &xmid, &ymid, &xmidf, &ymidf); } /* Give up if this did not work. */ if (in_area(xmid, ymid)) { latdeg = abs(lat) / 60; latminu = abs(lat) % 60; minbuf[0] = '\0'; if (latminu != 0) sprintf(minbuf, "%dm", latminu); sprintf(tmpbuf, "%dd%s%c", latdeg, minbuf, (lat >= 0 ? 'N' : 'S')); (*text_callback)(xmid, ymid, xmidf, ymidf, tmpbuf); } } if (lat % vp->lon_label_lat_interval == 0) { latmid = lat + incr / 2; latlong_to_xy(latmid, lon, &xmid, &ymid, &xmidf, &ymidf); /* If adding the incr goes off the world, subtract instead. */ if (!in_area(xmid, ymid)) { latmid = lat - incr / 2; latlong_to_xy(latmid, lon, &xmid, &ymid, &xmidf, &ymidf); } /* If this position is valid, draw the longitude. */ if (in_area(xmid, ymid)) { londeg = abs(lon) / 60; lonminu = abs(lon) % 60; minbuf[0] = '\0'; if (lonminu != 0) sprintf(minbuf, "%dm", lonminu); sprintf(tmpbuf, "%dd%s%c", londeg, minbuf, (lon >= 0 ? 'E' : 'W')); (*text_callback)(xmid, ymid, xmidf, ymidf, tmpbuf); } } } #if 0 /* use to debug meridian plotting */ sprintf(tmpbuf, "%d %d", lat, lon); (*text_callback)(x1, y1, x1f, y1f, tmpbuf); latlong_desc(tmpbuf, x1, y1, x1f, y1f, 3); (*text_callback)(x1, y1, x1f, y1f, tmpbuf); #endif } } } } void vp_latlong(VP *vp, int *lat1p, int *lon1p, int *lat2p, int *lon2p) { int xmin, ymin, xmax, ymax, xminf, yminf, xmaxf, ymaxf; /* The implementation of nearest_cell still does the correct calculation, even if the pixel position is not in the area. */ nearest_cell(vp, 0, vp->pxh, &xmin, &ymin, &xminf, &yminf); nearest_cell(vp, vp->pxw, 0, &xmax, &ymax, &xmaxf, &ymaxf); xy_to_latlong(xmin, ymin, xminf, yminf, lat1p, lon1p); xy_to_latlong(xmax, ymax, xmaxf, ymaxf, lat2p, lon2p); } /* Use this to set the contour interval, or, by setting to 0, let it float to a plausible value based on the actual range of elevations. */ void set_contour_interval(VP *vp, int n) { int ncontours; if (n > 0) { /* Set contour interval, compute num contours from it. */ vp->contour_interval = n; ncontours = (area.maxelev - area.minelev) / vp->contour_interval; vp->contour_interval_fixed = TRUE; } else { /* Set num contours, compute contour interval. */ ncontours = max(1, min(15, area.maxelev - area.minelev)); vp->contour_interval = (area.maxelev - area.minelev) / ncontours; vp->contour_interval_fixed = FALSE; } if (ncontours != vp->num_contours) { vp->num_contours = ncontours; if (vp->linebuf != NULL) { free(vp->linebuf); vp->linebuf = NULL; } } } /* The theory of contour lines is that each hex can be considered as six triangles, each of which has a vertex at the center and two on adjacent corners of the hex. The elevation of the center vertex is the overall elevation of the cell, the elevations of the corners are averages with the adjacent cells. If a particular contour elevation is between any pair of vertex elevations, then the contour line must cross that side of the triangle - and one of the other two sides. We decide which of the two it is, interpolate to get the actual positions of each endpoint of the line segment, then draw it. */ void contour_lines_at(VP *vp, int x, int y, int sx, int sy, LineSegment **lines, int *numlinesp) { int el, el2, dir, x1, y1, sum, n, lowest, liq0, liq, ecor[NUMDIRS], ec; int maxel; int sxcor[NUMDIRS], sycor[NUMDIRS], sxc, syc; int power = vp->power; int ecorr, ecorl, sxcorr, sycorr, sxcorl, sycorl; int sx1, sy1, sx2, sy2; *numlinesp = 0; if (vp->contour_interval < 1) return; if (vp->linebuf == NULL) vp->linebuf = (LineSegment *) xmalloc ((vp->num_contours + 2) * 2 * NUMDIRS * sizeof(LineSegment)); *lines = vp->linebuf; /* It's possible that the contour intervals have not yet been adjusted to account for all elevations in the world, so be sure to clip. */ maxel = area.minelev + vp->num_contours * vp->contour_interval; el = elev_at(x, y); if (el > maxel) el = maxel; sxc = sx + vp->hw / 2; syc = sy + vp->hh / 2; /* Compute the elevation at each corner of the cell. */ liq0 = t_liquid(terrain_at(x, y)); for_all_directions(dir) { sum = el; n = 1; lowest = el; liq = liq0; if (point_in_dir(x, y, dir, &x1, &y1)) { el2 = elev_at(x1, y1); if (el2 > maxel) el2 = maxel; sum += el2; ++n; lowest = min(lowest, el2); if (t_liquid(terrain_at(x1, y1))) liq = TRUE; } if (point_in_dir(x, y, left_dir(dir), &x1, &y1)) { el2 = elev_at(x1, y1); if (el2 > maxel) el2 = maxel; sum += el2; ++n; lowest = min(lowest, el2); if (t_liquid(terrain_at(x1, y1))) liq = TRUE; } /* Pick lowest, in the case of liquids, or average. */ if (liq) ecor[dir] = lowest; else ecor[dir] = sum / n; sxcor[dir] = sx + bsx[power][dir]; sycor[dir] = sy + bsy[power][dir]; } /* Iterate over all the possible elevations for contour lines. */ for (ec = area.minelev + vp->contour_interval; ec < area.maxelev; ec += vp->contour_interval) { for_all_directions(dir) { ecorr = ecor[dir]; ecorl = ecor[left_dir(dir)]; sxcorr = sxcor[dir]; sycorr = sycor[dir]; sxcorl = sxcor[left_dir(dir)]; sycorl = sycor[left_dir(dir)]; if (el != ecorr && between(min(el, ecorr), ec, max(el, ecorr))) { if (el != ecorl && between(min(el, ecorl), ec, max(el, ecorl))) { sx1 = sxc + ((sxcorr - sxc) * (ec - el)) / (ecorr - el); sy1 = syc + ((sycorr - syc) * (ec - el)) / (ecorr - el); sx2 = sxc + ((sxcorl - sxc) * (ec - el)) / (ecorl - el); sy2 = syc + ((sycorl - syc) * (ec - el)) / (ecorl - el); if (sx1 != sx2 || sy1 != sy2) { vp->linebuf[*numlinesp].sx1 = sx1; vp->linebuf[*numlinesp].sy1 = sy1; vp->linebuf[*numlinesp].sx2 = sx2; vp->linebuf[*numlinesp].sy2 = sy2; ++(*numlinesp); } } else if (ecorl != ecorr) { sx1 = sxc + ((sxcorr - sxc) * (ec - el)) / (ecorr - el); sy1 = syc + ((sycorr - syc) * (ec - el)) / (ecorr - el); /* By inverting odd directions before the calculation we ensure that the endpoints are calculated in exactly the same way for the two line segments from adjacent cells that should end on the same point. This eliminates the small jumps in contour lines as they cross from one cell to another. */ /* Line ends on outer edge. */ if (dir == NORTHEAST || dir == SOUTHEAST || dir == WEST) { sx2 = sxcorr + ((sxcorl - sxcorr) * (ec - ecorr)) / (ecorl - ecorr); sy2 = sycorr + ((sycorl - sycorr) * (ec - ecorr)) / (ecorl - ecorr); } else { sx2 = sxcorl + ((sxcorl - sxcorr) * (ec - ecorl)) / (ecorl - ecorr); sy2 = sycorl + ((sycorl - sycorr) * (ec - ecorl)) / (ecorl - ecorr); } if (sx1 != sx2 || sy1 != sy2) { vp->linebuf[*numlinesp].sx1 = sx1; vp->linebuf[*numlinesp].sy1 = sy1; vp->linebuf[*numlinesp].sx2 = sx2; vp->linebuf[*numlinesp].sy2 = sy2; ++(*numlinesp); } } } if (el != ecorl && between(min(el, ecorl), ec, max(el, ecorl))) { if (ecorl != ecorr && between(min(ecorr, ecorl), ec, max(ecorr, ecorl))) { sx1 = sxc + ((sxcorl - sxc) * (ec - el)) / (ecorl - el); sy1 = syc + ((sycorl - syc) * (ec - el)) / (ecorl - el); /* Line ends on outer edge */ if (dir == NORTHEAST || dir == SOUTHEAST || dir == WEST) { sx2 = sxcorr + ((sxcorl - sxcorr) * (ec - ecorr)) / (ecorl - ecorr); sy2 = sycorr + ((sycorl - sycorr) * (ec - ecorr)) / (ecorl - ecorr); } else { sx2 = sxcorl + ((sxcorl - sxcorr) * (ec - ecorl)) / (ecorl - ecorr); sy2 = sycorl + ((sycorl - sycorr) * (ec - ecorl)) / (ecorl - ecorr); } if (sx1 != sx2 || sy1 != sy2) { vp->linebuf[*numlinesp].sx1 = sx1; vp->linebuf[*numlinesp].sy1 = sy1; vp->linebuf[*numlinesp].sx2 = sx2; vp->linebuf[*numlinesp].sy2 = sy2; ++(*numlinesp); } } } } } } int unit_visible(Side *side, VP *vp, Unit *unit) { if (vp->show_all) return TRUE; /* Designer needs to see all units, even when show_all has been turned off - otherwise cells with units look empty. */ if (is_designer(side)) return TRUE; return side_sees_unit(side, unit); } /* Test whether the occupants of the given unit are visible to the given side. */ int occupants_visible(Side *side, VP *vp, Unit *unit) { if (unit->occupant == NULL) return FALSE; if (vp->show_all) return TRUE; /* side_sees_unit means we may freely examine the unit since we control it, the game is over or side->see-all is true. Occs should therefore always be visible then. */ if (side_sees_unit(side, unit)) return TRUE; if (u_see_occupants(unit->type)) return TRUE; if (side_owns_occupant(side, unit)) return TRUE; return FALSE; } /* Don't draw the temperature in every cell, only do ones with even coords or ones where the temperature in any adjacent cell is different. */ int draw_temperature_here(Side *side, int x, int y) { int dir, x1, y1, temphere = temperature_view(side, x, y); /* Designers should see temperature in every cell. */ if (is_designer(side)) return TRUE; for_all_directions(dir) { if (interior_point_in_dir(x, y, dir, &x1, &y1)) { if (temphere != temperature_view(side, x1, y1)) return TRUE; /* Always show temperature around edge of known area. */ if (terrain_view(side, x1, y1) == UNSEEN) return TRUE; } } return (x % 2 == 0 && y % 2 == 0); } /* Don't draw the winds in every cell, only do ones with odd coords or ones where the wind in any adjacent cell is different. */ int draw_winds_here(Side *side, int x, int y) { int dir, x1, y1, windhere = wind_view(side, x, y); /* Designers should see wind in every cell. */ if (is_designer(side)) return TRUE; /* Don't draw wind in unseen areas. */ if (terrain_view(side, x, y) == UNSEEN) return FALSE; for_all_directions(dir) { if (interior_point_in_dir(x, y, dir, &x1, &y1)) { if (windhere != wind_view(side, x1, y1)) return TRUE; /* Always show wind around edge of known area. */ if (terrain_view(side, x1, y1) == UNSEEN) return TRUE; } } return (x % 2 == 1 && y % 2 == 1); } /* Return a textual description of what is at the given pixel in the given viewport. */ void oneliner(Side *side, VP *vp, int sx, int sy) { int x, y, xf, yf; Unit *unit, *unit2, *user; int t2, u, ps = NOBODY, cs = NOBODY, dep, sayin = FALSE, userid; char *peopdesc = NULL, *str; char descbuf[80], ctrlbuf[80], buf[BUFSIZE]; Side *side2, *side3; Feature *feature; UnitView *uview; nearest_cell(vp, sx, sy, &x, &y, &xf, &yf); nearest_unit(side, vp, sx, sy, &unit); nearest_unit_view(side, vp, sx, sy, &uview); if (!in_area(x, y)) { strcpy(tmpbuf, "(nothing)"); return; } else if (vp->show_all || terrain_visible(side, x, y)) { strcpy(tmpbuf, " "); /* Describe the side of the people here. */ if (people_sides_defined()) { ps = people_side_at(x, y); if (ps != NOBODY) { side2 = side_n(ps); if (side2 == NULL) { peopdesc = "indep"; } else if (side2 == side) { peopdesc = "your"; } else { peopdesc = side_adjective(side2); if (peopdesc[0] == '\0') { sprintf(descbuf, "s%d", side2->id); peopdesc = descbuf; } } } } if (control_sides_defined()) { cs = control_side_at(x, y); if (cs != ps) { side3 = side_n(cs); if (side3 == NULL) { strcpy(ctrlbuf, "uncontrolled"); } else if (side3 == side) { strcpy(ctrlbuf, "your"); } else { strcpy(ctrlbuf, side_adjective(side3)); if (ctrlbuf[0] == '\0') { sprintf(ctrlbuf, "s%d", side3->id); } strcat(ctrlbuf, "-controlled"); } if (peopdesc != NULL) { strcat(ctrlbuf, " "); strcat(ctrlbuf, peopdesc); } peopdesc = ctrlbuf; } } if (vp->show_all || (uview != NULL && view_side(uview) == side)) { if (unit != NULL) { if (unit->side != side) { if (ps != NOBODY && ps == side_number(unit->side)) { peopdesc = "own"; } } strcat(tmpbuf, unit_handle(side, unit)); sayin = TRUE; } } else { if (uview != NULL) { u = view_type(uview); side2 = view_side(uview); if (ps != NOBODY && ps == side2->id) { peopdesc = "own"; } /* Display the real name of a non-mobile unit, even if it's not in view coverage. */ /* (should fix this to use view stack) */ unit = NULL; for_all_stack(x, y, unit2) { if (unit2->type == u && (!mobile(u) || u_acp(u) == 0)) { unit = unit2; break; } } if (unit != NULL) { strcat(tmpbuf, apparent_unit_handle(side, unit, side2)); } else { strcat(tmpbuf, side_adjective(side2)); strcat(tmpbuf, " "); strcat(tmpbuf, u_type_name(u)); } sayin = TRUE; } } if (sayin) { strcat(tmpbuf, " (in "); } if (peopdesc != NULL) { strcat(tmpbuf, peopdesc); strcat(tmpbuf, " "); } if (vp->show_all) t2 = terrain_at(x, y); else t2 = vterrain(terrain_view(side, x, y)); strcat(tmpbuf, t_type_name(t2)); if (sayin) { strcat(tmpbuf, ")"); } /* (should be able to display views of these) */ if (elevations_defined()) { tprintf(tmpbuf, " Elev %d", elev_at(x, y)); } if (temperatures_defined()) { tprintf(tmpbuf, " T %ddeg", temperature_at(x, y)); } if (numcoattypes > 0) { for_all_terrain_types(t2) { if (t_is_coating(t2) && aux_terrain_defined(t2) && ((dep = aux_terrain_view(side, x, y, t2)) > 0)) { tprintf(tmpbuf, " %s %d", t_type_name(t2), dep); } } } } else { sprintf(tmpbuf, "(unknown)"); } strcat(tmpbuf, " @"); if (1 /* drawxy */) { tprintf(tmpbuf, "%d,%d", x, y); } else if (vp->draw_meridians) { latlong_desc(descbuf, x, y, xf, yf, 3); strcat(tmpbuf, descbuf); } if (vp->show_all || terrain_visible(side, x, y)) { feature = feature_at(x, y); if (feature != NULL) { if (feature->size > 0) { str = feature_desc(feature, buf); if (str != NULL) { strcat(tmpbuf, " ("); strcat(tmpbuf, str); strcat(tmpbuf, ")"); } } } } if (1 /* drawxy */ && vp->draw_meridians) { latlong_desc(descbuf, x, y, xf, yf, 3); strcat(tmpbuf, " ("); strcat(tmpbuf, descbuf); strcat(tmpbuf, ")"); } if (user_defined() && ((userid = user_at(x, y)) != NOUSER)) { user = find_unit(userid); if (in_play(user)) { strcat(tmpbuf, " (used by "); if (user->name != NULL) strcat(tmpbuf, user->name); else sprintf(tmpbuf+strlen(tmpbuf), "%s u#%d", u_type_name(user->type), user->id); strcat(tmpbuf, ")"); } } if (vp->draw_ai && side_has_ai(side)) { str = ai_at_desig(side, x, y); if (str) { strcat(tmpbuf, " "); strcat(tmpbuf, str); } } } /* (needs a better home?) */ /* Given a side and a unit, calculate the correct "next unit". Typically used by autonext options, thus the name. */ Unit * autonext_unit(Side *side, Unit *unit) { int i, uniti = -1, n; Unit *nextunit; if (!side->ingame || side->finishedturn || side->actionvector == NULL) return NULL; if (could_be_next_unit(unit) && side_controls_unit(side, unit)) return unit; for (i = 0; i < side->actionvector->numunits; ++i) { nextunit = unit_in_vector(side->actionvector, i); if (in_play(nextunit) && side_controls_unit(side, nextunit)) { if (unit == NULL || unit == nextunit) { uniti = i; break; } } } if (uniti < 0) return NULL; /* (should scan for both a preferred and an alternate - preferred could be within a supplied bbox so as to avoid scrolling) */ for (i = uniti; i < uniti + side->actionvector->numunits; ++i) { n = i % side->actionvector->numunits; nextunit = unit_in_vector(side->actionvector, n); if (could_be_next_unit(nextunit) && side_controls_unit(side, nextunit)) return nextunit; } return NULL; } /* * This should really be called autonext_unit and the decision * whether to check inbox or not should depend on the bbox being * valid. i.e. could be called with -1,-1,-1,-1 to disable the bbox. */ Unit * autonext_unit_inbox(Side *side, Unit *unit, VP *vp) { int i, u, mx, my, val, prefval = -999, v = 10; Unit *nextunit = NULL, *prefunit = NULL; if (!side->ingame || side->finishedturn || side->actionvector == NULL) return NULL; /* degenerate case... this unit still has stuff to do. */ if (could_be_next_unit(unit) && side_controls_unit(side, unit)) return unit; if (unit == NULL) { u = 0; if (!nearest_cell(vp, vp->sx + vp->pxw / 2, vp->sy + vp->pxh / 2, &mx, &my, NULL, NULL)) { mx = area.width / 2; my = area.halfheight; } } else { u = unit->type; mx = unit->x; my = unit->y; } for (i = 0; i < side->actionvector->numunits; ++i) { nextunit = unit_in_vector(side->actionvector, i); if (side_controls_unit(side, nextunit) && could_be_next_unit(nextunit)) { val = v - distance(nextunit->x, nextunit->y, mx, my); if (cell_is_in_middle(vp, nextunit->x, nextunit->y)) val += v; if (nextunit->type == u) val += 2; if (val > prefval) { prefval = val; prefunit = nextunit; } } } return prefunit; } int could_be_next_unit(Unit *unit) { return (unit != NULL && alive(unit) && inside_area(unit->x, unit->y) && has_acp_left(unit) && (unit->plan && !unit->plan->asleep && !unit->plan->reserve && !unit->plan->delayed && unit->plan->waitingfortasks)); } /* Do a depth-first traversal of all the occupants of a unit. */ Unit * find_next_occupant(Unit *unit) { Unit *nextup; if (unit->occupant != NULL) { return unit->occupant; } else if (unit->nexthere != NULL) { return unit->nexthere; } else { nextup = unit->transport; if (nextup != NULL) { while (nextup->transport != NULL && nextup->nexthere == NULL) { nextup = nextup->transport; } if (nextup->nexthere != NULL) return nextup->nexthere; if (nextup->transport == NULL) return nextup; } else { /* This is a no-op if there is no stacking within a hex. */ return unit_at(unit->x, unit->y); } } return unit; } int find_units_matching(Side *side, char *name, Unit **unitp) { int num = 0; Unit *unit; if (empty_string(name)) return 0; for_all_units(unit) { if (!empty_string(unit->name) && strstr(unit->name, name) && side_sees_image(side, unit)) { *unitp = unit; ++num; } } return num; } Unit * embarkation_unit(Unit *unit) { Unit *transport, *occ; /* look for the first possible transport */ for_all_stack(unit->x, unit->y, transport) { /* make sure its not the transport we're in and we can enter it */ if (transport != unit->transport && valid(check_enter_action(unit, unit, transport))) { return transport; } /* check the occupants too */ for_all_occupants(transport, occ) { if (occ != unit->transport && valid(check_enter_action(unit, unit, occ))) { return occ; } } } return NULL; } /* Given a character, compute the direction(s) that it represents. Return the number of directions. */ int char_to_dir(int ch, int *dir1p, int *dir2p, int *modp) { char basech, *rawdir; int ndirs = 0; if (isupper(ch)) { basech = tolower(ch); if (modp) *modp = 1; } else if (ch < ' ') { basech = ch + 0x60; if (modp) *modp = 2; } else { basech = ch; if (modp) *modp = 0; } rawdir = strchr(dirchars, basech); if (rawdir) { *dir1p = rawdir - dirchars; ndirs = 1; } else if (basech == 'k') { if (flip_coin()) { *dir1p = NORTHEAST; if (dir2p) *dir2p = NORTHWEST; } else { *dir1p = NORTHWEST; if (dir2p) *dir2p = NORTHEAST; } ndirs = 2; } else if (basech == 'j') { if (flip_coin()) { *dir1p = SOUTHEAST; if (dir2p) *dir2p = SOUTHWEST; } else { *dir1p = SOUTHWEST; if (dir2p) *dir2p = SOUTHEAST; } ndirs = 2; } return ndirs; } /* Given that the player desires to move the given unit into the given cell/other unit, prepare a "most appropriate" action. */ /* (should share diff cell and same cell interaction code) */ extern void toggle_user_at(Unit *unit, int x, int y); int advance_into_cell(Side *side, Unit *unit, int x, int y, Unit *other, HistEventType *reason) { int z, m, rslt = H_UNDEFINED; /* Make sure we have some default value. */ if (reason) *reason = rslt; #ifdef DESIGNERS /* Designers use this function to push units around, bound only by the limits on occupancy. */ if (is_designer(side)) return net_designer_teleport(unit, x, y, other); #endif /* DESIGNERS */ z = unit->z; if (x != unit->x || y != unit->y) { /* Units that can't act/plan can't do anything to adjacent units. */ if (unit->act == NULL || unit->plan == NULL) return FALSE; if (!mobile(unit->type) && u_advanced(unit->type) && distance(unit->x, unit->y, x, y) <= unit->reach) { toggle_user_at(unit, x, y); return TRUE; } if (distance(unit->x, unit->y, x, y) == 1) { /* Destination is adjacent to us. */ /* First, handle the case where no unit was clicked on. */ if (other == NULL) { rslt = check_move_action(unit, unit, x, y, z); if (valid(rslt)) { net_prep_move_action(unit, unit, x, y, z); return TRUE; } if (reason) *reason = rslt; if (can_extract_at(unit, x, y, &m)) { net_set_collect_task(unit, m, x, y); return TRUE; } return FALSE; } /* If a unit was clicked on, decide how to interact. */ if (unit_trusts_unit(unit, other) || (other->side == indepside && sides_allow_entry(unit, other))) { /* A friend, maybe get on it. */ if (can_occupy(unit, other)) { if (valid(check_enter_action(unit, unit, other))) { net_prep_enter_action(unit, unit, other); } else { /* (should schedule for next turn?) */ } } else if (can_occupy(other, unit)) { if (u_acp(other->type) > 0) { /* Have other unit do an enter action, then move. */ /* (not quite right, move should happen after other unit is actually inside, in case it fills dest) */ net_prep_enter_action(other, other, unit); net_set_move_to_task(unit, x, y, 0); } else { net_prep_enter_action(unit, other, unit); net_set_move_to_task(unit, x, y, 0); } } else if (other->transport != NULL && can_occupy(unit, other->transport)) { if (valid(check_enter_action(unit, unit, other->transport))) { net_prep_enter_action(unit, unit, other->transport); } else { /* (should schedule for next turn?) */ } } else if (other->transport != NULL && other->transport->transport != NULL && can_occupy(unit, other->transport->transport)) { /* two levels up should be sufficient */ if (valid(check_enter_action(unit, unit, other->transport->transport))) { net_prep_enter_action(unit, unit, other->transport->transport); } else { /* (should schedule for next turn?) */ } } else if (valid(check_transfer_part_action(unit, unit, unit->hp, other))) { net_prep_transfer_part_action(unit, unit, unit->hp, other); } else if (can_extract_at(unit, x, y, &m) || can_load_at(unit, x, y, &m)) { /* (should test extraction from specific unit) */ net_set_collect_task(unit, m, x, y); } else { rslt = check_move_action(unit, unit, x, y, z); if (valid(rslt)) { net_prep_move_action(unit, unit, x, y, z); return TRUE; } if (reason) *reason = rslt; return FALSE; } } else { /* Somebody else's unit, try to victimize it in various ways, trying coexistence only as a last resort. */ rslt = check_capture_action(unit, unit, other); if (valid(rslt)) { net_prep_capture_action(unit, unit, other); return TRUE; } rslt = check_overrun_action(unit, unit, x, y, z, 100); if (valid(rslt)) { net_prep_overrun_action(unit, unit, x, y, z, 100); return TRUE; } if (reason && rslt == A_ANY_NO_AMMO) *reason = rslt; rslt = check_attack_action(unit, unit, other, 100); if (valid(rslt)) { net_prep_attack_action(unit, unit, other, 100); return TRUE; } if (reason && rslt == A_ANY_NO_AMMO) *reason = rslt; rslt = check_fire_at_action(unit, unit, other, -1); if (valid(rslt)) { net_prep_fire_at_action(unit, unit, other, -1); return TRUE; } if (reason && rslt == A_ANY_NO_AMMO) *reason = rslt; rslt = check_detonate_action(unit, unit, x, y, z); if (valid(rslt)) { net_prep_detonate_action(unit, unit, x, y, z); return TRUE; } if (can_extract_at(unit, x, y, &m) || can_load_at(unit, x, y, &m)) { /* (should test extraction from specific unit) */ net_set_collect_task(unit, m, x, y); return TRUE; } rslt = check_move_action(unit, unit, x, y, z); if (valid(rslt)) { net_prep_move_action(unit, unit, x, y, z); return TRUE; } /* None of the possible actions worked, so fail. */ return FALSE; } } else { /* We're not adjacent to the destination; actions alone won't suffice. */ if (can_extract_at(unit, x, y, &m) || can_load_at(unit, x, y, &m)) { net_set_collect_task(unit, m, x, y); } else if (mobile(unit->type)) { /* Although it's theoretically possible for a non-mobile unit to fulfill a move task by being carried all the way, in practice players are going to direct the transport to make the move, not its immobile occupants; so only allow mobile units and beep the player for trying to move a non-mobile unit. */ net_set_move_to_task(unit, x, y, 0); } else { return FALSE; } } } else { /* Destination is in the unit's own cell. */ if (other != NULL) { if (unit_trusts_unit(unit, other)) { if (valid(check_transfer_part_action(unit, unit, unit->hp, other))) { net_prep_transfer_part_action(unit, unit, unit->hp, other); } else if (valid(check_enter_action(unit, unit, other))) { net_prep_enter_action(unit, unit, other); } else { return FALSE; } } else { /* Somebody else's unit, try to victimize it in various ways, trying coexistence only as a last resort. */ if (valid(check_capture_action(unit, unit, other))) { net_prep_capture_action(unit, unit, other); } else if (valid(check_attack_action(unit, unit, other, 100))) { net_prep_attack_action(unit, unit, other, 100); } else if (valid(check_fire_at_action(unit, unit, other, -1))) { net_prep_fire_at_action(unit, unit, other, -1); } else if (valid(check_detonate_action(unit, unit, x, y, z))) { net_prep_detonate_action(unit, unit, x, y, z); } else { return FALSE; } } } else if (unit->transport != NULL) { /* Unit is an occupant wanting to leave, but yet remain in the same cell as the transport. */ rslt = check_move_action(unit, unit, x, y, z); if (valid(rslt)) { net_prep_move_action(unit, unit, x, y, z); return TRUE; } if (reason) *reason = rslt; return FALSE; } else { /* This is a no-op, don't do anything. */ } } /* All the failures got filtered out by early returns, so anything that got here is a success. */ return TRUE; } /* For an advanced unit, toggle its use of the given cell. */ void toggle_user_at(Unit *unit, int x, int y) { Unit *unit2; Side *side2; /* Return if landuse is undefined. */ if (!user_defined()) return; /* Return if the cell is not visible to this side. */ if (!terrain_visible(unit->side, x, y)) return; /* Return if the cell is used by another unit. */ if (user_at(x, y) != NOUSER && user_at(x, y) != unit->id) { /* (could be more helpful) */ notify(unit->side, "Cell at %d,%d is already in use", x, y); return; } /* Return if using maxcells and we are trying to add one more. */ if (unit->usedcells >= unit->maxcells && user_at(x, y) != unit->id) { notify(unit->side, "Cannot use any more cells"); return; } /* Return if independents or untrusted side has a unit in the cell. */ if (unit_at(x, y) != NULL) { for_all_stack(x, y, unit2) { if (!trusted_side(unit->side, unit2->side)) return; } } /* Toggle landuse by unit either on or off for the cell. */ if (user_at(x, y) == NOUSER) { set_user_at(x, y, unit->id); unit->usedcells += 1; } else if (user_at(x, y) == unit->id) { set_user_at(x, y, NOUSER); unit->usedcells -= 1; } for_all_sides(side2) { if (side2->see_all || side_sees_unit(side2, unit) || side_tracking_unit(side2, unit)) { update_cell_display(side2, x, y, UPDATE_ALWAYS); } } } /* Given a unit and amounts of supplies desired to transfer, move as much of them as possible into the unit's transport, or to a nearby units if there is no transport. */ static int give_supplies_to_one(Unit *unit, Unit *unit2, short *amts, short *rslts); Unit * give_supplies(Unit *unit, short *amts, short *rslts) { int dir, x1, y1, didsome; Unit *unit2; unit2 = unit->transport; if (unit2 != NULL) { didsome = give_supplies_to_one(unit, unit2, amts, rslts); if (didsome) return unit2; } for_all_occs_with_occs(unit, unit2) { /* Note that we might have enemy spies as occupants, so test relationship even here. */ if (unit_trusts_unit(unit, unit2)) { didsome = give_supplies_to_one(unit, unit2, amts, rslts); if (didsome) return unit2; } } for_all_stack(unit->x, unit->y, unit2) { if (unit2 != unit && unit_trusts_unit(unit, unit2)) { didsome = give_supplies_to_one(unit, unit2, amts, rslts); if (didsome) return unit2; } } /* Look around for adjacent units to give to. */ for_all_directions(dir) { if (interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) { for_all_stack(x1, y1, unit2) { if (unit_trusts_unit(unit, unit2)) { didsome = give_supplies_to_one(unit, unit2, amts, rslts); if (didsome) return unit2; } } } } /* Now try again on adjacent occupants. We do it this way so top-level adjacent units get tried before occupants, since for_all_stack_with_occs iterates depth-first. */ for_all_directions(dir) { if (interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) { for_all_stack_with_occs(x1, y1, unit2) { if (unit_trusts_unit(unit, unit2)) { didsome = give_supplies_to_one(unit, unit2, amts, rslts); if (didsome) return unit2; } } } } return NULL; } /* Given a pair of units, have one give the desired amounts to the other. */ static int give_supplies_to_one(Unit *unit, Unit *unit2, short *amts, short *rslts) { int m, gift, maxgift, actual, didsome = FALSE; if (!(in_play(unit2) && completed(unit2))) return FALSE; for_all_material_types(m) { if (rslts) rslts[m] = 0; maxgift = min(unit->supply[m], um_storage_x(unit2->type, m) - unit2->supply[m]); gift = ((amts == NULL || amts[m] == -1) ? (maxgift / 2) : amts[m]); if (gift > 0) { if (1 /* can do immed transfer */) { /* Be stingy if giver is low */ if (2 * unit->supply[m] < um_storage_x(unit->type, m)) gift = max(1, gift / 2); actual = transfer_supply(unit, unit2, m, gift); if (rslts) rslts[m] = actual; if (actual > 0) didsome = TRUE; } } } if (didsome) { /* Recompute the supply_is_low flags for both unit and unit2. */ if (unit->plan != NULL && !unit->plan->supply_is_low && past_halfway_point(unit) ) { unit->plan->supply_is_low = TRUE; update_unit_display(unit->side, unit, TRUE); } if (unit2->plan != NULL && unit2->plan->supply_is_low && !past_halfway_point(unit2) ) { unit2->plan->supply_is_low = FALSE; update_unit_display(unit2->side, unit2, TRUE); } } return didsome; } /* Attempt to transfer the given amounts of material from the unit's transport into the unit. */ int take_supplies(Unit *unit, short *amts, short *rslts) { int m, want, actual, neededsome; Unit *unit2; neededsome = FALSE; for_all_material_types(m) { if (rslts) rslts[m] = 0; want = ((amts == NULL || amts[m] == -1) ? (um_storage_x(unit->type, m) - unit->supply[m]) : amts[m]); if (want > 0) { neededsome = TRUE; unit2 = unit->transport; if (in_play(unit2) && completed(unit2)) { /* Bug fix. Units inside a transport inside a base or city were previously unable to resupply directly from the base. Now the occ checks if the transport is inside a base and then resupplies directly from it. Moreover, the 'stingy' 50% left principle is applied only to units that need m themselves. Other units are free to give away all their supply. */ Unit *base = unit2->transport; int request = want; /* First take as much supplies as possible from the transport's transport (the base) if it exists. */ actual = 0; if (in_play(base) && completed(base)) { /* Be stingy if base is low (but only if it also needs m). */ if (2 * base->supply[m] < um_storage_x(base->type, m) && (um_base_consumption(base->type, m) > 0 || um_consumption_per_move(base->type, m) > 0)) { request = max(1, want/2); } actual += transfer_supply(base, unit, m, request); want -= actual; } /* If still unsatisfied, also take supplies from transport. */ if (want) { request = want; /* Be stingy if transport is low (but only if it also needs m). */ if (2 * unit2->supply[m] < um_storage_x(unit2->type, m) && (um_base_consumption(unit2->type, m) > 0 || um_consumption_per_move(unit2->type, m) > 0)) { request = max(1, want/2); } actual += transfer_supply(unit2, unit, m, want); } if (rslts) rslts[m] = actual; } } } if (neededsome) { /* Recompute the supply_is_low flags for both unit and unit2. */ if (unit2 != NULL && unit2->plan != NULL && !unit2->plan->supply_is_low && past_halfway_point(unit2) ) { unit2->plan->supply_is_low = TRUE; update_unit_display(unit2->side, unit2, TRUE); } if (unit->plan != NULL && unit->plan->supply_is_low && !past_halfway_point(unit) ) { unit->plan->supply_is_low = FALSE; update_unit_display(unit->side, unit, TRUE); } } return neededsome; } /* Return the type to build that dialogs should highlight initially. */ int favored_type(Unit *unit) { int u; if (unit == NULL) return NONUTYPE; if (unit->plan && unit->plan->tasks && unit->plan->tasks->type == TASK_BUILD) return unit->plan->tasks->args[0]; for_all_unit_types(u) { if (uu_acp_to_create(unit->type, u) > 0 && side_can_build(unit->side, u)) return u; } return NONUTYPE; } ImageFamily * get_unit_type_images(Side *side, int u) { char *name; ImageFamily *imf; if (!empty_string(u_image_name(u))) name = u_image_name(u); else name = u_internal_name(u); imf = get_generic_images(name); if (imf != NULL && imf->numsizes == 0) { imf->ersatz = TRUE; imf = add_default_unit_image(imf, u); } record_imf_get(imf); return imf; } static ImageFamily * add_default_unit_image(ImageFamily *imf, int u) { int i, hi, lo; Image *img; img = get_img(imf, 16, 16); if (img == NULL) return imf; img->rawmonodata = xmalloc(32); img->rawmaskdata = xmalloc(32); hi = u >> 4; lo = (u & 0xf) << 4; for (i = 4; i < 28; i += 2) { (img->rawmonodata)[i] = hi; (img->rawmonodata)[i + 1] = lo; (img->rawmaskdata)[i] = 0x7f; (img->rawmaskdata)[i + 1] = 0xfe; } (img->rawmonodata)[14] = 0x1f; (img->rawmonodata)[15] = 0xf8; (img->rawmonodata)[16] = 0x1f; (img->rawmonodata)[17] = 0xf8; (img->rawmaskdata)[2] = 0x7f; (img->rawmaskdata)[3] = 0xfe; (img->rawmaskdata)[28] = 0x7f; (img->rawmaskdata)[29] = 0xfe; if (imf_interp_hook) imf = (*imf_interp_hook)(imf, NULL, FALSE); return imf; } /* Acquire all imagery relating to the given material type. */ ImageFamily * get_material_type_images(Side *side, int m) { char *name; ImageFamily *imf; if (!empty_string(m_image_name(m))) name = m_image_name(m); else name = m_type_name(m); imf = get_generic_images(name); if (imf != NULL && imf->numsizes == 0) { imf->ersatz = TRUE; imf = add_default_material_image(imf, m); } record_imf_get(imf); return imf; } /* The default material image is ugly but functional; basically a binary encoding of the material type number. Also add a shade of gray, for use when solid colors are wanted. */ static ImageFamily * add_default_material_image(ImageFamily *imf, int m) { int gray; Image *img; img = get_img(imf, 8, 8); img->istile = TRUE; img->rawmonodata = xmalloc(8); (img->rawmonodata)[1] = (m << 2); (img->rawmonodata)[2] = (m << 2); (img->rawmonodata)[3] = 0x7e; (img->rawmonodata)[4] = (m << 2); (img->rawmonodata)[5] = (m << 2); /* Also add a shade of gray. */ img = get_img(imf, 1, 1); img->istile = TRUE; /* Range from dark to light gray. */ gray = 15000 + ((m * 45000) / nummtypes); img->palette = cons(cons(new_number(0), cons(new_number(gray), cons(new_number(gray), cons(new_number(gray), lispnil)))), lispnil); if (imf_interp_hook) imf = (*imf_interp_hook)(imf, NULL, FALSE); return imf; } /* Acquire all imagery relating to the given terrain type. */ ImageFamily * get_terrain_type_images(Side *side, int t) { char *name; ImageFamily *imf; if (!empty_string(t_image_name(t))) name = t_image_name(t); else name = t_type_name(t); imf = get_generic_images(name); if (imf != NULL && imf->numsizes == 0) { imf->ersatz = TRUE; imf = add_default_terrain_image(imf, t); } record_imf_get(imf); return imf; } /* The default terrain image is ugly but functional; basically a binary encoding of the terrain type number. */ static ImageFamily * add_default_terrain_image(ImageFamily *imf, int t) { Image *img; img = get_img(imf, 8, 8); img->istile = TRUE; img->rawmonodata = xmalloc(8); (img->rawmonodata)[1] = (t << 2); (img->rawmonodata)[2] = (t << 2); (img->rawmonodata)[3] = 0x7e; (img->rawmonodata)[4] = (t << 2); (img->rawmonodata)[5] = (t << 2); if (imf_interp_hook) imf = (*imf_interp_hook)(imf, NULL, FALSE); return imf; } ImageFamily * get_unseen_images(Side *side) { if (!empty_string(g_unseen_color())) { unseen_image = get_generic_images(g_unseen_color()); if (unseen_image != NULL && unseen_image->numsizes == 0) { /* Appears to have failed - clear the unseen image then. */ unseen_image = NULL; /* Note that we shouldn't try to free the imf, because it may be in use elsewhere. */ } } record_imf_get(unseen_image); return unseen_image; } ImageFamily * get_emblem_images(Side *side, Side *side2) { char *s, *c, *name, tmpbuf[BUFSIZE]; int s2 = side_number(side2); ImageFamily *imf; if (side2 == NULL) { name = "s0"; } else if (!empty_string(side2->emblemname)) { name = side2->emblemname; } else if (!empty_string(side2->colorscheme)) { /* Take the first (main) color if there are multiple colors. */ for (s = side2->colorscheme, c = tmpbuf;; ++s) { /* Test for end of name or end of scheme. */ if (*s == ',' || *s == '\0') { /* Terminate the name string. */ *c = '\0'; c = tmpbuf; break; /* Add one more character. */ } else *c++ = *s; } name = copy_string(tmpbuf); /* Make sure the color emblemname is set so that it is saved correctly. */ net_set_side_emblemname(side, side2, name); } else { /* There is a set of default emblems named "s1", etc. */ sprintf(tmpbuf, "s%d", s2); name = copy_string(tmpbuf); /* Make sure the default emblemname is set so that it is saved correctly. */ net_set_side_emblemname(side, side2, name); } imf = get_generic_images(name); /* If we must have an image, and none were acquired, invoke the default image getter. */ if (imf != NULL && imf->numsizes == 0 && strcmp(name, "none") != 0) { imf->ersatz = TRUE; imf = add_default_emblem_image(imf, s2); } record_imf_get(imf); return imf; } /* For a substitute emblem, make a solid white square with stripes at top and bottom, with the encoding of the side number in the middle. (In practice, the number emblems usually get used.) */ static ImageFamily * add_default_emblem_image(ImageFamily *imf, int s2) { int i; Image *img; img = get_img(imf, 8, 8); if (img == NULL) return imf; img->rawmonodata = xmalloc(8); img->rawmaskdata = xmalloc(8); for (i = 0; i < 8; ++i) { (img->rawmonodata)[i] = s2; (img->rawmaskdata)[i] = 0xff; } (img->rawmonodata)[0] = 0xff; (img->rawmonodata)[7] = 0xff; if (imf_interp_hook) imf = (*imf_interp_hook)(imf, NULL, FALSE); return imf; } /* Record that the given image family was used. This is used when saving a game, for instance. */ void record_imf_get(ImageFamily *imf) { int i; ImageFamily **new_record; if (imf == NULL) return; /* Estimate and allocate the usual amount of space needed. */ if (max_recorded_imfs == 0) max_recorded_imfs = numutypes + numttypes + (MAXSIDES + 1) + 1; if (recorded_imfs == NULL) { recorded_imfs = (ImageFamily **) xmalloc(max_recorded_imfs * sizeof(ImageFamily *)); } /* Allocate more space if needed. */ if (num_recorded_imfs >= max_recorded_imfs) { max_recorded_imfs += max_recorded_imfs / 2; new_record = (ImageFamily **) xmalloc(max_recorded_imfs * sizeof(ImageFamily *)); for (i = 0; i < num_recorded_imfs; ++i) { new_record[i] = recorded_imfs[i]; } recorded_imfs = new_record; } for (i = 0; i < num_recorded_imfs; ++i) { if (strcmp(imf->name, recorded_imfs[i]->name) == 0) return; } recorded_imfs[num_recorded_imfs++] = imf; /* Expand any interface-specific data into its all-interface form, so that saved games will include it. This needs to be done now, because game saving may occur in a low-memory situation and there may not be enough memory available then. */ make_generic_image_data(imf); } /* Output a general description of an image family. */ void describe_imf(Side *side, char *classname, char *typename, ImageFamily *imf) { Image *img; if (imf == NULL) { DGprintf("No image family for %s %s for %s", classname, typename, side_desig(side)); return; } DGprintf("%s %s family for %s has %d images", classname, typename, side_desig(side), imf->numsizes); if (imf->location) DGprintf(" and is in %s", imf->location->name); DGprintf("\n"); for_all_images(imf, img) { DGprintf(" %dx%d", img->w, img->h); if (img->istile) DGprintf(" tile"); if (img->isconnection) DGprintf(" connection"); if (img->isborder) DGprintf(" border"); if (img->istransition) DGprintf(" transition"); if (img->numsubimages > 0) DGprintf(" %d subimages", img->numsubimages); if (imf_describe_hook) (*imf_describe_hook)(side, img); DGprintf("\n"); } } /* Compute and cache single-char representations for things. */ void init_ui_chars(void) { int u, t; char *str; if (unitchars == NULL) { unitchars = xmalloc(numutypes); for_all_unit_types(u) { str = u_uchar(u); unitchars[u] = (!empty_string(str) ? str[0] : utype_name_n(u, 1)[0]); } } if (terrchars == NULL) { terrchars = xmalloc(numttypes); for_all_terrain_types(t) { str = t_char(t); terrchars[t] = (!empty_string(str) ? str[0] : t_type_name(t)[0]); } } unseen_char_1 = unseen_char_2 = ' '; str = g_unseen_char(); if (strlen(str) >= 1) { unseen_char_1 = unseen_char_2 = str[0]; if (strlen(str) >= 2) { unseen_char_2 = str[1]; } } } /* Write the side's view of the world, as ASCII. */ /* (should be intelligent enough to cut into pages, or else document how to do it) */ /* (maybe display names too somehow, perhaps as second layer?) */ #define VIEWFILE "view.ccq" void dump_text_view(Side *side, int use_both_chars) { char ch1, ch2; int x, y, t, u, s, draw, i; Side *side2; Unit *unit; UnitView *uview; FILE *fp; fp = open_file(VIEWFILE, "w"); if (fp != NULL) { for (y = area.height-1; y >= 0; --y) { for (i = 0; i < y; ++i) fputc(' ', fp); for (x = 0; x < area.width; ++x) { ch1 = ch2 = ' '; if (in_area(x, y) && terrain_visible(side, x, y)) { t = terrain_at(x, y); ch1 = terrchars[t]; ch2 = (use_both_chars ? ch1 : ' '); draw = FALSE; if (side->see_all) { unit = unit_at(x, y); if (unit != NULL) { u = unit->type; s = side_number(unit->side); draw = TRUE; } } else { uview = unit_view_at(side, wrapx(x), y); if (uview != NULL) { u = view_type(uview); s = view_side(uview)->id; draw = TRUE; } } if (draw) { ch1 = unitchars[u]; ch2 = ' '; if (between(1, s, 9)) ch2 = s + '0'; else if (s >= 10) /* This could get weird if s > 36, but not much chance of that because MAXSIDES < 31 always. */ ch2 = s - 10 + 'A'; } } fputc(ch1, fp); fputc(ch2, fp); } fprintf(fp, "\n"); } fprintf(fp, "\n\nTerrain Types:\n"); for_all_terrain_types(t) { fprintf(fp, " %c%c %s\n", terrchars[t], terrchars[t], t_type_name(t)); } fprintf(fp, "\n\nUnit Types:\n"); for_all_unit_types(u) { fprintf(fp, " %c %s\n", unitchars[u], u_type_name(u)); } fprintf(fp, "\n\nSides:\n"); for_all_sides(side2) { fprintf(fp, " %d %s\n", side_number(side2), side_name(side2)); } fclose(fp); notify(side, "Dumped area view to \"%s\".", VIEWFILE); } else { notify(side, "Can't open \"%s\"!!", VIEWFILE); } } /* Return the type of cell terrain that the given side sees at the given location. */ int terrain_seen_at(Side *side, int x, int y) { if (in_area(x, y) && (side->see_all #ifdef DESIGNERS || side->designer #endif /* DESIGNERS */ || terrain_view(side, x, y) != UNSEEN)) { return terrain_at(x, y); } else { return NONTTYPE; } } /* (should remove both of these, only used in ps.c currently) */ /* Return the unit seen by the given side at the given location. Note that this should not be used casually by interfaces, since the result is a pointer to a real unit, not a view of one. */ /* (should result depend on contents of stack?) */ Unit * unit_seen_at(Side *side, int x, int y) { if (!in_area(x, y)) return NULL; if (side->see_all #ifdef DESIGNERS || side->designer #endif /* DESIGNERS */ || cover(side, x, y) > 0) return unit_at(x, y); return NULL; } int utype_seen_at(Side *side, int x, int y) { Unit *unit; #if 0 UnitView *uview; #endif if (!in_area(x, y)) return NONUTYPE; unit = unit_seen_at(side, x, y); if (unit) return unit->type; #if 0 /* for now */ uview = unit_view_at(side, x, y); if (uview != NULL) return view_type(uview); #endif return NONUTYPE; }