/* Sides in Xconq. Copyright (C) 1987-1989, 1991-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 implements sides and functionality relating to sides in general. */ #include "conq.h" #include "kernel.h" #define checked_elev_at(x, y) (elevations_defined() ? elev_at(x, y) : 0) static void init_visible_elevation(int x, int y); static void init_visible_elevation_2(int x, int y); static void calc_visible_elevation(int x, int y); static void calc_visible_elevation_2(int x, int y); static void cover_area_1(Side *side, struct a_unit *unit, int x0, int y0, int x1, int y1); static int see_materials(Side *side, int x, int y); static int see_weather(Side *side, int x, int y); static int mistaken_type(int u2); static int remove_unit_view(Side *side, UnitView *olduview); #define VIEW_HASH_SIZE 257 /* Head of the list of all sides. */ Side *sidelist; /* Pointer to the side representing independent units. */ Side *indepside; /* Pointer to the last side of the list. */ Side *lastside; /* Temporary used in many places. */ Side *tmpside; /* The actual number of sides in a game. This number never decreases; sides no longer playing need to be around for recordskeeping purposes. */ int numsides; /* This is the number of sides including indepside. We need it for the AI assignment in init.c. We want to keep numsides referring to normal sides only, since it is used a lot in the interface code, where we typically don't want to include indepside. */ int numtotsides; /* Used to generate the id number of the side. */ int nextsideid; /* Used for solid color emblems. These colors have been carefully tested so that they stand out against most backgrounds and also can be easily distinguished from each other. The indepside default emblem is set to "none" since it usually goes without emblem. */ static char *default_emblems[16] = { /* Current MAXSIDES + 1. */ "none", "blue", "red", "yellow", "chartreuse", "dark-orange", "magenta", "cyan", "pink", "purple", "medium-sea-green", "rosy-brown", "cornflower-blue", "firebrick", "white", "goldenrod" }; /* Cached values of global vision vars. */ short any_los = -1; int any_material_views = -1; char *any_material_views_by_m; /* Pointer to buffer used for readable side description (for debugging). */ char *sidedesigbuf = NULL; /* Pointer to the head of the list of players. */ Player *playerlist; /* Pointer to the last player of the list, used to add players at end. */ Player *last_player; /* The total number of players. */ int numplayers; /* Use to generate the id number of each player. */ int nextplayerid; char *playerdesigbuf = NULL; /* The list of doctrine objects. */ Doctrine *doctrine_list; Doctrine *last_doctrine; int next_doctrine_id; /* Init side machinery. Don't fill in indepside yet since numutypes etc. may not be defined. */ void init_sides(void) { /* Set up the list of sides. */ sidelist = lastside = (Side *) xmalloc(sizeof(Side)); /* The independent units' side will be the first one in the list, is always created. It cannot be filled in yet, because the numbers of the various types is not yet known. */ indepside = sidelist; /* Note: indepside is included in numtotsides, but not numsides. */ numtotsides = 0; numsides = 0; nextsideid = 0; /* Set up the list of players. */ playerlist = last_player = NULL; numplayers = 0; /* Indepside also needs a player, so we now start with id 0. */ nextplayerid = 0; /* Set up the player/side assignment array. */ assignments = (Assign *) xmalloc(MAXSIDES * sizeof(Assign)); /* Set up the list of doctrines. */ doctrine_list = last_doctrine = NULL; /* Doctrine ids must start at 1. */ next_doctrine_id = 1; } /* Create an object representing a side. */ Side * create_side(void) { int u, m; Side *newside; if (numsides >= g_sides_max()) { run_error("Cannot have more than %d sides total!", g_sides_max()); } /* Just fill in the names if dealing with indepside. */ if (nextsideid == 0) { newside = indepside; newside->name = "Independents"; newside->noun = "Independent"; newside->pluralnoun = "Independents"; newside->adjective = "independent"; newside->colorscheme = "black,black,white"; newside->emblemname = "none"; } else { /* Else allocate a new side object and increase numsides. */ newside = (Side *) xmalloc(sizeof(Side)); /* numsides does not include indepside, thus increment it here. */ ++numsides; } /* Fill in various side slots. Only those with non-zero/non-NULL defaults need have anything done to them. */ newside->id = nextsideid++; /* Always start sides IN the game. */ newside->ingame = TRUE; /* Note that "everingame" is only set at the beginning of a turn. */ /* Set up the relationships with other sides. */ newside->knows_about = ALLSIDES; /* for now */ newside->trusts = (short *) xmalloc((g_sides_max() + 1) * sizeof(short)); newside->trades = (short *) xmalloc((g_sides_max() + 1) * sizeof(short)); /* Set up per-unit-type slots. */ newside->counts = (short *) xmalloc(numutypes * sizeof(short)); newside->tech = (short *) xmalloc(numutypes * sizeof(short)); newside->inittech = (short *) xmalloc(numutypes * sizeof(short)); newside->numunits = (short *) xmalloc(numutypes * sizeof(short)); newside->emblemname = default_emblems[newside->id]; for_all_unit_types(u) { /* Start unit numbering at 1, not 0. */ newside->counts[u] = 1; } newside->priority = -1; /* All sides should auto-finish by default. */ newside->autofinish = TRUE; /* True by default, players should disable manually. */ newside->willingtosave = TRUE; /* Put valid Lisp data into slots that need it. */ newside->symbol = lispnil; newside->instructions = lispnil; newside->rawscores = lispnil; newside->aidata = lispnil; newside->uidata = lispnil; newside->possible_units = lispnil; newside->startx = newside->starty = -1; newside->init_center_x = newside->init_center_y = -1; newside->gaincounts = (short *) xmalloc(numutypes * num_gain_reasons * sizeof(short)); newside->losscounts = (short *) xmalloc(numutypes * num_loss_reasons * sizeof(short)); newside->atkstats = (long **) xmalloc(numutypes * sizeof(long *)); newside->hitstats = (long **) xmalloc(numutypes * sizeof(long *)); /* Necessary to enable AI control toggling of selected units! */ newside->prefixarg = -1; if (numatypes > 0) { newside->advance = (short *) xmalloc(numatypes * sizeof(short)); newside->canresearch = (short *) xmalloc(numatypes * sizeof(short)); update_canresearch_vector(newside); newside->research_topic = NOADVANCE; newside->autoresearch = FALSE; } newside->canbuild = (short *) xmalloc(numutypes * sizeof(short)); /* We can't update the canbuild vector yet, because its contents may depend on side class, which isn't set yet. */ if (nummtypes > 0) { /* Set up the side's supply of materials. */ newside->treasury = (short *) xmalloc(nummtypes * sizeof(short)); for_all_material_types(m) if (side_has_treasury(newside, m)) newside->treasury[m] = m_initial_treasury(m); /* Make space for default conversions and copy them over. */ newside->c_rates = (short *) xmalloc(nummtypes * sizeof(short)); { int m1, m2; /* First initilaize m. */ m = NONMTYPE; /* For now, assume only one m. */ for_all_material_types(m1) { for_all_material_types(m2) { if (mm_conversion(m1, m2) > 0) { m = m1; break; } } } if (m != NONMTYPE) { for_all_material_types(m2) { newside->c_rates[m2] = mm_conversion_default(m, m2); } } } } /* Link in at the end of the list of sides. */ newside->next = NULL; /* Important to avoid the indepside->next = indepside loop! */ if (newside != indepside) { lastside->next = newside; lastside = newside; } init_side_unithead(newside); /* numtotsides also includes indepside. */ ++numtotsides; return newside; } /* To make the double links work, we have to have one pseudo-unit to serve as a head. This unit should not be seen by any outside code. */ void init_side_unithead(Side *side) { if (side->unithead == NULL) { side->unithead = create_bare_unit(0); side->unithead->next = side->unithead; side->unithead->prev = side->unithead; } } /* Quick check to see if we have units. */ /* This should be improved to not be fooled by dead units? */ int side_has_units(Side *side) { return (side->unithead != NULL && (side->unithead->next != side->unithead)); } /* Set up doctrine structures. */ void init_doctrine(Side *side) { int u; /* Every side must have a default doctrine to fall back on. */ if (side->default_doctrine == NULL) { side->default_doctrine = new_doctrine(0); } /* Make each individual unit doctrine point to the generic doctrine by default. */ if (side->udoctrine == NULL) { side->udoctrine = (Doctrine **) xmalloc(numutypes * sizeof(Doctrine *)); } for_all_unit_types(u) { if (side->udoctrine[u] == NULL) side->udoctrine[u] = side->default_doctrine; } } void init_self_unit(Side *side) { Unit *unit; if ((g_self_required() /* || side prop? */) && side->self_unit == NULL) { for_all_side_units(side, unit) { if (u_can_be_self(unit->type) && in_play(unit) && completed(unit)) side->self_unit = unit; } } } /* Initialize basic viewing structures for a side. This happens when the side is created (during/after reading but before synthesis). At this time we're mostly allocating space; filling in the views happens once all the units are in position. We don't know the final values of parameters like side->see_all, so we have to allocate some data that might never be used. */ int init_view(Side *side) { int terrainset = FALSE, t, u, m; /* Be sure that we're not trying to set this up too soon. */ check_area_shape(); /* Allocate terrain view layers. */ if ((!g_terrain_seen() || !g_see_terrain_always()) && side->terrview == NULL) { side->terrview = malloc_area_layer(char); if (!g_see_terrain_always()) { side->terrviewdate = malloc_area_layer(short); /* The terrview also holds the "have we seen it" flag, so it must always be allocated, but the aux terrain view need not be. */ if (numcelltypes < numttypes) { side->auxterrview = (char **) xmalloc(numttypes * sizeof(short *)); for_all_terrain_types(t) { if (!t_is_cell(t)) { side->auxterrview[t] = malloc_area_layer(char); } } if (0 /* aux terrain seen separately from main terrain */) { side->auxterrviewdate = (short **) xmalloc(numttypes * sizeof(short *)); for_all_terrain_types(t) { if (!t_is_cell(t)) { side->auxterrviewdate[t] = malloc_area_layer(short); } } } } } } else { terrainset = TRUE; } /* Allocate material view layers. */ if (any_material_views < 0) { any_material_views = FALSE; for_all_material_types(m) { for_all_terrain_types(t) { if (tm_storage_x(t, m) > 0 && tm_see_always(t, m) == 0) { any_material_views = TRUE; if (any_material_views_by_m == NULL) any_material_views_by_m = xmalloc(nummtypes); any_material_views_by_m[m] = TRUE; break; } } } } if (any_material_views) { if (side->materialview == NULL) side->materialview = (short **) xmalloc(nummtypes * sizeof(short *)); if (side->materialviewdate == NULL) side->materialviewdate = (short **) xmalloc(nummtypes * sizeof(short *)); for_all_material_types(m) { if (any_material_views_by_m[m]) { if (side->materialview[m] == NULL) { side->materialview[m] = malloc_area_layer(short); } if (side->materialviewdate[m] == NULL) { side->materialviewdate[m] = malloc_area_layer(short); } } } } /* Allocate weather view layers. */ if (any_temp_variation && !g_see_weather_always()) { if (side->tempview == NULL) { side->tempview = malloc_area_layer(short); } if (side->tempviewdate == NULL) { side->tempviewdate = malloc_area_layer(short); } } if (any_clouds && !g_see_weather_always()) { if (side->cloudview == NULL) { side->cloudview = malloc_area_layer(short); } if (side->cloudbottomview == NULL) { side->cloudbottomview = malloc_area_layer(short); } if (side->cloudheightview == NULL) { side->cloudheightview = malloc_area_layer(short); } if (side->cloudviewdate == NULL) { side->cloudviewdate = malloc_area_layer(short); } } if (any_wind_variation && !g_see_weather_always()) { if (side->windview == NULL) { side->windview = malloc_area_layer(short); } if (side->windviewdate == NULL) { side->windviewdate = malloc_area_layer(short); } } /* Allocate the vision coverage cache. */ if (side->coverage == NULL) { side->coverage = malloc_area_layer(short); } /* Allocate vision altitude coverage if needed */ if (any_los < 0) { any_los = FALSE; for_all_unit_types(u) { if (u_vision_bend(u) != 100) { any_los = TRUE; break; } } } if (side->alt_coverage == NULL && any_los) { side->alt_coverage = malloc_area_layer(short); } return terrainset; } /* Calculate the centroid of all the starting units. */ void calc_start_xy(Side *side) { int num = 0, sumx = 0, sumy = 0; Unit *unit; for_all_side_units(side, unit) { if (in_play(unit)) { sumx += unit->x; sumy += unit->y; ++num; } } if (num > 0) { side->startx = wrapx(sumx / num); side->starty = sumy / num; } } /* Given a side, get its "number", which is same as its "id". */ int side_number(Side *side) { return side->id; } /* The inverse function - given a number, figure out which side it is. Returns NULL for any failure. */ /* (should cache values in a table, do direct lookup) */ Side * side_n(int n) { Side *side; for_all_sides(side) if (side->id == n) return side; return NULL; } Side * find_side_by_name(char *str) { Side *side; if (empty_string(str)) return NULL; for_all_sides(side) { if (!empty_string(side->name) && strcmp(side->name, str) == 0) return side; if (!empty_string(side->noun) && strcmp(side->noun, str) == 0) return side; if (!empty_string(side->pluralnoun) && strcmp(side->pluralnoun, str) == 0) return side; if (!empty_string(side->adjective) && strcmp(side->adjective, str) == 0) return side; } return NULL; } Side * parse_side_spec(char *str) { int s; char *reststr; if (isdigit(str[0])) { s = strtol(str, &reststr, 10); if (between(1, s, numsides)) { return side_n(s); } } else { return find_side_by_name(str); } return NULL; } /* This is true when one side controls another. */ int side_controls_side(Side *side, Side *side2) { return (side == side2 || side2->controlled_by == side); } short *max_control_ranges; static int controller_here(int x, int y); static int controller_here(int x, int y) { Unit *unit2; if (distance(x, y, tmpunit->x, tmpunit->y) < 2) return FALSE; for_all_stack(x, y, unit2) { if (side_controls_unit(tmpside, unit2) && probability(uu_control(unit2->type, tmpunit->type))) return TRUE; } return FALSE; } /* This is true if the given side may operate on the given unit. */ int side_controls_unit(Side *side, Unit *unit) { int dir, x1, y1; Unit *unit2; if (unit == NULL) return FALSE; if (is_designer(side)) return TRUE; /* Also give debuggers full control. */ if (Debug || DebugG || DebugM) return TRUE; if (side_controls_side(side, unit->side)) { /* The *unit* side must have the tech to use the unit, the controlling side would have to actually take over the unit if it wants to use its tech level to control the unit. */ if (unit->side != NULL && unit->side->tech[unit->type] < u_tech_to_use(unit->type)) return FALSE; if (u_direct_control(unit->type) || unit == side->self_unit) return TRUE; /* Unit is not under direct control of the side; look for a controlled unit that can control this unit. */ if (max_control_ranges == NULL) { int u1, u2; max_control_ranges = (short *) xmalloc(numutypes * sizeof(short)); for_all_unit_types(u2) { max_control_ranges[u2] = -1; for_all_unit_types(u1) { max_control_ranges[u2] = max(max_control_ranges[u2], uu_control_range(u1, u2)); } } } if (max_control_ranges[unit->type] >= 0) { for_all_stack(unit->x, unit->y, unit2) { if (unit != unit2 && side_controls_unit(side, unit2) && probability(uu_control_at(unit2->type, unit->type))) return TRUE; } /* (what about occupants that could be controllers?) */ } if (max_control_ranges[unit->type] >= 1) { for_all_directions(dir) { if (interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) { for_all_stack(x1, y1, unit2) { if (side_controls_unit(side, unit2) && probability(uu_control_adj(unit2->type, unit->type))) return TRUE; } } } } if (max_control_ranges[unit->type] >= 2) { tmpside = side; tmpunit = unit; return search_around(unit->x, unit->y, max_control_ranges[unit->type], controller_here, &x1, &y1, 1); } } return FALSE; } /* This is true if the given side may examine the given unit. */ int side_sees_unit(Side *side, Unit *unit) { if (unit == NULL) return FALSE; if (side->see_all || side->show_all) return TRUE; if (side_controls_side(side, unit->side)) return TRUE; /* We may also examine units that trust us, */ if (trusted_side(unit->side, side)) return TRUE; /* or if the game is over. */ if (endofgame) return TRUE; return FALSE; } /* This is true if the side can see the image of a unit. */ int side_sees_image(Side *side, Unit *unit) { UnitView *uview; if (side == NULL) run_error("NULL side to side_sees_image"); if (unit == NULL) return FALSE; if (side->see_all || side->show_all) return TRUE; if (side_controls_side(side, unit->side)) return TRUE; #if 0 /* shouldn't be necessary */ if (u_see_always(unit->type)) return TRUE; #endif for_all_view_stack(side, unit->x, unit->y, uview) { if (unit->type == view_type(uview) && unit->side == view_side(uview)) return TRUE; } return FALSE; } int num_units_in_play(Side *side, int u) { int num = 0; Unit *unit; if (side != NULL && side->ingame) { for_all_side_units(side, unit) { if (unit->type == u && in_play(unit) && completed(unit)) ++num; } } return num; } int num_units_incomplete(Side *side, int u) { int num = 0; Unit *unit; if (side != NULL && side->ingame) { for_all_side_units(side, unit) { if (unit->type == u && alive(unit) && !completed(unit)) ++num; } } return num; } Unit * find_next_unit(Side *side, Unit *prevunit) { Unit *unit = NULL; if (side != NULL) { if (prevunit == NULL) prevunit = side->unithead; for (unit = prevunit->next; unit != prevunit; unit = unit->next) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y)) { return unit; } } } return NULL; } Unit * find_prev_unit(Side *side, Unit *nextunit) { Unit *unit = NULL; if (side != NULL) { if (nextunit == NULL) nextunit = side->unithead; for (unit = nextunit->prev; unit != nextunit; unit = unit->prev) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y)) { return unit; } } } return NULL; } Unit * find_next_actor(Side *side, Unit *prevunit) { Unit *unit = NULL; if (side != NULL) { if (prevunit == NULL) prevunit = side->unithead; for (unit = prevunit->next; unit != prevunit; unit = unit->next) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y) && (unit->act && unit->act->initacp)) { return unit; } } } return NULL; } Unit * find_prev_actor(Side *side, Unit *nextunit) { Unit *unit = NULL; if (side != NULL) { if (nextunit == NULL) nextunit = side->unithead; for (unit = nextunit->prev; unit != nextunit; unit = unit->prev) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y) && (unit->act && unit->act->initacp)) { return unit; } } } return NULL; } Unit * find_next_mover(Side *side, Unit *prevunit) { Unit *unit = NULL; if (side != NULL) { if (prevunit == NULL) prevunit = side->unithead; for (unit = prevunit->next; unit != prevunit; unit = unit->next) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y) && has_acp_left(unit)) { return unit; } } } return NULL; } Unit * find_prev_mover(Side *side, Unit *nextunit) { Unit *unit = NULL; if (side != NULL) { if (nextunit == NULL) nextunit = side->unithead; for (unit = nextunit->prev; unit != nextunit; unit = unit->prev) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y) && has_acp_left(unit)) { return unit; } } } return NULL; } Unit * find_next_awake_mover(Side *side, Unit *prevunit) { Unit *unit = NULL; if (side != NULL) { if (prevunit == NULL) prevunit = side->unithead; for (unit = prevunit->next; unit != prevunit; unit = unit->next) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y) && has_acp_left(unit) && (unit->plan && !unit->plan->asleep && !unit->plan->reserve && !unit->plan->delayed)) { return unit; } } } return NULL; } Unit * find_prev_awake_mover(Side *side, Unit *nextunit) { Unit *unit = NULL; if (side != NULL) { if (nextunit == NULL) nextunit = side->unithead; for (unit = nextunit->prev; unit != nextunit; unit = unit->prev) { if (is_unit(unit) && unit->id > 0 && alive(unit) && inside_area(unit->x, unit->y) && has_acp_left(unit) && (unit->plan && !unit->plan->asleep && !unit->plan->reserve && !unit->plan->delayed)) { return unit; } } } return NULL; } /* Compute the total number of action points available to the side at the beginning of the turn. */ int side_initacp(Side *side) { int totacp = 0; Unit *unit; for_all_side_units(side, unit) { if (alive(unit) && unit->act) { totacp += unit->act->initacp; } } return totacp; } /* Return the total of acp still unused by the side. */ int side_acp(Side *side) { int acpleft = 0; Unit *unit; for_all_side_units(side, unit) { if (alive(unit) && unit->act) { acpleft += unit->act->acp; } } return acpleft; } int side_acp_reserved(Side *side) { int acpleft = 0; Unit *unit; for_all_side_units(side, unit) { if (alive(unit) && unit->act) { if (unit->plan && (unit->plan->reserve || unit->plan->asleep)) { acpleft += unit->act->acp; } } } return acpleft; } #if 0 /* not presently needed, but possibly useful */ /* Return the total of acp still expected to be used by a human player. */ int side_acp_human(Side *side) { int acpleft = 0; Unit *unit; if (!side_has_display(side)) return acpleft; for_all_side_units(side, unit) { if (unit != NULL && alive(unit) && inside_area(unit->x, unit->y) && has_acp_left(unit) && (unit->plan && !unit->plan->asleep && !unit->plan->reserve && unit->plan->tasks == NULL)) { acpleft += unit->act->acp; } } return acpleft; } #endif /* (note that technology could be "factored out" of a game if all sides reach max tech at some point) */ /* (otherwise should compute once and cache) */ /* (note that once tech factors out, can never factor in again) */ int using_tech_levels(void) { int u; Side *side; for_all_sides(side) { for_all_unit_types(u) { if (side->tech[u] < u_tech_max(u)) return TRUE; } } return FALSE; } /* Take the given side out of the game entirely. This does not imply winning or losing, nor does it take down the side's display or AI. */ void remove_side_from_game(Side *side) { Side *side2; /* Officially flag this side as being no longer in the game. */ side->ingame = FALSE; /* Update everybody on this. */ for_all_sides(side2) { update_side_display(side2, side, TRUE); } /* Note that we no longer try to remove any images from other sides' views, because even with the side gone, the images may be useful information about where its units had gotten to. For instance, if a unit had been captured shortly before the side lost, then its image might still correspond to an actual unit, with only its side changed, and other sides may want to investigate for themselves. */ } int num_displayed_sides(void) { int n = 0; Side *side; for_all_sides(side) { if (side_has_display(side)) ++n; } return n; } void update_side_display_all_sides(Side *side, int rightnow) { Side *side2; for_all_sides(side2) { if (side_has_display(side2)) { update_side_display(side2, side, rightnow); } } } void set_side_name(Side *side, Side *side2, char *newname) { side2->name = newname; update_side_display_all_sides(side2, TRUE); } void set_side_longname(Side *side, Side *side2, char *newname) { side2->longname = newname; update_side_display_all_sides(side2, TRUE); } void set_side_shortname(Side *side, Side *side2, char *newname) { side2->shortname = newname; update_side_display_all_sides(side2, TRUE); } void set_side_noun(Side *side, Side *side2, char *newname) { side2->noun = newname; update_side_display_all_sides(side2, TRUE); } void set_side_pluralnoun(Side *side, Side *side2, char *newname) { side2->pluralnoun = newname; update_side_display_all_sides(side2, TRUE); } void set_side_adjective(Side *side, Side *side2, char *newname) { side2->adjective = newname; update_side_display_all_sides(side2, TRUE); } void set_side_emblemname(Side *side, Side *side2, char *newname) { side2->emblemname = newname; update_side_display_all_sides(side2, TRUE); } void set_side_colorscheme(Side *side, Side *side2, char *newname) { side2->colorscheme = newname; update_side_display_all_sides(side2, TRUE); } #ifdef DESIGNERS /* Implement the transition from regular side to a side that can alter the game's state directly (aka "designer"). */ void become_designer(Side *side) { Side *side2; if (side->designer) return; side->designer = TRUE; ++numdesigners; /* Designers have godlike power in the game, so mark it (permanently) as no longer a normal game. */ compromised = TRUE; /* Designers get to see everything if they want, but it's useful to see a player view sometimes, so give designers the ability to toggle the accurate display. Note that we must leave the side's see_all at its original value, so that unit views continue to be constructed and manipulated properly. */ if (!side->see_all) side->may_set_show_all = TRUE; side->show_all = TRUE; update_everything(); /* Let everybody know of the change in status. */ notify_all("%s IS NOW A DESIGNER.", short_side_title(side)); for_all_sides(side2) { update_side_display(side2, side, TRUE); } } /* Give up the godlike powers. */ void become_nondesigner(Side *side) { Side *side2; if (!side->designer) return; side->designer = FALSE; --numdesigners; /* Go back to the original viewing capabilities. */ side->may_set_show_all = FALSE; side->show_all = side->see_all; update_everything(); notify_all("%s is no longer a designer.", short_side_title(side)); for_all_sides(side2) { update_side_display(side2, side, TRUE); } } #endif /* DESIGNERS */ /* Return TRUE if side1 trusts side2. Note that the nature of trust is such that we don't want to check that the trust is mutual (although in practice it would be a foolish player who would trust a side that doesn't reciprocate!) */ int trusted_side(Side *side1, Side *side2) { if (side1 == side2) return TRUE; if (side1 == NULL || side2 == NULL || side1->trusts == NULL) return FALSE; return (side1->trusts[side2->id]); } void set_trust(Side *side, Side *side2, int val) { int oldval; Side *side3; if (side == side2) return; if (side->trusts == NULL) return; oldval = side->trusts[side2->id]; if (val == oldval) return; side->trusts[side2->id] = val; /* This is a major change that all other sides will know about. */ for_all_sides(side3) { if (active_display(side3)) { /* (should be handled by nlang.c) */ notify(side3, "%s %s %s", side_name(side), (val ? "now trusts" : "no longer trusts"), side_name(side2)); update_side_display(side3, side, FALSE); update_side_display(side3, side2, TRUE); } } /* (should update views, list of units known about, etc) */ /* (if cell goes from exact to only recorded, update display anyhow) */ } void set_controlled_by(Side *side, Side *side2, int val) { char tmpbuf[BUFSIZE]; int changed = FALSE; if (side == NULL || side2 == NULL || side == side2) return; if (val) { /* Make side be controlled. */ if (side->controlled_by == NULL) { side->controlled_by = side2; /* (should rework unit movement vectors now?) */ changed = TRUE; } else { /* Can't happen. */ } } else { /* Make side be uncontrolled. */ if (side->controlled_by == side2) { side->controlled_by = NULL; /* (should rework unit movement vectors now?) */ changed = TRUE; } else { /* Can't happen. */ } } if (changed) { strcpy(tmpbuf, short_side_title(side2)); /* (should be handled by nlang.c) */ notify_all("%s%s control%s %s now.", tmpbuf, (val ? "" : " no longer"), (short_side_title_plural_p(side2) ? "" : "s"), short_side_title(side)); } } /* What interfaces should use to tweak the autofinish flag. */ void set_autofinish(Side *side, int value) { side->autofinish = value; } /* What interfaces should use to tweak the autoresearch flag. */ void set_autoresearch(Side *side, int value) { side->autoresearch = value; } /* Being at war requires only ones of the sides to consider itself so. */ /* (Should the other side's relationships be tweaked also?) */ int enemy_side(Side *side1, Side *side2) { if (trusted_side(side1, side2)) return FALSE; return TRUE; } /* A formal alliance requires the agreement of both sides. */ int allied_side(Side *side1, Side *side2) { if (trusted_side(side1, side2)) return TRUE; return FALSE; } /* Neutralness is basically anything else. */ int neutral_side(Side *side1, Side *side2) { return (!enemy_side(side1, side2) && !allied_side(side1, side2)); } void set_willing_to_save(Side *side, int flag) { int oldflag = side->willingtosave; Side *side2; if (flag != oldflag) { side->willingtosave = flag; /* Inform everybody of our willingness to save. */ for_all_sides(side2) { if (active_display(side2)) { notify(side2, "%s is%s willing to save the game.", side_name(side), (flag ? "" : " not")); update_side_display(side2, side, TRUE); } } } } void set_willing_to_draw(Side *side, int flag) { int oldflag = side->willingtodraw; Side *side2; if (flag != oldflag) { side->willingtodraw = flag; /* Inform everybody of our willingness to draw. */ for_all_sides(side2) { if (active_display(side2)) { notify(side2, "%s is%s willing to declare the game a draw.", side_name(side), (flag ? "" : " not")); update_side_display(side2, side, TRUE); } } } } /* Set the self-unit of the given side. This is only called when done at the direction of the side, and may fail if the side can't change its self-unit voluntarily. */ void set_side_self_unit(Side *side, Unit *unit) { if (!in_play(unit)) return; if (side->self_unit && in_play(side->self_unit) && !u_self_changeable(side->self_unit->type)) return; side->self_unit = unit; /* (should update some part of display?) */ } /* Message-forwarding function. */ void send_message(side, sidemask, str) Side *side; SideMask sidemask; char *str; { char *sidedesc, buf[BUFSIZE]; SideMask testmask; Side *side2, *sender; sender = side; testmask = add_side_to_set(side, NOSIDES); if (sidemask == NOSIDES || empty_string(str)) { notify(side, "You say nothing."); return; } else if (sidemask == testmask) { notify(side, "You mumble to yourself."); return; } else if (sidemask == ALLSIDES) { notify(side, "You broadcast \"%s\" to all.", str); } else { sidedesc = sidemask_desc(buf, sidemask); notify(side, "You send \"%s\" to \"%s\".", str, sidedesc); } /* Handle messages that are to have anonymous senders. */ if (strlen(str) > 6 && strncmp(str, "(anon)", 6) == 0) { str += 6; sender = NULL; } for_all_sides(side2) { if (side2 != side && side_in_set(side2, sidemask)) { receive_message(side2, sender, str); } } } /* Handle the receipt of a message. Some messages may result in specific actions, but the default is just to forward to AIs and displays. */ void receive_message(side, sender, str) Side *side, *sender; char *str; { /* Look for specially-recognized messages. */ if (strcmp("%reveal", str) == 0) { reveal_side(sender, side, NULL); } else { /* Give the message to interface if present. */ if (side_has_display(side)) { update_message_display(side, sender, str, TRUE); } /* Also give the message to any local AI. */ if (side_has_ai(side)) { ai_receive_message(side, sender, str); } } } /* General method for passing along info about one side to another. */ void reveal_side(Side *sender, Side *recipient, int *types) { int x, y; Unit *unit; if (sender == NULL) return; if (g_see_all()) return; if (!g_terrain_seen()) { for_all_cells(x, y) { if (terrain_view(sender, x, y) != UNSEEN && terrain_view(recipient, x, y) == UNSEEN) { set_terrain_view(recipient, x, y, terrain_view(sender, x, y)); /* (should update unit views also) */ } } } for_all_side_units(sender, unit) { if (in_play(unit) && (types == NULL || types[unit->type])) { see_exact(recipient, unit->x, unit->y); } } /* Whether the amount of data is small or large, there is little value in being selective about updates - just redo entire maps. */ update_area_display(recipient); } /* Modify doctrine according to a specification. */ void set_doctrine(Side *side, char *spec) { int u; char *arg, *arg2, *rest, substr[BUFSIZE]; Doctrine *doctrine; rest = get_next_arg(spec, substr, &arg); if ((doctrine = find_doctrine_by_name(arg)) != NULL) { /* Found a specific named doctrine. */ } else if ((u = utype_from_name(arg)) != NONUTYPE) { doctrine = side->udoctrine[u]; } else if (strcmp(arg, "default") == 0) { doctrine = side->default_doctrine; } if (doctrine->locked) { /* (should mention name of doctrine) */ notify(side, "This doctrine cannot be changed!"); return; } rest = get_next_arg(rest, substr, &arg); if (strcmp(arg, "resupply") == 0) { rest = get_next_arg(rest, substr, &arg2); doctrine->resupply_percent = atoi(arg2); } else if (strcmp(arg, "rearm") == 0) { rest = get_next_arg(rest, substr, &arg2); doctrine->rearm_percent = atoi(arg2); } else if (strcmp(arg, "repair") == 0) { rest = get_next_arg(rest, substr, &arg2); doctrine->repair_percent = atoi(arg2); } else if (strcmp(arg, "run") == 0) { notify(side, "Can't modify construction runs yet"); } else { notify(side, "\"%s\" not a known doctrine property", arg); notify(side, "Known ones are: ask, resupply, rearm, repair, run"); } } void set_side_research(Side *side, int a) { side->research_topic = a; } /* Vision. */ static UnitView tmp_unit_view; UnitView * unit_view_at(Side *side, int x, int y) { int hash; Unit *unit; UnitView *uv; if (side->see_all) { unit = unit_at(x, y); if (unit == NULL) return NULL; tmp_unit_view.type = unit->type; tmp_unit_view.side_id = unit->side->id; tmp_unit_view.size = unit->size; tmp_unit_view.x = unit->x; tmp_unit_view.y = unit->y; tmp_unit_view.date = g_turn(); tmp_unit_view.id = unit->id; tmp_unit_view.unit = unit; tmp_unit_view.nexthere = NULL; return &tmp_unit_view; } /* This might be called during synthesis, before sides are set up. */ if (side->unit_views == NULL) return NULL; hash = (x ^ y) % VIEW_HASH_SIZE; for (uv = side->unit_views[hash]; uv != NULL; uv = uv->nexthere) { if (x == uv->x && y == uv->y) return uv; } return NULL; } UnitView * unit_view_next(Side *side, int x, int y, UnitView *uview) { Unit *unit; UnitView *uv2; if (side->see_all) { unit = uview->unit->nexthere; if (unit == NULL) return NULL; tmp_unit_view.type = unit->type; tmp_unit_view.side_id = unit->side->id; tmp_unit_view.size = unit->size; tmp_unit_view.x = unit->x; tmp_unit_view.y = unit->y; tmp_unit_view.date = g_turn(); tmp_unit_view.id = unit->id; tmp_unit_view.unit = unit; tmp_unit_view.nexthere = NULL; return &tmp_unit_view; } for (uv2 = uview->nexthere; uv2 != NULL; uv2 = uv2->nexthere) { if (x == uv2->x && y == uv2->y) return uv2; } return NULL; } UnitView * add_unit_view(Side *side, Unit *unit) { int i, changed = FALSE, rehash = FALSE; UnitView *uv, *uview = NULL; /* Look for an existing unit view that is tied to the given unit. */ if (side->unit_views != NULL) { for (i = 0; i < VIEW_HASH_SIZE; ++i) { for (uv = side->unit_views[i]; uv != NULL; uv = uv->nexthere) { if (uv->id == unit->id) { uview = uv; break; } } if (uview != NULL) break; } } if (uview == NULL) { uview = (UnitView *) xmalloc(sizeof(UnitView)); uview->id = unit->id; add_unit_view_raw(side, uview, unit->x, unit->y); changed = TRUE; } if (uview->type != unit->type) { uview->type = unit->type; changed = TRUE; } if (uview->side_id != unit->side->id) { uview->side_id = unit->side->id; changed = TRUE; } if (uview->size != unit->size) { uview->size = unit->size; changed = TRUE; } if (side == unit->side) uview->unit = unit; else uview->unit = NULL; if (uview->x != unit->x) { remove_unit_view(side, uview); rehash = TRUE; uview->x = unit->x; changed = TRUE; } if (uview->y != unit->y) { remove_unit_view(side, uview); uview->y = unit->y; rehash = TRUE; changed = TRUE; } if (rehash) { add_unit_view_raw(side, uview, uview->x, uview->y); } /* Irrespective of whether any view content changed, we now know that the view of this unit is current, so date it. */ uview->date = g_turn(); return (changed ? uview : NULL); } void add_unit_view_raw(Side *side, UnitView *uview, int x, int y) { int hash; UnitView *uv2, *prevview; if (side->unit_views == NULL) side->unit_views = (UnitView **) xmalloc(VIEW_HASH_SIZE * sizeof(UnitView *)); prevview = NULL; hash = (x ^ y) % VIEW_HASH_SIZE; for (uv2 = side->unit_views[hash]; uv2 != NULL; uv2 = uv2->nexthere) { if (uview->id < uv2->id && (prevview == NULL || uview->id > prevview->id)) { break; } prevview = uv2; } uview->nexthere = uv2; if (prevview == NULL) { side->unit_views[hash] = uview; } else { prevview->nexthere = uview; } uview->x = x; uview->y = y; } int remove_unit_view(Side *side, UnitView *olduview) { int hash; UnitView *uv, *prevview = NULL; if (side->unit_views == NULL) return FALSE; hash = (olduview->x ^ olduview->y) % VIEW_HASH_SIZE; for (uv = side->unit_views[hash]; uv != NULL; uv = uv->nexthere) { if (olduview == uv) { if (prevview == NULL) { side->unit_views[hash] = uv->nexthere; } else { prevview->nexthere = uv->nexthere; } return TRUE; } prevview = uv; } return FALSE; } /* What happens when a unit appears on a given cell. */ /* An always-seen unit has builtin spies/tracers to inform everybody else of all its movements. When such a unit occupies a cell, coverage is turned on and remains on until the unit leaves that cell. */ /* If this unit with onboard spies wanders into unknown territory, shouldn't that territory become known as well? I think the unseen test should only apply during initialization. */ /* But if always-seen unit concealed during init, will magically appear when it first moves! */ void all_see_occupy(Unit *unit, int x, int y, int inopen) { Side *side; int always = u_see_always(unit->type); for_all_sides(side) { if (side->see_all) { /* No work to do if the side sees everything. */ update_cell_display(side, x, y, UPDATE_ALWAYS); } else if (side_sees_unit(side, unit)) { see_cell(side, x, y); } else if (side_tracking_unit(side, unit)) { see_cell(side, x, y); } else { if (always && terrain_view(side, x, y) != UNSEEN) { add_cover(side, x, y, 1); set_alt_cover(side, x, y, 0); } if (inopen || always) { see_cell(side, x, y); } } } /* If the occupation happens during the game, nearby units that are on unfriendly sides should wake up. (should also be: "unless they were directed to sleep in presence of enemy") */ if (gameinited) { int dir, x1, y1; Unit *unit2, *unit3; for_all_directions(dir) { if (interior_point_in_dir(x, y, dir, &x1, &y1)) { for_all_stack(x1, y1, unit2) { if (unit2->side != NULL && unit2->side != unit->side && units_visible(unit2->side, x, y) && !unit_trusts_unit(unit2, unit)) { wake_unit(unit2->side, unit2, FALSE); } for_all_occupants(unit2, unit3) { if (unit3->side != NULL && unit3->side != unit->side && units_visible(unit3->side, x, y) && !unit_trusts_unit(unit3, unit)) { wake_unit(unit3->side, unit3, FALSE); } } } } } } } /* Some highly visible unit types cannot leave a cell without everybody knowing about the event. The visibility is attached to the unit, not the cell, so first the newly-empty cell is viewed, then view coverage is decremented. */ void all_see_leave(Unit *unit, int x, int y, int inopen) { Side *side; int always = u_see_always(unit->type), domore, update; UnitView *uview; for_all_sides(side) { if (side->see_all) { /* No work to do if the side sees everything. */ update_cell_display(side, x, y, UPDATE_ALWAYS); } else if (side_sees_unit(side, unit) && in_area(unit->x, unit->y)) { see_cell(side, x, y); } else if (side_tracking_unit(side, unit)) { see_cell(side, x, y); } else { if (always && terrain_view(side, x, y) != UNSEEN) { see_cell(side, x, y); if (cover(side, x, y) > 0) add_cover(side, x, y, -1); if (side->alt_coverage) /* (should recalc alt coverage, since was 0) */; } /* Won't be called twice, because view coverage is 0 now. */ if (inopen) { see_cell(side, x, y); } /* special hack to flush images we *know* are garbage */ if (cover(side, x, y) < 1) { domore = TRUE; update = FALSE; while (domore) { domore = FALSE; for_all_view_stack(side, x, y, uview) { if (side == view_side(uview)) { remove_unit_view(side, uview); domore = TRUE; update = TRUE; break; } } } if (update) update_cell_display(side, x, y, UPDATE_ALWAYS); } } } } static int tmpx0, tmpy0, tmpz0; static int tmpnx, tmpny, tmpnz; static void init_visible_elevation(int x, int y) { int elev = checked_elev_at(x, y); set_tmp2_at(x, y, elev - tmpz0); } static void init_visible_elevation_2(int x, int y) { int elev = checked_elev_at(x, y); set_tmp3_at(x, y, elev - tmpnz); } static void calc_visible_elevation(int x, int y) { int dir, x1, y1, elev, tmp, tmpa, tmpb; int adjelev = 9999, viselev, dist, dist1, cellwid = area.cellwidth; elev = checked_elev_at(x, y); dist = distance(x, y, tmpx0, tmpy0); if (cellwid <= 0) cellwid = 1; for_all_directions(dir) { if (point_in_dir(x, y, dir, &x1, &y1)) { dist1 = distance(x1, y1, tmpx0, tmpy0); if (dist1 < dist) { tmpa = tmp2_at(x1, y1); /* Account for the screening effect of the elevation difference. */ /* (dist1 will never be zero) */ tmpa = (tmpa * dist * cellwid) / (dist1 * cellwid); tmpb = checked_elev_at(x1, y1) + t_thickness(terrain_at(x1, y1)) - tmpz0; tmpb = (tmpb * dist * cellwid) / (dist1 * cellwid); tmp = max(tmpa, tmpb); adjelev = min(adjelev, tmp + tmpz0); } } } viselev = max(adjelev, elev); set_tmp2_at(x, y, viselev - tmpz0); } static void calc_visible_elevation_2(int x, int y) { int dir, x1, y1, elev, tmp, tmpa, tmpb; int adjelev = 9999, viselev, dist, dist1, cellwid = area.cellwidth; elev = checked_elev_at(x, y); dist = distance(x, y, tmpnx, tmpny); if (cellwid <= 0) cellwid = 1; for_all_directions(dir) { if (point_in_dir(x, y, dir, &x1, &y1)) { dist1 = distance(x1, y1, tmpnx, tmpny); if (dist1 < dist) { tmpa = tmp3_at(x1, y1); /* Account for the screening effect of the elevation difference. */ /* (dist1 will never be zero) */ tmpa = (tmpa * dist * cellwid) / (dist1 * cellwid); tmpb = checked_elev_at(x1, y1) + t_thickness(terrain_at(x1, y1)) - tmpnz; tmpb = (tmpb * dist * cellwid) / (dist1 * cellwid); tmp = max(tmpa, tmpb); adjelev = min(adjelev, tmp + tmpnz); } } } viselev = max(adjelev, elev); set_tmp3_at(x, y, viselev - tmpnz); } /* Unit's beady eyes are shifting from one location to another. Since new things may be coming into view, we have to check and maybe draw lots of cells (but only need the one output flush, fortunately). */ /* (LOS comes in here, to make irregular coverage areas) */ void cover_area(Side *side, Unit *unit, Unit *oldtransport, int x0, int y0, int nx, int ny) { Side *side2; if (side != NULL && !side->see_all && completed(unit) && (oldtransport == NULL || uu_occ_vision(unit->type, oldtransport->type) > 0)) { if (side->ingame) { /* Active sides keep their allies informed. Note that this will effectively leak information to allies of allies, even if the unit's side is not an ally directly. */ for_all_sides(side2) { if (trusted_side(side, side2)) { cover_area_1(side2, unit, x0, y0, nx, ny); } } } else { /* Inactive sides just have their own data maintained (not clear if this is critical, but seems sensible) */ cover_area_1(side, unit, x0, y0, nx, ny); } } } /* Set this flag to redo coverage without doing all the side effects that come with see_cell. */ static int suppress_see_cell; /* Set this flag to initialize views without waking any units. This is necessary when restoring a game, since we construct the view using this code, but don't want to modify the units' plans from what they were when the game was saved. */ int suppress_see_wakeup; static void cover_area_1(Side *side, Unit *unit, int x0, int y0, int nx, int ny) { int u = unit->type, range0, nrange, range, x, y, x1, y1, x2, y2; int y1c, y2c, xw, cov, los, r; int xmin, ymin, xmax, ymax, oldcov, newcov, anychanges; if (side->coverage == NULL) return; range0 = nrange = u_vision_range(u); /* Adjust for the effects of nighttime on vision range. */ if (in_area(x0, y0)) { if (night_at(x0, y0)) { range0 = (range0 * ut_vision_night_effect(u, terrain_at(x0, y0))) / 100; } } else { range0 = 0; } if (in_area(nx, ny)) { if (night_at(nx, ny)) { nrange = (nrange * ut_vision_night_effect(u, terrain_at(nx, ny))) / 100; } } else { nrange = 0; } range = max(range0, nrange); allocate_area_scratch(1); anychanges = FALSE; /* First, set the union of the from and to areas to the existing coverage. */ /* These may be outside the area - necessary since units may be able to see farther in x than the height of the area. */ /* Compute the maximum bounds that may be affected. */ if (y0 >= 0) { if (ny >= 0) { ymin = min(y0, ny); ymax = max(y0, ny); } else { ymin = ymax = y0; } } else if (ny >= 0) { ymin = ymax = ny; } if (x0 >= 0) { if (nx >= 0) { xmin = min(x0, nx); xmax = max(x0, nx); } else { xmin = xmax = x0; } } else if (nx >= 0) { xmin = xmax = nx; } if (any_los) { /* Need extra scratch layers, will be used for visible elevation cache. */ allocate_area_scratch(3); } los = FALSE; /* (should also adjust for effect of clouds here) */ if (u_vision_bend(u) != 100) { los = TRUE; /* Compute the minimum elevation for visibility at each cell. */ if (in_area(x0, y0)) { tmpx0 = x0; tmpy0 = y0; tmpz0 = checked_elev_at(x0, y0) + unit_alt(unit) + ut_eye_height(u, terrain_at(x0, y0)); apply_to_area(x0, y0, range0, init_visible_elevation); /* Leave own and adj cells alone, they will always be visible. */ for (r = 2; r <= range0; ++r) { apply_to_ring(x0, y0, r, r, calc_visible_elevation); } /* We now have a layer indicating how high things must be to be visible. */ } if (in_area(nx, ny)) { tmpnx = nx; tmpny = ny; tmpnz = checked_elev_at(nx, ny) + unit_alt(unit) + ut_eye_height(u, terrain_at(nx, ny)); apply_to_area(nx, ny, nrange, init_visible_elevation_2); /* Leave own and adj cells alone, they will always be visible. */ for (r = 2; r <= nrange; ++r) { apply_to_ring(nx, ny, r, r, calc_visible_elevation_2); } /* We now have another layer, indicating how high things must be to be visible from the new location. */ } } /* Copy the current coverage into the tmp layer. */ y1 = y1c = ymin - range; y2 = y2c = ymax + range; /* Clip the iteration bounds. */ if (y1c < 0) y1c = 0; if (y2c > area.height - 1) y2c = area.height - 1; for (y = y1c; y <= y2c; ++y) { x1 = xmin - range; x2 = xmax + range; for (x = x1; x <= x2; ++x) { if (in_area(x, y)) { xw = wrapx(x); set_tmp1_at(xw, y, cover(side, xw, y)); } } } /* Decrement coverage around the old location. */ if (in_area(x0, y0)) { y1 = y1c = y0 - range0; y2 = y2c = y0 + range0; /* Clip the iteration bounds. */ if (y1c < 0) y1c = 0; if (y2c > area.height - 1) y2c = area.height - 1; for (y = y1c; y <= y2c; ++y) { x1 = x0 - (y < y0 ? (y - y1) : range0); x2 = x0 + (y > y0 ? (y2 - y) : range0); for (x = x1; x <= x2; ++x) { if (in_area(x, y)) { xw = wrapx(x); if (!los || ((tmp2_at(xw, y) + tmpz0) <= (checked_elev_at(xw, y) + t_thickness(terrain_at(xw, y))))) { cov = tmp1_at(xw, y) - 1; /* Should never go negative, detect if so. */ if (cov < 0) { Dprintf("Negative coverage for %s at %d,%d\n", side_desig(side), xw, y); } set_tmp1_at(xw, y, cov); } if (los && (alt_cover(side, xw, y) == (tmp2_at(xw, y) + tmpz0))) /* this unit set the min, should recalc alt coverage now */; } } } } /* Increment coverage around the new location. */ if (in_area(nx, ny)) { y1 = y1c = ny - nrange; y2 = y2c = ny + nrange; /* Clip the iteration bounds. */ if (y1c < 0) y1c = 0; if (y2c > area.height - 1) y2c = area.height - 1; for (y = y1c; y <= y2c; ++y) { x1 = nx - (y < ny ? (y - y1) : nrange); x2 = nx + (y > ny ? (y2 - y) : nrange); for (x = x1; x <= x2; ++x) { if (in_area(x, y)) { xw = wrapx(x); if (!los || ((tmp3_at(xw, y) + tmpnz) <= (checked_elev_at(xw, y) + t_thickness(terrain_at(xw, y))))) { cov = tmp1_at(xw, y) + 1; set_tmp1_at(xw, y, cov); } if (los) set_alt_cover(side, xw, y, min(alt_cover(side, xw, y), (tmp3_at(xw, y) + tmpnz))); } } } } /* Now update the actual coverage. Do this over an area that includes both the decrement and increment changes. */ y1 = y1c = ymin - range; y2 = y2c = ymax + range; /* Clip the iteration bounds. */ if (y1c < 0) y1c = 0; if (y2c > area.height - 1) y2c = area.height - 1; for (y = y1c; y <= y2c; ++y) { x1 = xmin - range; x2 = xmax + range; for (x = x1; x <= x2; ++x) { if (in_area(x, y)) { xw = wrapx(x); oldcov = cover(side, xw, y); newcov = tmp1_at(xw, y); if (newcov != oldcov) { set_cover(side, xw, y, newcov); /* Skip over unit view updating if we're just repairing the coverage layer. */ if (suppress_see_cell) continue; if (newcov > oldcov && see_cell(side, xw, y) && !suppress_see_wakeup) react_to_seen_unit(side, unit, xw, y); if ((newcov > 0 && oldcov == 0) || (newcov == 0 && oldcov > 0) || (DebugG && newcov != oldcov)) update_cell_display(side, xw, y, UPDATE_COVER); anychanges = TRUE; } } } } /* If we're seeing new things, make sure they're on the display. */ if (anychanges) flush_display_buffers(side); } /* Use this to clear out garbled view coverage. Note however that this may not touch any unit views, otherwise players could collect info about hard-to-see units by refreshing/recomputing over and over. */ void reset_coverage(void) { Side *side; if (g_see_all()) return; suppress_see_cell = TRUE; for_all_sides(side) calc_coverage(side); suppress_see_cell = FALSE; } /* Calculate/recalculate the view coverage layers of a side. */ void calc_coverage(Side *side) { int x, y, pop, visible[MAXSIDES]; Unit *unit; Side *side2; if (side->coverage == NULL) return; Dprintf("Calculating all view coverage for %s\n", side_desig(side)); /* Either init all cells to 0, or use populations to decide. */ if (people_sides_defined()) { /* First figure out which sides' people will tell us stuff and cache it, so as to speed up the init. */ for_all_sides(side2) { visible[side2->id] = FALSE; if (trusted_side(side2, side) || side2->controlled_by == side) visible[side2->id] = TRUE; } for_all_cells(x, y) { pop = people_side_at(x, y); set_cover(side, x, y, ((pop != NOBODY && visible[pop]) ? 1 : 0)); } } else { for_all_cells(x, y) { set_cover(side, x, y, 0); } } /* Add coverage by, and of, the units already in place. */ for_all_units(unit) { if (in_play(unit)) { x = unit->x; y = unit->y; if (trusted_side(unit->side, side)) { /* Units that trust us tell us stuff. */ cover_area(side, unit, unit->transport, -1, -1, x, y); } else if (u_see_always(unit->type) && terrain_view(side, x, y) != UNSEEN) { /* Always-seen units effectively monitor their current location for us, even if they don't like us. */ add_cover(side, x, y, 1); } } } } /* Look for and clear out any bogus view data. */ void reset_all_views(void) { Side *side; for_all_sides(side) { if (!side->see_all) { reset_view(side); } } } void reset_view(Side *side) { int x, y, domore; UnitView *uview; for_all_cells(x, y) { /* Remove any leftover images of our own units. */ if (cover(side, x, y) < 1) { domore = TRUE; while (domore) { domore = FALSE; for_all_view_stack(side, x, y, uview) { if (side == view_side(uview)) { remove_unit_view(side, uview); domore = TRUE; break; } } } } } } void react_to_seen_unit(Side *side, Unit *unit, int x, int y) { int eu; Unit *eunit; UnitView *uview; Side *es; if (g_see_all() /* see real unit */) { /* (should look at all of stack if can be mixed) */ if ((eunit = unit_at(x, y)) != NULL) { if (unit->plan && !allied_side(eunit->side, side)) { /* should do a more general alarm */ wake_unit(unit->side, unit, TRUE); } } } else if (side->coverage != NULL) { uview = unit_view_at(side, x, y); if (uview != NULL) { eu = view_type(uview); es = view_side(uview); /* react only to certain utypes? */ if (unit->plan && !allied_side(es, side)) { /* should do a more general alarm */ wake_unit(unit->side, unit, TRUE); } } } else { /* ??? */ } } /* Decide whether any side acquires tracking on a unit. */ int any_tracking = -1; void maybe_track(Unit *unit) { int x0, y0, dir, x1, y1, chance; Unit *unit2; if (any_tracking < 0) { int u1, u2; any_tracking = FALSE; for_all_unit_types(u1) { for_all_unit_types(u2) { if (uu_track(u1, u2) > 0) { any_tracking = TRUE; break; } } if (any_tracking) break; } } if (!any_tracking) return; x0 = unit->x; y0 = unit->y; for_all_stack(x0, y0, unit2) { if (in_play(unit2) && unit2 != unit && unit2->side != unit->side && unit2->side != NULL) { chance = uu_track(unit2->type, unit->type); if (xrandom(10000) < chance) { add_side_to_set(unit2->side, unit->tracking); } } } for_all_directions(dir) { if (interior_point_in_dir(x0, y0, dir, &x1, &y1)) { for_all_stack(x1, y1, unit2) { if (in_play(unit2) && unit2 != unit && unit2->side != unit->side && unit2->side != NULL) { chance = uu_track(unit2->type, unit->type); if (xrandom(10000) < chance) { add_side_to_set(unit2->side, unit->tracking); } } } } } } /* Decide whether any side tracking a unit lost it. */ void maybe_lose_track(Unit *unit, int nx, int ny) { int t = terrain_at(nx, ny), chance; Side *side; chance = ut_lose_track(unit->type, t); if (chance > 0) { for_all_sides(side) { if (side_tracking_unit(side, unit) && xrandom(10000) < chance) { remove_side_from_set(side, unit->tracking); /* (should notify?) */ } } } } extern void compute_see_chances(void); int any_see_chances = -1; int any_people_see_chances = -1; int people_always_see; int max_see_chance_range; int any_see_mistake_chances = -1; int max_see_mistake_range; /* Determine whether there is any possibility of an uncertain sighting, and cache the conclusion. */ void compute_see_chances(void) { int u1, u2, u3, m1; any_see_chances = FALSE; any_people_see_chances = FALSE; people_always_see = TRUE; max_see_chance_range = -1; any_see_mistake_chances = FALSE; max_see_mistake_range = -1; for_all_unit_types(u2) { for_all_unit_types(u1) { if (uu_see_at(u1, u2) != 100) { any_see_chances = TRUE; max_see_chance_range = max(max_see_chance_range, 0); } if (uu_see_adj(u1, u2) != 100) { any_see_chances = TRUE; max_see_chance_range = max(max_see_chance_range, 1); } if (uu_see(u1, u2) != 100) { any_see_chances = TRUE; max_see_chance_range = max(max_see_chance_range, u_vision_range(u1)); } if (uu_see_mistake(u1, u2) > 0) { for_all_unit_types(u3) { if (uu_looks_like(u2, u3) > 0) { any_see_mistake_chances = TRUE; break; } } max_see_mistake_range = max(max_see_mistake_range, u_vision_range(u1)); } } for_all_material_types(m1) { if (m_people(m1) > 0 && um_people_see(u2, m1) > 0) { any_people_see_chances = TRUE; if (um_people_see(u2, m1) < 100) people_always_see = FALSE; } } if (people_sides_defined()) any_people_see_chances = TRUE; } } /* Update the view of this cell for everybody's benefit. May have to write to many displays. */ void all_see_cell(int x, int y) { Side *side; for_all_sides(side) { see_cell(side, x, y); } } static int test_for_successful_viewer(int x, int y); static int test_for_possible_viewer(int x, int y); static Unit *tmpunittosee, *tmpseer; static void mistake_view(Side *side, Unit *seer, UnitView *uview); /* Look at the given position, possibly not seeing anything. Return true if a unit was spotted. */ int see_cell(Side *side, int x, int y) { int update, updatet, chance, curview, x1, y1; int m, mupdate, wupdate, flags, domore, rslt, sawthis; Unit *unit, *unit2; UnitView *uview, *newuview; if (!in_area(x, y)) return FALSE; update = updatet = rslt = FALSE; /* If we see everything, just pass through to updating the display. */ /* (avoid using views so that designer mode won't affect view state?) */ if (side->see_all) { update = updatet = TRUE; rslt = (unit_at(x, y) != NULL); } else if (cover(side, x, y) > 0) { /* Always update our knowledge of the terrain. */ curview = terrain_view(side, x, y); if (curview == UNSEEN || !g_see_terrain_always()) { set_terrain_view(side, x, y, buildtview(terrain_at(x, y))); if (!g_see_terrain_always()) set_terrain_view_date(side, x, y, g_turn()); update = updatet = TRUE; } if (any_material_views) { mupdate = see_materials(side, x, y); if (mupdate) update = TRUE; } if (temperatures_defined() || clouds_defined() || winds_defined()) { wupdate = see_weather(side, x, y); if (wupdate) update = TRUE; } if (any_see_chances < 0) compute_see_chances(); /* Get rid of any unit views that are no longer valid here. */ domore = TRUE; while (domore) { domore = FALSE; for_all_view_stack(side, x, y, uview) { if ((unit = view_unit(uview)) == NULL || uview->x != unit->x || uview->y != unit->y) { if (remove_unit_view(side, uview)) update = TRUE; domore = TRUE; break; } } } for_all_stack(x, y, unit) { /* Reset the flag that says whether we've spotted this particular unit in the stack. */ sawthis = FALSE; /* First test for always-accurate views. */ if (side_sees_unit(side, unit) || u_see_always(unit->type) || (people_always_see && people_sides_defined() && people_side_at(x, y) == side->id)) { newuview = add_unit_view(side, unit); if (newuview) update = TRUE; rslt = sawthis = TRUE; } else if (any_see_chances) { /* If there are any unit-vs-unit see chances, use this code to scan around the given location for units that might be viewing this cell. */ /* First consider the possibility that we might be stacked in the same cell as someone who could see us. */ for_all_stack(x, y, unit2) { if (unit2->side == side) { chance = uu_see_at(unit2->type, unit->type); if (chance >= 100 || probability(chance)) { newuview = add_unit_view(side, unit); if (newuview) update = TRUE; rslt = sawthis = TRUE; /* (should check for hallucination?) */ break; } } } /* If nothing in the stack to see us, scan the area. */ if (max_see_chance_range > 0 && !sawthis) { tmpside = side; tmpunittosee = unit; tmpseer = NULL; if (search_around(x, y, max_see_chance_range, test_for_successful_viewer, &x1, &y1, 1) || test_for_successful_viewer(x, y)) { newuview = add_unit_view(side, unit); if (newuview) update = TRUE; rslt = sawthis = TRUE; /* Check for hallucination. Since we know the viewing unit from our search just above, we can test it directly. */ if (any_see_mistake_chances && cover(side, x, y) == 1) mistake_view(side, tmpseer, newuview); } } } else { /* Default case tests terrain visibility effect only. */ chance = ut_visibility(unit->type, terrain_at(x, y)); if (chance >= 100 || probability(chance)) { newuview = add_unit_view(side, unit); if (newuview) update = TRUE; rslt = sawthis = TRUE; /* If only one unit is viewing the cell, it might make a mistake. Since we don't know the specific viewing unit, we have to search for it. */ if (any_see_mistake_chances && cover(side, x, y) == 1) { tmpside = side; tmpunittosee = unit; tmpseer = NULL; if (search_around(x, y, max_see_mistake_range, test_for_possible_viewer, &x1, &y1, 1)) mistake_view(side, tmpseer, newuview); } } } } /* Check if any populations in the cell see something. */ if (any_people_see_chances && people_sides_defined() && people_side_at(x, y) == side->id && any_cell_materials_defined()) { for_all_stack(x, y, unit) { for_all_material_types(m) { if (cell_material_defined(m) && material_at(x, y, m) > 0) { chance = um_people_see(unit->type, m); if (probability(chance)) { newuview = add_unit_view(side, unit); if (newuview) update = TRUE; /* Proceed to consider the next unit. */ break; } } } } } } /* If there was any change in what was visible, tell the display. */ if (update) { flags = UPDATE_ALWAYS; /* If the view of the terrain changed, adjacent cells probably need to be redrawn as well. */ if (updatet) flags |= UPDATE_ADJ; update_cell_display(side, x, y, flags); } /* Indicate whether any units were seen at this location, even if no display updates were needed. */ return rslt; } /* Test the given location to see if there is a unit that will spot the given tmpunittosee. If the roll of the dice is successful, then write the seeing unit into tmpseer. */ static int test_for_successful_viewer(int x, int y) { int u, u2 = tmpunittosee->type, x2 = tmpunittosee->x, y2 = tmpunittosee->y; int dist, chance; Unit *unit; dist = distance(x, y, x2, y2); for_all_stack_with_occs(x, y, unit) { u = unit->type; if (unit->side == tmpside && u_vision_range(u) >= dist /* (should test LOS) */) { if (dist == 1) chance = uu_see_adj(u, u2); else /* (should interpolate according to distance?) */ chance = uu_see(u, u2); chance = (chance * ut_visibility(u2, terrain_at(x2, y2))) / 100; if (unit->transport) chance = (chance * uu_occ_vision(u, unit->transport->type)) / 100; if (chance >= 100 || probability(chance)) { tmpseer = unit; return TRUE; } } } return FALSE; } /* Return TRUE if there is a unit at the given location that could be the one seeing the tmpunittosee. This is not that accurate, since it doesn't test whether the unit found is the actual one that would have seen the given unit. */ static int test_for_possible_viewer(int x, int y) { int x2 = tmpunittosee->x, y2 = tmpunittosee->y; int dist; Unit *unit; dist = distance(x, y, x2, y2); for_all_stack(x, y, unit) { if (unit->side == tmpside && u_vision_range(unit->type) >= dist /* (should test LOS) */) { tmpseer = unit; return TRUE; } } return FALSE; } /* If the viewing unit might possibly misidentify the type of unit it saw, compute the chance and replace the view's type with one of the possible types that it could be mistaken for. */ static void mistake_view(Side *side, Unit *seer, UnitView *uview) { int mistakechance; if (seer == NULL) return; /* Decide if the unit appears to be one of some other type. */ mistakechance = uu_see_mistake(seer->type, uview->type); if (mistakechance > 0 && xrandom(10000) < mistakechance) uview->type = mistaken_type(uview->type); } /* Given a unit type, return a similar-looking type that it might be confused with. */ static int mistaken_type(int u2) { int u3, totalweight, randval; totalweight = 0; for_all_unit_types(u3) { totalweight += uu_looks_like(u2, u3); } /* If nothing that it looks like, return original type. */ if (totalweight == 0) return u2; randval = xrandom(totalweight); totalweight = 0; for_all_unit_types(u3) { totalweight += uu_looks_like(u2, u3); if (randval < totalweight) return u3; } return u2; } /* "Bare-bones" viewing, for whenever you know exactly what's there. This is the lowest level of all viewing routines, and executed a lot. */ void see_exact(Side *side, int x, int y) { int oldtview, newtview, update, mupdate, wupdate; Unit *unit; UnitView *newuview; if (!in_area(x, y)) return; if (side->see_all) { /* It may not really be necessary to do anything to the display, but the kernel doesn't know if the interface is drawing all the units that are visible, or is only drawing "interesting" ones, or whatever. It would be up to the interface to decide that, say, its magnification power for a map is such that only one unit is being displayed, and that the update from here doesn't result in any visible changes to what's already been drawn on the screen. */ update = TRUE; } else { update = FALSE; oldtview = terrain_view(side, x, y); newtview = buildtview(terrain_at(x, y)); set_terrain_view(side, x, y, newtview); set_terrain_view_date(side, x, y, g_turn()); if (oldtview != newtview) update = TRUE; mupdate = see_materials(side, x, y); if (mupdate) update = TRUE; wupdate = see_weather(side, x, y); if (wupdate) update = TRUE; for_all_stack(x, y, unit) { newuview = add_unit_view(side, unit); if (newuview) update = TRUE; } } /* If there was any change in what was visible, tell the display. */ if (update) { update_cell_display(side, x, y, UPDATE_ALWAYS | UPDATE_ADJ); } } static int see_materials(Side *side, int x, int y) { int m, curview, update; update = FALSE; if (!any_material_views) return update; for_all_material_types(m) { if (any_material_views_by_m[m]) { curview = material_view(side, x, y, m); if (curview != material_at(x, y, m)) { set_material_view(side, x, y, m, material_at(x, y, m)); update = TRUE; } /* Even if the data didn't change, our information is now up-to-date. */ if (1) set_material_view_date(side, x, y, m, g_turn()); } } return update; } static int see_weather(Side *side, int x, int y) { int curview, update; update = FALSE; if (temperatures_defined()) { curview = temperature_view(side, x, y); if (curview != temperature_at(x, y)) { set_temperature_view(side, x, y, temperature_at(x, y)); update = TRUE; } /* Even if the data didn't change, our information is now up-to-date. */ if (!g_see_weather_always()) set_temperature_view_date(side, x, y, g_turn()); } if (clouds_defined()) { curview = cloud_view(side, x, y); if (curview != raw_cloud_at(x, y)) { set_cloud_view(side, x, y, raw_cloud_at(x, y)); update = TRUE; } curview = cloud_bottom_view(side, x, y); if (curview != raw_cloud_bottom_at(x, y)) { set_cloud_bottom_view(side, x, y, raw_cloud_bottom_at(x, y)); update = TRUE; } curview = cloud_height_view(side, x, y); if (curview != raw_cloud_height_at(x, y)) { set_cloud_height_view(side, x, y, raw_cloud_height_at(x, y)); update = TRUE; } /* Even if the data didn't change, our information is now up-to-date. */ if (!g_see_weather_always()) set_cloud_view_date(side, x, y, g_turn()); /* Only need one date for the three layers of view data. */ } if (winds_defined()) { curview = wind_view(side, x, y); if (curview != raw_wind_at(x, y)) { set_wind_view(side, x, y, raw_wind_at(x, y)); update = TRUE; } /* Even if the data didn't change, our information is now up-to-date. */ if (!g_see_weather_always()) set_wind_view_date(side, x, y, g_turn()); } return update; } /* A border has been seen if the cells on both sides has been seen. */ int seen_border(Side *side, int x, int y, int dir) { int x1, y1; if (side->see_all) return TRUE; if (terrain_view(side, x, y) == UNSEEN) return FALSE; if (point_in_dir(x, y, dir, &x1, &y1)) if (terrain_view(side, x1, y1) == UNSEEN) return FALSE; return TRUE; } /* Make a printable identification of the given side. This should be used for debugging and designing, not regular play. */ char * side_desig(Side *side) { if (sidedesigbuf == NULL) sidedesigbuf = xmalloc(BUFSIZE); if (side != NULL) { sprintf(sidedesigbuf, "s%d (%s)", side_number(side), side_name(side)); if (side->self_unit) { tprintf(sidedesigbuf, "(self is #%d)", side->self_unit->id); } } else { sprintf(sidedesigbuf, "nullside"); } return sidedesigbuf; } /* Add a player into the list of players. All values are defaults here. */ Player * add_player(void) { Player *player = (Player *) xmalloc(sizeof(Player)); player->id = nextplayerid++; /* Note that all names and suchlike slots are NULL. */ ++numplayers; /* Add this one to the end of the player list. */ if (last_player == NULL) { playerlist = last_player = player; } else { last_player->next = player; last_player = player; } Dprintf("Added player #%d\n", player->id); return player; } Player * find_player(int n) { Player *player; for_all_players(player) { if (player->id == n) return player; } return NULL; } /* Transform a player object into a regularized form. */ void canonicalize_player(Player *player) { if (player == NULL) return; if (empty_string(player->displayname)) player->displayname = NULL; if (empty_string(player->aitypename)) player->aitypename = NULL; /* This seems like a logical place to canonicalize an AI type of "ai" into a specific type, but we don't necessarily know the best default until the game is closer to starting. */ } /* Make a printable identification of the given player. The output here must be correctly parseable according to player spec rules, although it doesn't have to be pretty or minimal. */ char * player_desig(Player *player) { if (playerdesigbuf == NULL) playerdesigbuf = xmalloc(BUFSIZE); if (player != NULL) { sprintf(playerdesigbuf, "%s,%s/%s@%s+%d", (player->name ? player->name : ""), (player->aitypename ? player->aitypename : ""), (player->configname ? player->configname : ""), (player->displayname ? player->displayname : ""), player->advantage); } else { sprintf(playerdesigbuf, "nullplayer"); } return playerdesigbuf; } /* Return the net advantage of the side, accounting for both the side's predetermined advantage and the player's choice. */ int actual_advantage(Side *side) { int advantage = max(1, side->advantage); if (side->player) advantage = side->player->advantage; return advantage; } /* Doctrine handling. */ Doctrine * new_doctrine(int id) { Doctrine *doctrine = (Doctrine *) xmalloc(sizeof(Doctrine)); /* Doctrine ids start at 1 and go up. */ if (id <= 0) id = next_doctrine_id++; else next_doctrine_id = id + 1; doctrine->id = id; /* Fill in all the doctrine slots with their default values. */ doctrine->resupply_percent = 50; doctrine->rearm_percent = 20; /* 35% is basically 1/3, rounded up. */ doctrine->repair_percent = 35; doctrine->construction_run = (short *) xmalloc (numutypes * sizeof(short)); /* We just committed on the number of unit types. */ canaddutype = FALSE; /* Add the new doctrine to the end of the list of doctrines. */ if (last_doctrine != NULL) { last_doctrine->next = doctrine; last_doctrine = doctrine; } else { doctrine_list = last_doctrine = doctrine; } return doctrine; } Doctrine * find_doctrine_by_name(char *name) { Doctrine *doctrine; if (name == NULL) return NULL; for_all_doctrines(doctrine) { if (doctrine->name != NULL && strcmp(name, doctrine->name) == 0) return doctrine; } return NULL; } Doctrine * find_doctrine(int id) { Doctrine *doctrine; for_all_doctrines(doctrine) { if (doctrine->id == id) return doctrine; } return NULL; } #if 0 /* seems useful, but never actually used */ Doctrine * clone_doctrine(Doctrine *olddoc) { int tmpid; Doctrine *newdoc, *tmpnext; newdoc = new_doctrine(0); tmpid = newdoc->id; tmpnext = newdoc->next; memcpy(newdoc, olddoc, sizeof(Doctrine)); newdoc->id = tmpid; /* Always allocate and copy subarrays. */ newdoc->construction_run = (short *) xmalloc (numutypes * sizeof(short)); memcpy(newdoc->construction_run, olddoc->construction_run, numutypes * sizeof(short)); newdoc->next = tmpnext; return newdoc; } #endif /* Standing order handling. */ StandingOrder * new_standing_order(void) { StandingOrder *sorder; sorder = (StandingOrder *) xmalloc(sizeof(StandingOrder)); sorder->types = xmalloc(numutypes); return sorder; } /* Add a new standing order for the side. This function can add to front or back of existing list of orders. */ int order_conds_match(StandingOrder *sorder, StandingOrder *sorder2); void add_standing_order(Side *side, StandingOrder *sorder, int pos) { StandingOrder *sorder2, *saved; if (sorder->task == NULL) { /* Cancelling an order. */ saved = NULL; if (side->orders == NULL) { /* No orders, so nothing to do. */ notify(side, "No orders to cancel"); } else if (order_conds_match(sorder, side->orders)) { /* Delete the first order in the list. */ saved = side->orders; if (side->last_order == side->orders) side->last_order = side->orders->next; side->orders = side->orders->next; } else { for (sorder2 = side->orders; sorder2->next != NULL; sorder2 = sorder2->next) { if (order_conds_match(sorder, sorder2->next)) { saved = sorder2->next; if (side->last_order == sorder2->next) side->last_order = sorder2->next->next; sorder2->next = sorder2->next->next; break; } } /* If we're here, no match; might be user error, but can't be sure, so don't say anything. */ } if (saved != NULL) { notify(side, "Cancelled order `%s'", standing_order_desc(saved, spbuf)); } } else if (pos == 0) { /* Add order to front of list. */ sorder->next = side->orders; side->orders = sorder; if (side->last_order == NULL) side->last_order = sorder; } else if (side->last_order != NULL) { /* Add order to end of list. */ side->last_order->next = sorder; side->last_order = sorder; } else { /* First standing order for the side. */ side->orders = side->last_order = sorder; } } int order_conds_match(StandingOrder *sorder, StandingOrder *sorder2) { return (sorder->condtype == sorder2->condtype && sorder->a1 == sorder2->a1 && sorder->a2 == sorder2->a2 && sorder->a3 == sorder2->a3); } int parse_standing_order(Side *side, char *cmdstr) { StandingOrder *sorder, *sorder2; if (cmdstr[0] == '?') { if (side->orders != NULL) { notify(side, "Current standing orders:"); for (sorder2 = side->orders; sorder2 != NULL; sorder2 = sorder2->next) { notify(side, " %s", standing_order_desc(sorder2, spbuf)); } } else { notify(side, "No standing orders in effect."); } return 0; } sorder = new_standing_order(); cmdstr = parse_unit_types(side, cmdstr, sorder->types); if (cmdstr == NULL) return (-1); cmdstr = parse_order_cond(side, cmdstr, sorder); if (cmdstr == NULL) return (-2); cmdstr = parse_task(side, cmdstr, &(sorder->task)); if (cmdstr == NULL) return (-3); add_standing_order(side, sorder, 0); if (sorder->task != NULL) { notify(side, "New standing order: %s", standing_order_desc(sorder, spbuf)); } return 0; } /* (should all go to nlang.c?) */ char * parse_unit_types(Side *side, char *str, char *utypevec) { char *arg, substr[BUFSIZE], *rest; int u; rest = get_next_arg(str, substr, &arg); u = utype_from_name(arg); if (u != NONUTYPE) { utypevec[u] = 1; } else if (strcmp(arg, "all") == 0) { for_all_unit_types(u) utypevec[u] = 1; } else { notify(side, "Unit type \"%s\" not recognized", arg); return NULL; } return rest; } char * parse_order_cond(Side *side, char *str, StandingOrder *sorder) { int x = 0, y = 0, dist = 0; char *arg, *arg2, substr[BUFSIZE], *rest; Unit *unit; rest = get_next_arg(str, substr, &arg); if (strcmp(arg, "at") == 0 || strcmp(arg, "@") == 0) { sorder->condtype = sorder_at; } else if (strcmp(arg, "in") == 0) { sorder->condtype = sorder_in; } else if (strcmp(arg, "within") == 0 || strcmp(arg, "near") == 0) { sorder->condtype = sorder_near; } else { notify(side, "Condition type \"%s\" not recognized", arg); return NULL; } if (sorder->condtype == sorder_near) { rest = get_next_arg(rest, substr, &arg); dist = strtol(arg, &arg2, 10); sorder->a3 = dist; } rest = get_next_arg(rest, substr, &arg); x = strtol(arg, &arg2, 10); if (arg != arg2 && *arg2 == ',') { y = strtol(arg2 + 1, &arg, 10); sorder->a1 = x; sorder->a2 = y; return rest; } else if ((unit = find_unit_by_name(arg)) != NULL) { if (sorder->condtype == sorder_at || sorder->condtype == sorder_near) { sorder->a1 = x; sorder->a2 = y; } else if (sorder->condtype == sorder_in) { sorder->a1 = unit->id; } else { return NULL; } return rest; } else { notify(side, "Condition argument \"%s\" not recognized", arg); return NULL; } } char * standing_order_desc(StandingOrder *sorder, char *buf) { int u, v = -1, i = 1; int *args; for_all_unit_types(u) { if (sorder->types[u]) { if (v < 0) v = u; } else { i = 0; } } if (v < 0 || sorder->task == NULL) { strcpy(buf, "invalid"); return buf; } sprintf(buf, "if %s ", (i ? "all" : u_type_name(v))); switch (sorder->condtype) { case sorder_at: tprintf(buf, "at %d,%d ", sorder->a1, sorder->a2); break; case sorder_in: tprintf(buf, "in %s ", short_unit_handle(find_unit(sorder->a1))); break; case sorder_near: tprintf(buf, "within %d %d,%d ", sorder->a3, sorder->a1, sorder->a2); break; default: strcat(buf, "unknown"); return buf; } i = sorder->task->type; args = sorder->task->args; switch (i) { case TASK_MOVE_TO: tprintf(buf, "%s %d,%d", taskdefns[i].display_name, args[0], args[1]); break; case TASK_SENTRY: tprintf(buf, "%s %d", taskdefns[i].display_name, args[0]); break; default: task_desc(buf+strlen(buf), NULL, NULL, sorder->task); break; } return buf; } /* Collect the next whitespace-separated argument. */ /* (should move to util.c or nlang.c) */ char * get_next_arg(char *str, char *buf, char **rsltp) { char *p; strcpy(buf, str); p = buf; /* Skip past any leading whitespace. */ while (isspace(*p)) ++p; if (*p == '"') { ++p; *rsltp = p; while (*p != '"' && *p != '\0') ++p; *p = '\0'; /* Increment so later scanning looks past the closing quote. */ ++p; } else { *rsltp = p; while (!isspace(*p) && *p != '\0') ++p; *p = '\0'; } return str + (p - buf); } /* Agreement handling here for now. */ Agreement *agreement_list = NULL; Agreement *last_agreement; int num_agreements = 0; int next_agreement_id = 1; char *agreement_desig_buf; void init_agreements(void) { agreement_list = last_agreement = NULL; num_agreements = 0; } Agreement * create_agreement(int id) { Agreement *ag = (Agreement *) xmalloc(sizeof(Agreement)); if (id == 0) id = next_agreement_id++; ag->id = id; ag->terms = lispnil; ag->next = NULL; if (agreement_list != NULL) { last_agreement->next = ag; } else { agreement_list = ag; } last_agreement = ag; ++num_agreements; return ag; } Agreement * find_agreement(int id) { Agreement *ag; for_all_agreements(ag) { if (ag->id == id) return ag; } return NULL; } char * agreement_desig(Agreement *ag) { if (agreement_desig_buf == NULL) agreement_desig_buf = xmalloc(BUFSIZE); sprintf(agreement_desig_buf, "", (ag->typename ? ag->typename : "(null)"), (ag->name ? ag->name : "(null)"), (ag->terms == lispnil ? "(no terms)" : "...terms...")); return agreement_desig_buf; } /* Helper functions to init view layers from rle encoding. */ void fn_set_terrain_view(int x, int y, int val) { int rawval; if (1) rawval = val; else /* This is a more efficient encoding, but only usable if can guarantee see-terrain-always upon rereading. */ rawval = (val ? buildtview(terrain_at(x, y)) : 0); set_terrain_view(tmpside, x, y, rawval); } void fn_set_terrain_view_date(int x, int y, int val) { set_terrain_view_date(tmpside, x, y, val); } void fn_set_aux_terrain_view(int x, int y, int val) { /* Filter anything but the basic six bits. */ val &= 0x3f; set_aux_terrain_view(tmpside, x, y, tmpttype, val); } void fn_set_aux_terrain_view_date(int x, int y, int val) { set_aux_terrain_view_date(tmpside, x, y, tmpttype, val); } void fn_set_material_view(int x, int y, int val) { set_material_view(tmpside, x, y, tmpmtype, val); } void fn_set_material_view_date(int x, int y, int val) { set_material_view_date(tmpside, x, y, tmpmtype, val); } void fn_set_temp_view(int x, int y, int val) { set_temperature_view(tmpside, x, y, val); } void fn_set_temp_view_date(int x, int y, int val) { set_temperature_view_date(tmpside, x, y, val); } void fn_set_cloud_view(int x, int y, int val) { set_cloud_view(tmpside, x, y, val); } void fn_set_cloud_bottom_view(int x, int y, int val) { set_cloud_bottom_view(tmpside, x, y, val); } void fn_set_cloud_height_view(int x, int y, int val) { set_cloud_height_view(tmpside, x, y, val); } void fn_set_cloud_view_date(int x, int y, int val) { set_cloud_view_date(tmpside, x, y, val); } void fn_set_wind_view(int x, int y, int val) { set_wind_view(tmpside, x, y, val); } void fn_set_wind_view_date(int x, int y, int val) { set_wind_view_date(tmpside, x, y, val); } #ifdef DESIGNERS static void paint_view_1(int x, int y); static int tmptview; static int tmpuviewflag; /* Paint the side's view with given values. */ void paint_view(Side *side, int x, int y, int r, int tview, int uviewflag) { tmpside = side; tmptview = tview; tmpuviewflag = uviewflag; apply_to_area_plus_edge(x, y, r, paint_view_1); } static void paint_view_1(int x, int y) { int tview, oldtview = terrain_view(tmpside, x, y); if (oldtview != tmptview) { tview = tmptview; if (tview == 1234567) { tview = buildtview(terrain_at(x, y)); } set_terrain_view(tmpside, x, y, tview); /* If the flag says so, see all units in the cell, otherwise leave all units unseen. */ /* (should we do a see_cell in any case?) */ if (tmpuviewflag && tview != UNSEEN) { see_exact(tmpside, x, y); } update_cell_display(tmpside, x, y, UPDATE_ALWAYS | UPDATE_ADJ); } } #endif /* DESIGNERS */