/* Units in Xconq. Copyright (C) 1986-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. */ #include "conq.h" #include "kernel.h" /* This is not a limit, just sets initial allocation of unit objects and how many additional to get if more are needed. This can be tweaked for more frequent but smaller allocation, or less frequent but larger and possibly space-wasting allocation. */ #ifndef INITMAXUNITS #define INITMAXUNITS 200 #endif static void allocate_unit_block(void); static int compare_units(Unit *unit1, Unit *unit2); static int compare_units_by_keys(const void *e1, const void *e2); static void leave_cell_aux(Unit *unit, Unit *oldtransport); static void change_cell_aux(Unit *unit, int x0, int y0, int x, int y); static void kill_unit_aux(Unit *unit, int reason); static void free_used_cells(Unit *unit); static void set_unit_position(Unit *unit, int x, int y, int z); static void enter_cell_aux(Unit *unit, int x, int y); static void eject_occupant(Unit *unit, Unit *occ); static void insert_unit(Unit *unithead, Unit *unit); static void delete_unit(Unit *unit); static void check_unit(Unit *unit); static int disband_unit_directly(Side *side, Unit *unit); static void add_unit_to_stack(Unit *unit, int x, int y); static void remove_unit_from_stack(Unit *unit); static void glimpse_adjacent_terrain(Unit *unit); static int test_class_membership(Obj *leaf); /* The list of available units. */ Unit *freeunits; /* The global linked list of all units. */ Unit *unitlist; /* A scratch global. */ Unit *tmpunit; /* Buffers for descriptions of units. */ /* We use several and rotate among them; this is so multiple calls in a single printf will work as expected. Not elegant, but simple and sufficient. */ #define NUMSHORTBUFS 3 static int curshortbuf; static char *shortbufs[NUMSHORTBUFS] = { NULL, NULL, NULL }; static char *utypenamen; char **shortestnames = NULL; int longest_shortest; char **shortestgnames = NULL; char *actorstatebuf = NULL; /* Total number of units in existence. */ int numunits; /* Total number of units that are alive. */ int numliveunits; /* Total number of live units, by type. */ int *numlivebytype; /* The next number to use for a unit id. */ int nextid = 1; /* A cache of completenesses. */ short *completenesses; /* Have units died since dead unit lists made. */ int recent_dead_flushed = TRUE; /* Local tmp var. */ static Side *tmpside_classtest; /* Advanced unit support. */ short default_size = 1; /* Default abstract size of unit */ short default_usedcells = 0; /* Number of cells being used by the unit */ short default_maxcells = 1; /* Default max number of cells that unit can use */ short default_curadvance = -1; /* Default advance being researched by new unit */ long default_population = 1; /* Default size of unit's population */ extern void free_used_cells(Unit *unit); /* Grab a block of unit objects to work with. */ static void allocate_unit_block(void) { int i; Unit *unitblock = (Unit *) xmalloc(INITMAXUNITS * sizeof(Unit)); for (i = 0; i < INITMAXUNITS; ++i) { unitblock[i].id = -1; unitblock[i].next = &unitblock[i+1]; } unitblock[INITMAXUNITS-1].next = NULL; freeunits = unitblock; Dprintf("Allocated space for %d units.\n", INITMAXUNITS); } /* Init gets a first block of units, and sets up the "independent side" list, since newly created units will appear on it. */ void init_units(void) { unitlist = NULL; allocate_unit_block(); init_side_unithead(indepside); completenesses = (short *) xmalloc(MAXUTYPES * sizeof(short)); } /* The primitive unit creator, called by regular creator and also used to make the dummy units that sit at the heads of the per-side unit lists. */ Unit * create_bare_unit(int type) { Unit *newunit; /* If our free list is empty, go and get some more units. */ if (freeunits == NULL) { allocate_unit_block(); } /* Take the first unit off the free list. */ newunit = freeunits; freeunits = freeunits->next; /* Give it a valid type... */ newunit->type = type; /* ...but an invalid id. */ newunit->id = -1; return newunit; } /* The regular unit creation routine. All the slots should have something reasonable in them, since individual units objects see a lot of reuse. */ Unit * create_unit(int type, int makebrains) { int m; Unit *newunit; if (numlivebytype == NULL) { numlivebytype = (int *) xmalloc(MAXUTYPES * sizeof(int)); } /* Fill this in. */ if (completenesses[0] == 0) { int i; for_all_unit_types(i) completenesses[i] = u_cp(i) / u_parts(i); } /* Test whether we've hit any designer-specified limits. */ if ((u_type_in_game_max(type) >= 0 && numlivebytype[type] >= u_type_in_game_max(type)) || (g_units_in_game_max() >= 0 && numliveunits >= g_units_in_game_max())) { return NULL; } /* Allocate the unit object. Xconq will fail instead of returning null. */ newunit = create_bare_unit(type); /* Init all the slots to distinguishable values. The unit is not necessarily newly allocated, so we have to hit all of its slots. */ /* Note that we don't check for overflow of unit ids, since it's highly unlikely that a single game would ever create 2 billion units. */ newunit->id = nextid++; newunit->name = NULL; /* Number == 0 means unit is unnumbered. */ newunit->number = 0; /* Outside the world. */ newunit->x = newunit->y = -1; /* At ground level. */ newunit->z = 0; /* Units default to being independent. */ newunit->side = indepside; newunit->origside = indepside; /* Create at max hp, let others reduce if necessary. */ newunit->hp = newunit->hp2 = u_hp(type); /* Create fully functional, let other routines set incompleteness. */ newunit->cp = u_cp(type); /* Not in a transport. */ newunit->transport = NULL; /* Note that the space never needs to be freed. */ if (newunit->supply == NULL && nummtypes > 0) { newunit->supply = (short *) xmalloc(nummtypes * sizeof(short)); } /* Always zero out all the supply values. */ for_all_material_types(m) newunit->supply[m] = 0; /* Allocate cache for units last production. */ if (nummtypes > 0) newunit->production = (short *) xmalloc(nummtypes * sizeof(short)); /* Always zero out all the production values. */ for_all_material_types(m) newunit->production[m] = 0; /* Will allocate tooling state when actually needed. */ newunit->tooling = NULL; /* Will allocate opinions when actually needed. */ newunit->opinions = NULL; if (makebrains) { init_unit_actorstate(newunit, TRUE); init_unit_plan(newunit); } else { newunit->act = NULL; newunit->plan = NULL; } if (newunit->extras != NULL) init_unit_extras(newunit); newunit->occupant = NULL; newunit->nexthere = NULL; /* Glue this unit into the list of independent units. */ newunit->next = newunit; newunit->prev = newunit; insert_unit(indepside->unithead, newunit); newunit->unext = unitlist; unitlist = newunit; /* Init more random slots. */ newunit->prevx = newunit->prevy = -1; newunit->transport_id = lispnil; newunit->aihook = NULL; /* Advanced unit support. */ newunit->size = default_size; newunit->reach = u_reach(type); newunit->usedcells = default_usedcells; newunit->maxcells = default_maxcells; newunit->population = default_population; newunit->curadvance = default_curadvance; newunit->autobuild = FALSE; newunit->autoresearch = FALSE; newunit->autoplan = FALSE; newunit->cp_stash = 0; newunit->buildingdone = FALSE; newunit->researchdone = FALSE; /* Add to the global unit counts. */ ++numunits; ++numliveunits; ++(numlivebytype[type]); return newunit; } void init_unit_tooling(Unit *unit) { unit->tooling = (short *) xmalloc(numutypes * sizeof(short)); } /* Set or resize the array of a unit's opinions about each side in a game. */ void init_unit_opinions(Unit *unit, int nsides) { int i; short *temp; temp = NULL; if (u_opinion_min(unit->type) != u_opinion_max(unit->type)) { if (unit->opinions != NULL && nsides > numsides) { temp = unit->opinions; unit->opinions = NULL; } if (unit->opinions == NULL) unit->opinions = (short *) xmalloc(nsides * sizeof(short)); /* Opinions are now all neutral. */ if (temp != NULL) { /* Copy over old opinions. */ for (i = 0; i < numsides; ++i) unit->opinions[i] = temp[i]; free(temp); } } else { if (unit->opinions != NULL) free(unit->opinions); unit->opinions = NULL; } } /* Alter the actorstate object to be correct for this unit. */ void init_unit_actorstate(Unit *unit, int flagacp) { if ((u_acp(unit->type) > 0 /* Acp-independent units still need a plan. */ || acp_indep(unit)) && unit->cp >= 0) { /* Might already have an actorstate, don't realloc if so; but do clear an existing actorstate, since might be reusing. */ if (unit->act == NULL) unit->act = (ActorState *) xmalloc(sizeof(ActorState)); else memset((char *) unit->act, 0, sizeof(ActorState)); /* Indicate that the action points have not been set. */ if (flagacp) unit->act->acp = unit->act->initacp = u_acp_min(unit->type) - 1; /* Flag the action as undefined. */ unit->act->nextaction.type = ACTION_NONE; } else { if (unit->act != NULL) free(unit->act); unit->act = NULL; } } /* Allocate and fill in the unit extras. This is needed whenever any one of the extra properties is going to get a non-default value. (Normally, the accessors for this structure return a default value if one is not present in a unit.) */ void init_unit_extras(Unit *unit) { if (unit->extras == NULL) unit->extras = (UnitExtras *) xmalloc(sizeof(UnitExtras)); /* Each slot must get the same value as the accessor would return if the structure had not been allocated. */ unit->extras->point_value = -1; unit->extras->appear = -1; unit->extras->appear_var_x = -1; unit->extras->appear_var_y = -1; unit->extras->disappear = -1; unit->extras->priority = -1; unit->extras->sym = lispnil; unit->extras->sides = lispnil; } /* Changing a unit's type has many effects. */ void change_unit_type(Unit *unit, int newtype, int reason) { int oldtype = unit->type, oldhp = unit->hp; PastUnit *pastunit; Side *oldside; /* Don't do anything if we're "changing" to the same type. */ if (oldtype == newtype) return; oldside = unit->side; pastunit = change_unit_to_past_unit(unit); if (reason >= 0) record_event(reason, ALLSIDES, pastunit->id, unit->id); /* Decrement viewing coverage of our old type. */ cover_area(unit->side, unit, unit->transport, unit->x, unit->y, -1, -1); all_see_leave(unit, unit->x, unit->y, (unit->transport == NULL)); /* Do the actual change. */ unit->type = newtype; /* Set the new hp to the same ratio of max as the unit had before. Caller can tweak to something else if necessary. */ unit->hp = (oldhp * u_hp_max(newtype)) / u_hp_max(oldtype); /* Need to guarantee a positive value though. */ if (unit->hp < 1) unit->hp = 1; unit->hp2 = unit->hp; /* Invalidate the side's point value cache. */ if (unit->side) unit->side->point_value_valid = FALSE; /* We might have to change sides as a result. */ /* (should modify per-side counts) */ if (!type_allowed_on_side(newtype, unit->side)) { if (type_allowed_on_side(newtype, indepside)) { /* Unit becomes independent. */ change_unit_side(unit, indepside, reason, NULL); } else { /* (what to do? search for an allowed side?) */ run_warning("Leaving unit on disallowed side"); } } /* Unit will always need a new number. */ assign_unit_number(unit); init_unit_opinions(unit, numsides); /* Redo the supply numbers. */ /* (should have separate control, so don't get cheated of old supply) */ init_supply(unit); /* This clears the unit's acp - desirable? */ init_unit_actorstate(unit, FALSE); init_unit_plan(unit); unit->aihook = NULL; /* Advanced unit support, but only if we're becoming an advanced type and we weren't before. */ /* (should share with unit creation) */ if (u_advanced(newtype) && !u_advanced(oldtype)) { unit->size = default_size; unit->reach = u_reach(unit->type); unit->usedcells = default_usedcells; unit->maxcells = default_maxcells; unit->population = default_population; unit->curadvance = default_curadvance; unit->autobuild = FALSE; unit->autoresearch = FALSE; unit->autoplan = FALSE; unit->cp_stash = 0; } /* Increment viewing coverage. */ cover_area(unit->side, unit, unit->transport, -1, -1, unit->x, unit->y); /* If vision range is 0, allow glimpses of adjacent cell terrain. This applies to terrain only, adjacent units cannot be seen. */ if (u_vision_range(unit->type) == 0) glimpse_adjacent_terrain(unit); all_see_occupy(unit, unit->x, unit->y, (unit->transport == NULL)); count_loss(oldside, oldtype, (reason == H_UNIT_WRECKED ? combat_loss : other_loss)); count_gain(unit->side, newtype, other_gain); /* Update global counts. */ --(numlivebytype[oldtype]); ++(numlivebytype[newtype]); } /* Test if any occupant of a given unit belongs to the given side. This is used to detect if a unit might be known about even if it's normally not visible (we have a spy inside a secret base for instance). */ int side_owns_occupant(Side *side, Unit *unit) { int suboccs = FALSE; Unit *occ; if (unit->occupant == NULL) return FALSE; for_all_occupants(unit, occ) { if (occ->side == side) return TRUE; if (occ->occupant) suboccs = TRUE; } if (suboccs) { for_all_occupants(unit, occ) { if (side_owns_occupant(side, occ)) return TRUE; } } return FALSE; } /* A unit occupies a cell by adding itself to the list of occupants. It will not occupy a transport even if one is at this position (other code should have taken care of this case already) If something goes wrong, return false. This routine is heavily used. */ int enter_cell(Unit *unit, int x, int y) { #ifdef DEBUGGING /* Not necessarily an error, but indicative of bugs elsewhere. */ if (unit->x >= 0 || unit->y >= 0) { run_warning("unit %d occupying cell (%d, %d), was at (%d %d)", unit->id, x, y, unit->x, unit->y); } #endif /* DEBUGGING */ /* Always check this one, but not necessarily fatal. */ if (!inside_area(x, y)) { run_warning("No cell at %d,%d, %s can't enter it", x, y, unit_desig(unit)); /* Let the unit remain off-world. */ return FALSE; } if (!can_occupy_cell(unit, x, y) && !can_occupy_conn(unit, x, y, unit->z)) { run_warning("Cell at %d,%d is too full for %s", x, y, unit_desig(unit)); /* Let the unit remain off-world. */ return FALSE; } add_unit_to_stack(unit, x, y); /* Set the location slots now. */ enter_cell_aux(unit, x, y); /* Inevitable side-effect of appearing in the new location. */ all_see_occupy(unit, x, y, TRUE); kick_out_enemy_users(unit->side, x, y); return TRUE; } /* Return true if the given unit can fit onto the given cell. */ /* (should eventually account for variable-size units) */ int can_occupy_cell(Unit *unit, int x, int y) { int u = unit->type, u2, u3, t = terrain_at(x, y), numthistype = 0; int fullness = 0, tcap, utcap, numtypes[MAXUTYPES]; Unit *unit2; if (unit == NULL) run_error("null unit?"); /* should never happen */ tcap = t_capacity(t); utcap = ut_capacity_x(u, t); if (tcap <= 0 && utcap <= 0) return FALSE; for_all_unit_types(u3) numtypes[u3] = 0; for_all_stack(x, y, unit2) { u2 = unit2->type; ++numtypes[u2]; if (u2 == u) ++numthistype; /* Only count against fullness if exclusive capacity exceeded. */ if (numtypes[u2] > ut_capacity_x(u2, t)) { fullness += ut_size(u2, t); } } /* Unit can be in this cell if there is dedicated space. */ if (numthistype + 1 <= utcap) return TRUE; /* Otherwise decide on the basis of fullness. */ return (fullness + ut_size(u, t) <= tcap); } int type_can_occupy_cell(int u, int x, int y) { int t = terrain_at(x, y), u2, u3, numthistype = 0, fullness = 0; int tcap, utcap, numtypes[MAXUTYPES]; Unit *unit2; tcap = t_capacity(t); utcap = ut_capacity_x(u, t); if (tcap <= 0 && utcap <= 0) return FALSE; for_all_unit_types(u3) numtypes[u3] = 0; for_all_stack(x, y, unit2) { u2 = unit2->type; ++numtypes[u2]; if (u2 == u) ++numthistype; /* Only count against fullness if exclusive capacity exceeded. */ if (numtypes[u2] > ut_capacity_x(u2, t)) { fullness += ut_size(u2, t); } } /* Unit can be in this cell if there is dedicated space. */ if (numthistype + 1 <= utcap) return TRUE; /* Otherwise decide on the basis of fullness. */ return (fullness + ut_size(u, t) <= tcap); } /* Similar, but don't count a specific given unit when calculating. */ /* (should share with can_occupy_cell, make unit3 be optional arg?) */ int can_occupy_cell_without(Unit *unit, int x, int y, Unit *unit3) { int u = unit->type, u2, u3, t = terrain_at(x, y), numthistype = 0; int fullness = 0, tcap, utcap, numtypes[MAXUTYPES]; Unit *unit2; if (unit == NULL) run_error("null unit?"); /* should never happen */ tcap = t_capacity(t); utcap = ut_capacity_x(u, t); if (tcap <= 0 && utcap <= 0) return FALSE; for_all_unit_types(u3) numtypes[u3] = 0; for_all_stack(x, y, unit2) { if (unit2 == unit3) continue; u2 = unit2->type; ++numtypes[u2]; if (u2 == u) ++numthistype; /* Only count against fullness if exclusive capacity exceeded. */ if (numtypes[u2] > ut_capacity_x(u2, t)) { fullness += ut_size(u2, t); } } /* Unit can be in this cell if there is dedicated space. */ if (numthistype + 1 <= utcap) return TRUE; /* Otherwise decide on the basis of fullness. */ return (fullness + ut_size(u, t) <= tcap); } int type_can_occupy_cell_without(int u, int x, int y, Unit *unit3) { int t = terrain_at(x, y), u2, u3, numthistype = 0, fullness = 0; int tcap, utcap, numtypes[MAXUTYPES]; Unit *unit2; tcap = t_capacity(t); utcap = ut_capacity_x(u, t); if (tcap <= 0 && utcap <= 0) return FALSE; for_all_unit_types(u3) numtypes[u3] = 0; for_all_stack(x, y, unit2) { if (unit2 == unit3) continue; u2 = unit2->type; ++numtypes[u2]; if (u2 == u) ++numthistype; /* Only count against fullness if exclusive capacity exceeded. */ if (numtypes[u2] > ut_capacity_x(u2, t)) { fullness += ut_size(u2, t); } } /* Unit can be in this cell if there is dedicated space. */ if (numthistype + 1 <= utcap) return TRUE; /* Otherwise decide on the basis of fullness. */ return (fullness + ut_size(u, t) <= tcap); } /* Test whether the given position has a connection that the unit may use. */ int can_occupy_conn_1(Unit *unit, int nx, int ny, int c); int can_occupy_conn(Unit *unit, int nx, int ny, int nz) { int c; if (numconntypes == 0) return FALSE; if ((nz & 1) == 1) { c = nz / 2; if (!t_is_connection(c) || !aux_terrain_defined(c)) run_warning("%s is on an invalid connection type %d?", unit_desig(unit), c); return can_occupy_conn_1(unit, nx, ny, c); } else { /* Test each connection type to see if it will work. */ for_all_connection_types(c) { if (aux_terrain_defined(c) && can_occupy_conn_1(unit, nx, ny, c)) return TRUE; } return FALSE; } } int can_occupy_conn_1(Unit *unit, int nx, int ny, int c) { int u, u2, tcap, utcap, fullness, numthistype, numtypes[MAXUTYPES]; Unit *unit2; if (!any_connections_at(nx, ny, c)) return FALSE; u = unit->type; tcap = t_capacity(c); utcap = ut_capacity_x(u, c); if (tcap <= 0 && utcap <= 0) return FALSE; for_all_unit_types(u2) numtypes[u2] = 0; fullness = 0; numthistype = 0; for_all_stack(nx, ny, unit2) { u2 = unit2->type; if (ut_capacity_neg(u2, c)) return FALSE; /* (should also count units assumed to be on connection) */ if ((unit2->z & 1) == 1 && (unit2->z >> 1) == c) { ++numtypes[u2]; if (u2 == u) ++numthistype; /* Only count against fullness if exclusive capacity exceeded. */ if (numtypes[u2] > ut_capacity_x(u2, c)) fullness += ut_size(u2, c); } } if (numthistype + 1 <= utcap) return TRUE; return (fullness + ut_size(u, c) <= tcap); } /* Return TRUE if there is any connection terrain at the given location that the given type of unit may sit on, even if the cell terrain is hostile. */ int type_can_sit_on_conn(int u, int x, int y) { int c; for_all_connection_types(c) { if (aux_terrain_defined(c) && any_connections_at(x, y, c) && !ut_vanishes_on(u, c) && !ut_wrecks_on(u, c)) return TRUE; } return FALSE; } /* Recursive helper to update everybody's position. This should be one of two routines that modify actual unit positions (leaving is the other). */ void enter_cell_aux(Unit *unit, int x, int y) { int u = unit->type; Unit *occ; #ifdef DEBUGGING /* Not necessarily an error, but indicative of bugs elsewhere. */ if (unit->x >= 0 || unit->y >= 0) { run_warning("unit %d occupying cell (%d, %d), was at (%d %d)", unit->id, x, y, unit->x, unit->y); } #endif /* DEBUGGING */ if (!in_area(x, y)) run_error("trying to enter cell outside world"); /* Actually set the unit position. */ set_unit_position(unit, x, y, unit->z); /* Increment viewing coverage. */ cover_area(unit->side, unit, unit->transport, -1, -1, x, y); /* If vision range is 0, allow glimpses of adjacent cell terrain. This applies to terrain only, adjacent units cannot be seen. */ if (u_vision_range(u) == 0) glimpse_adjacent_terrain(unit); /* Do for all the occupants too, recursively. */ for_all_occupants(unit, occ) { enter_cell_aux(occ, x, y); } } /* Decide whether the given unit can actually be in the given transport. */ /* (this still needs to account for multipart units) */ int can_occupy(Unit *unit, Unit *transport) { return can_carry(transport, unit); } int can_carry(Unit *transport, Unit *unit) { int u = unit->type, u2 = transport->type, u3, o; int numthistype = 0, numalltypes = 0, occvolume = 0; int numfacilities = 0; int nummobiles = 0; int ucap, uucap; int numtypes[MAXUTYPES]; Unit *occ; /* Intercept nonsensical arguments. */ if (transport == unit) return FALSE; /* Don't allow occupation of incomplete transports unless the unit is of a type that can help complete. */ if (!completed(transport) && uu_acp_to_build(u, u2) < 1) return FALSE; if (unit->occupant != NULL && !uu_occ_can_have_occs(u, u2)) return FALSE; ucap = u_capacity(u2); uucap = uu_capacity_x(u2, u); if (ucap <= 0 && uucap <= 0) return FALSE; for_all_unit_types(u3) numtypes[u3] = 0; /* Compute the transport's fullness. */ for_all_occupants(transport, occ) { o = occ->type; ++numalltypes; ++numtypes[occ->type]; if (o == u) ++numthistype; if (u_facility(o)) ++numfacilities; if (mobile(o)) ++nummobiles; /* Only count against fullness if exclusive capacity exceeded. */ if (numtypes[o] > uu_capacity_x(u2, o)) { occvolume += uu_size(o, u2); } } /* Can carry if dedicated space available. */ if (numthistype + 1 <= uucap) return TRUE; /* Check upper limit on count of occupants of this type. */ if (uu_occ_max(u2, u) >= 0 && numthistype + 1 - uucap > uu_occ_max(u2, u)) return FALSE; /* Check upper limit on number of facilities. */ if (u_facility_total_max(u2) >= 0 && u_facility(u) && numfacilities + 1 > u_facility_total_max(u2)) return FALSE; /* Check upper limit on number of mobiles. */ if (u_mobile_total_max(u2) >= 0 && mobile(u) && nummobiles + 1 > u_mobile_total_max(u2)) return FALSE; /* Check upper limit on count of occupants of all types. */ if (u_occ_total_max(u2) >= 0 && numalltypes + 1 > u_occ_total_max(u2)) return FALSE; /* Can carry if general unit hold has room. */ return (occvolume + uu_size(u, u2) <= ucap); } /* (should share with prev routine somehow) */ int type_can_occupy(int u, Unit *transport) { int u2 = transport->type, u3, o; int numthistype = 0, numalltypes = 0, occvolume = 0; int numfacilities = 0; int nummobiles = 0; int ucap, uucap; int numtypes[MAXUTYPES]; Unit *occ; /* Don't allow occupation of incomplete transports unless the unit is of a type that can help complete. */ if (!completed(transport) && uu_acp_to_build(u, u2) < 1) return FALSE; ucap = u_capacity(u2); uucap = uu_capacity_x(u2, u); if (ucap <= 0 && uucap <= 0) return FALSE; for_all_unit_types(u3) numtypes[u3] = 0; /* Compute the transport's fullness. */ for_all_occupants(transport, occ) { o = occ->type; ++numalltypes; ++numtypes[o]; if (o == u) ++numthistype; if (u_facility(o)) ++numfacilities; if (mobile(o)) ++nummobiles; /* Only count against fullness if exclusive capacity exceeded. */ if (numtypes[o] > uu_capacity_x(u2, o)) { occvolume += uu_size(o, u2); } } /* Can carry if dedicated space available. */ if (numthistype + 1 <= uucap) return TRUE; /* Check upper limit on count of occupants of this type. */ if (uu_occ_max(u2, u) >= 0 && numthistype + 1 - uucap > uu_occ_max(u2, u)) return FALSE; /* Check upper limit on number of facilities. */ if (u_facility_total_max(u2) >= 0 && u_facility(u) && numfacilities + 1 > u_facility_total_max(u2)) return FALSE; /* Check upper limit on number of mobiles. */ if (u_mobile_total_max(u2) >= 0 && mobile(u) && nummobiles + 1 > u_mobile_total_max(u2)) return FALSE; /* Check upper limit on count of occupants of all types. */ if (u_occ_total_max(u2) >= 0 && numalltypes + 1 > u_occ_total_max(u2)) return FALSE; /* Can carry if general unit hold has room. */ return (occvolume + uu_size(u, u2) <= ucap); } int can_occupy_type(Unit *unit, int u2) { int u = unit->type; /* Can occupy if nonzero reserved capacity for this type. */ if (uu_capacity_x(u2, u) > 0) return TRUE; /* Can occupy if general unit hold has room for at least one unit. */ return (uu_size(u, u2) <= u_capacity(u2)); } int can_carry_type(Unit *transport, int u) { return type_can_occupy(u, transport); } /* Units become occupants by linking into the transport's occupant list. */ void enter_transport(Unit *unit, Unit *transport) { int u = unit->type, ustack, u2stack; Unit *unit2, *topunit, *prevunit = NULL, *nextunit = NULL; /* Don't enter transport if there is no room. */ if (!can_carry(transport, unit)) { /* Disband the unit if it is incomplete. */ if (!completed(unit)) notify(unit->side, "%s is full. New unit disbanded on creation.", transport->name); kill_unit(unit, H_UNIT_DISBANDED); return; } if (unit == transport) { run_error("Unit is trying to enter itself"); } topunit = transport->occupant; if (topunit) { /* Insert the entering unit into the occupant list at its correct position. */ ustack = u_stack_order(u); for_all_occupants(transport, unit2) { u2stack = u_stack_order(unit2->type); if (ustack > u2stack || (ustack == u2stack && unit->id < unit2->id)) { nextunit = unit2; if (unit2 == topunit) topunit = unit; break; } prevunit = unit2; } if (prevunit != NULL) prevunit->nexthere = unit; } else { topunit = unit; } unit->nexthere = nextunit; transport->occupant = topunit; /* Point from the unit back to its transport. */ unit->transport = transport; /* If the transport is not yet on the map (such as when patching object refs during readin), skip anything that needs the transport's location. It will be handled when the transport is placed. */ if (inside_area(transport->x, transport->y)) { /* Set the passenger's coords to match the transport's. */ enter_cell_aux(unit, transport->x, transport->y); /* Others might be watching. */ all_see_occupy(unit, transport->x, transport->y, FALSE); } } /* Unit departs from a cell by zeroing out pointer if in cell or by being removed from the list of transport occupants. */ /* Dead units (hp = 0) may be run through here, so don't error out. */ void leave_cell(Unit *unit) { int ux = unit->x, uy = unit->y; Unit *transport = unit->transport; if (ux < 0 || uy < 0) { /* Sometimes called twice */ } else if (transport != NULL) { leave_transport(unit); leave_cell_aux(unit, transport); all_see_leave(unit, ux, uy, FALSE); /* not all_see_cell here because can't see inside transports */ update_unit_display(transport->side, transport, TRUE); } else { remove_unit_from_stack(unit); /* Now bash the coords. */ leave_cell_aux(unit, NULL); /* Now let everybody observe that the unit is gone. */ all_see_leave(unit, ux, uy, TRUE); } } /* When leaving, remove view coverage, record old position, and then trash the old coordinates just in case. Catches many bugs. Do this for all the occupants as well. */ static void leave_cell_aux(Unit *unit, Unit *oldtransport) { Unit *occ; if (unit->x < 0 && unit->y < 0) run_warning("unit %s has already left the cell", unit_desig(unit)); /* Stash the old coords. */ unit->prevx = unit->x; unit->prevy = unit->y; /* Set to a recognizable value. */ unit->x = -1; unit->y = -1; /* Make any occupants leave too. */ for_all_occupants(unit, occ) { leave_cell_aux(occ, unit); } /* Decrement viewing coverage around our old location. */ cover_area(unit->side, unit, oldtransport, unit->prevx, unit->prevy, -1, -1); } /* Disembarking unlinks from the list of passengers only, leaves the unit hanging in limbo, so should have it occupy something immediately. */ void leave_transport(Unit *unit) { Unit *transport = unit->transport, *occ; if (unit == transport) { run_error("Unit is trying to leave itself"); } if (unit == transport->occupant) { transport->occupant = unit->nexthere; } else { for_all_occupants(transport, occ) { if (unit == occ->nexthere) { occ->nexthere = occ->nexthere->nexthere; break; } } } /* Bash the now-spurious link. */ unit->transport = NULL; } int change_cell(Unit *unit, int x, int y) { int ux = unit->x, uy = unit->y; Unit *transport = unit->transport; /* Always check this one, but not necessarily fatal. */ if (!inside_area(x, y)) { run_warning("No cell at %d,%d, %s can't enter it", x, y, unit_desig(unit)); /* Let the unit remain off-world. */ return FALSE; } if (!can_occupy_cell(unit, x, y)) { run_warning("Cell at %d,%d is too full for %s", x, y, unit_desig(unit)); /* Let the unit remain off-world. */ return FALSE; } if (transport != NULL) { leave_transport(unit); update_unit_display(transport->side, transport, TRUE); } else { remove_unit_from_stack(unit); } add_unit_to_stack(unit, x, y); change_cell_aux(unit, ux, uy, x, y); all_see_leave(unit, ux, uy, (transport == NULL)); /* Inevitable side-effect of appearing in the new location. */ all_see_occupy(unit, x, y, TRUE); return TRUE; } void set_unit_position(Unit *unit, int x, int y, int z) { int u, t, tmpz; /* Actually set the unit position. */ unit->x = x; unit->y = y; unit->z = z; /* Constrain the altitude according to terrain if nonzero. */ if (unit->z != 0) { if ((unit->z & 1) == 0) { u = unit->type; t = terrain_at(x, y); tmpz = unit->z / 2; tmpz = min(tmpz, ut_alt_max(u, t)); tmpz = max(tmpz, ut_alt_min(u, t)); unit->z = tmpz * 2; } else { /* (should adjust connection type?) */ } } } static void change_cell_aux(Unit *unit, int x0, int y0, int x, int y) { int u = unit->type; Unit *occ; /* Stash the old coords. */ unit->prevx = x0; unit->prevy = y0; set_unit_position(unit, x, y, unit->z); /* Change viewing coverage. */ cover_area(unit->side, unit, unit->transport, x0, y0, x, y); /* If vision range is 0, allow glimpses of adjacent cell terrain. This applies to terrain only, adjacent units cannot be seen. */ if (u_vision_range(u) == 0) glimpse_adjacent_terrain(unit); /* Do for all the occupants too, recursively. */ for_all_occupants(unit, occ) { change_cell_aux(occ, x0, y0, x, y); } } /* Put the given unit into the cell and/or unit stack at the given location. Do not modify the unit's xyz properties, just the unit layer and links to other units. */ void add_unit_to_stack(Unit *unit, int x, int y) { int u = unit->type, ustack, u2stack; Unit *topunit, *unit2, *prevunit = NULL, *nextunit = NULL; topunit = unit_at(x, y); if (topunit) { /* Insert the entering unit into the stack at its correct position. */ ustack = u_stack_order(u); for_all_stack(x, y, unit2) { u2stack = u_stack_order(unit2->type); if (ustack > u2stack || (ustack == u2stack && unit->id < unit2->id)) { nextunit = unit2; if (unit2 == topunit) topunit = unit; break; } prevunit = unit2; } if (prevunit != NULL) prevunit->nexthere = unit; } else { topunit = unit; } unit->nexthere = nextunit; set_unit_at(x, y, topunit); } /* Remove the given unit from the stack at its current location. */ void remove_unit_from_stack(Unit *unit) { int ux = unit->x, uy = unit->y; Unit *other; /* Unsplice ourselves from the list of units in this cell. */ if (unit == unit_at(ux, uy)) { set_unit_at(ux, uy, unit->nexthere); } else { for_all_stack(ux, uy, other) { if (unit == other->nexthere) { other->nexthere = other->nexthere->nexthere; break; } } } /* Bash this now-spurious link. */ unit->nexthere = NULL; } /* Add only the terrain types of adjacent cells to the unit's side's view, don't show units or anything else. This is a workaround for units with a vision range of 0. */ void glimpse_adjacent_terrain(Unit *unit) { int x = unit->x, y = unit->y, dir, x1, y1; Side *side = unit->side; if (u_vision_range(unit->type) == 0 && unit->transport == NULL && !g_see_all() && !g_terrain_seen() && side != NULL) { for_all_directions(dir) { if (point_in_dir(x, y, dir, &x1, &y1)) { if (terrain_view(side, x1, y1) == UNSEEN) { set_terrain_view(side, x1, y1, buildtview(terrain_at(x1, y1))); update_cell_display(side, x1, y1, UPDATE_ALWAYS | UPDATE_ADJ); } } } } } /* Given an overfull unit, spew out occupants until back within limits. */ void eject_excess_occupants(Unit *unit) { int u, u2 = unit->type, overfull = TRUE, count; int numalltypes = 0, occvolume = 0; int numeachtype[MAXUTYPES], sharedeachtype[MAXUTYPES]; Unit *occ; for_all_unit_types(u) numeachtype[u] = sharedeachtype[u] = 0; /* Eject occupants overflowing counts in shared space. */ for_all_occupants(unit, occ) ++numeachtype[occ->type]; for_all_unit_types(u) { if (numeachtype[u] > uu_capacity_x(u2, u)) { sharedeachtype[u] = numeachtype[u] - uu_capacity_x(u2, u); if (uu_occ_max(u2, u) >= 0 && sharedeachtype[u] > uu_occ_max(u2, u)) { count = sharedeachtype[u] - uu_occ_max(u2, u); while (count > 0) { for_all_occupants(unit, occ) { if (occ->type == u) { eject_occupant(unit, occ); --count; break; } } } } } } /* Eject occupants over the total max count allowed. */ for_all_occupants(unit, occ) ++numalltypes; if (u_occ_total_max(u2) >= 0 && numalltypes > u_occ_total_max(u2)) { count = numalltypes - u_occ_total_max(u2); while (unit->occupant != NULL) { eject_occupant(unit, unit->occupant); if (--count <= 0) break; } } /* Eject occupants overflowing volume of shared space. */ while (overfull) { for_all_unit_types(u) numeachtype[u] = 0; occvolume = 0; for_all_occupants(unit, occ) ++numeachtype[occ->type]; for_all_unit_types(u) { occvolume += max(0, numeachtype[u] - uu_capacity_x(u2, u)) * uu_size(u, u2); } if (occvolume > u_capacity(u2)) { overfull = TRUE; eject_occupant(unit, unit->occupant); } else { overfull = FALSE; } } } /* Given that an occupant must leave its transport, decide what happens; either move out into the open, into another unit, or vanish. */ /* (should be generic test) */ #define ut_dies_on(u, t) (ut_vanishes_on(u,t) || ut_wrecks_on(u, t)) void eject_occupant(Unit *unit, Unit *occ) { if (!in_play(unit) || !in_play(occ)) return; /* If the occupant is mobile and the current cell has room, let it escape but be stacked in the transport's cell. */ if (mobile(occ->type) && !ut_dies_on(occ->type, terrain_at(unit->x, unit->y)) && can_occupy_cell(occ, unit->x, unit->y)) { leave_cell(occ); enter_cell(occ, unit->x, unit->y); return; } /* (should let occupants escape into other units in cell) */ /* (should let occupants with acp escape into adj cells) */ /* Evaporating the occupant is our last option. */ kill_unit(occ, H_UNIT_KILLED); } /* Handle the general situation of a unit changing allegiance from one side to another. This is a common internal routine, so no messages here. */ void change_unit_side(Unit *unit, Side *newside, int reason, Unit *agent) { int ux = unit->x, uy = unit->y; Side *oldside = unit->side; Unit *occ; if (oldside == newside) return; /* Fail if the unit may not be on the new side. */ if (!unit_allowed_on_side(unit, newside)) return; if (reason >= 0) record_unit_side_change(unit, newside, reason, agent); if (oldside != NULL) { /* Last view of unit on its old side. */ update_unit_display(oldside, unit, TRUE); } /* (Should this be switchable maybe?) */ for_all_occupants(unit, occ) { change_unit_side(occ, newside, reason, agent); } /* Adjust view coverage. The sequencing here is to make sure that no viewing coverage gets left on or off inadvertantly. */ if (alive(unit) && inside_area(ux, uy)) { /* Uncover the current viewed area. */ cover_area(unit->side, unit, unit->transport, ux, uy, -1, -1); /* Actually set the side slot of the unit here. */ set_unit_side(unit, newside); /* Always redo the unit's number. */ unit->number = 0; assign_unit_number(unit); /* Cover it for the new side now. */ cover_area(unit->side, unit, unit->transport, -1, -1, ux, uy); /* A freebie for the unit's previous side. */ see_exact(oldside, ux, uy); } /* The new side gets to decide the unit's new plans. */ init_unit_plan (unit); /* Reflect the changeover in any appropriate displays. */ if (oldside != NULL) { /* Now we see the unit as belonging to someone else. */ update_unit_display(oldside, unit, TRUE); } if (newside != NULL) { update_unit_display(newside, unit, TRUE); } } /* This is a general test as to whether the given unit can be on the given side. */ int unit_allowed_on_side(Unit *unit, Side *side) { int u; if (unit == NULL) return FALSE; u = unit->type; return new_unit_allowed_on_side(u, side); } /* It is crucial to pass the unit type instead of the unit itself as argument in cases where the test must be applied before a new unit is created, to prevent the appearance of independent ghost units that never got the correct side set. Fortunately, there is no need to pass a real unit since the test only uses the type. */ int new_unit_allowed_on_side(int u, Side *side) { int u2, sum; /* Test general limitations on the type. */ if (!type_allowed_on_side(u, side)) return FALSE; /* Test specific game limits. */ if (u_type_per_side_max(u) >= 0) { if (side->numunits[u] >= u_type_per_side_max(u)) return FALSE; } if (g_units_per_side_max() >= 0) { sum = 0; for_all_unit_types(u2) { sum += side->numunits[u2]; } if (sum >= g_units_per_side_max()) return FALSE; } return TRUE; } int test_class_membership(Obj *leaf) { char *sclass; if (stringp(leaf)) { sclass = c_string(leaf); if (tmpside_classtest != NULL && tmpside_classtest != indepside) { if (empty_string(tmpside_classtest->sideclass)) return FALSE; return (strcmp(sclass, tmpside_classtest->sideclass) == 0); } else { return (strcmp(sclass, "independent") == 0); } } else { init_warning("testing side against garbled class expression"); /* Be permissive if continued. */ return TRUE; } } /* Inability to build a unit should not preclude its capture etc. Separate calls to has_advance_to_build have therefore been introduced everywhere in the code. */ int type_allowed_on_side(int u, Side *side) { int u2; if (side->uavail == NULL) { side->uavail = (short *) xmalloc(numutypes * sizeof(short)); for_all_unit_types(u2) { tmpside_classtest = side; side->uavail[u2] = eval_boolean_expression(u_possible_sides(u2), test_class_membership, TRUE); } } return side->uavail[u]; } int type_ever_available(int u, Side *side) { int u2; Unit *unit; if (!type_allowed_on_side(u, side)) return FALSE; for_all_side_units(side, unit) { if (unit->type == u) return TRUE; } for_all_unit_types(u2) { if (uu_acp_to_create(u2, u) > 0 && type_allowed_on_side(u2, side)) return TRUE; } /* (should add tests for capture etc) */ return FALSE; } int num_sides_allowed(int u) { int rslt; Side *side; rslt = 0; for_all_sides(side) { if (type_allowed_on_side(u, side)) { ++rslt; } } return rslt; } int unit_trusts_unit(Unit *unit1, Unit *unit2) { return (unit1->side == unit2->side || trusted_side(unit1->side, unit2->side)); } /* Put the given unit on the given side, without all the fancy effects. Important to handle independents, because this gets called during init. This is the only way that a unit's side may be altered. */ /* Note that this may be run on dead units, as part of clearing out a side's units, in which case we just want to relink, don't care about testing whether the type is allowed or not. */ void set_unit_side(Unit *unit, Side *side) { int u = unit->type; Side *oldside, *newside; /* Might not have anything to do. */ if (unit->side == side) return; /* Subtract from the counts for the ex-side. */ oldside = unit->side; if (oldside->numunits) --(oldside->numunits[u]); /* Set the unit's slot. */ /* Note that indep units have a NULL side, even though there is an actual side object for independents. */ unit->side = side; /* Make sure this unit is off anybody else's list. */ delete_unit(unit); newside = side; insert_unit(newside->unithead, unit); /* Add to counts for the side. */ if (newside->numunits) ++(newside->numunits[u]); /* Invalidate both sides' point value caches. */ oldside->point_value_valid = FALSE; newside->point_value_valid = FALSE; /* Bump the tech level if owning this type helps. */ if (side != NULL && side->tech[u] < u_tech_from_ownership(u)) { side->tech[u] = u_tech_from_ownership(u); /* (should update any displays of tech - how to ensure?) */ } } /* The origside is more of a historical record or cache, doesn't need the elaboration that unit side change does. */ void set_unit_origside(Unit *unit, Side *side) { unit->origside = side; } void set_unit_name(Side *side, Unit *unit, char *newname) { /* Always turn 0-length names into NULL. */ if (newname != NULL && strlen(newname) == 0) newname = NULL; /* Don't do anything if the name didn't actually change. */ if ((unit->name == NULL && newname == NULL) || (unit->name != NULL && newname != NULL && strcmp(unit->name, newname) == 0)) return; /* Record this in the history. */ record_unit_name_change(unit, newname); unit->name = newname; update_unit_display(side, unit, TRUE); update_unit_display(unit->side, unit, TRUE); /* (should also send to any other side directly viewing this unit!) */ } /* Given an amount to add, add it to the unit's hp, being careful to check the limits. Expect the caller to update the unit's display, for instance there may be a health bar to change. */ void add_to_unit_hp(Unit *unit, int hp) { int hpmax; unit->hp += hp; hpmax = u_hp(unit->type); if (unit->hp > hpmax) unit->hp = hpmax; unit->hp2 += hp; if (unit->hp2 > hpmax) unit->hp2 = hpmax; } void change_morale(Unit *unit, int sign, int morchange) { int u = unit->type, oldmorale; if (morchange != 0) { oldmorale = unit->morale; unit->morale += (sign * prob_fraction(morchange)); if (unit->morale < 0) unit->morale = 0; if (unit->morale > u_morale_max(u)) unit->morale = u_morale_max(u); if (unit->morale != oldmorale) { update_unit_display(unit->side, unit, TRUE); /* (should also send to any other side directly viewing this unit?) */ } } } int disband_unit(Side *side, Unit *unit) { int rslt; #ifdef DESIGNERS if (side->designer) { return designer_disband(unit); } #endif /* DESIGNERS */ if (side_can_disband(side, unit)) { rslt = disband_unit_directly(side, unit); if (rslt) { /* Nothing to do */ } else if (unit->plan) { set_disband_task(unit); } else { /* In order for this to work, we would need a way to direct one sort of unit to disband another. Just fail for now. */ return FALSE; } return TRUE; } else { return FALSE; } } int disband_unit_directly(Side *side, Unit *unit) { if (side_can_disband(side, unit)) { if (!completed(unit)) { /* Nothing complicated about getting rid of an incomplete unit. */ kill_unit(unit, H_UNIT_DISBANDED); return TRUE; } else { return FALSE; } } else { return FALSE; } } void wreck_unit(Unit *unit) { int u = unit->type, nu; /* Change the unit's type at its new location. */ change_unit_type(unit, u_wrecked_type(u), H_UNIT_WRECKED); nu = unit->type; /* Restore to default hp for the new type. */ unit->hp = unit->hp2 = u_hp(nu); /* Get rid of occupants if now overfull. */ eject_excess_occupants(unit); } /* Remove a unit from play. This is different from making it available for reallocation - only the unit flusher can do that. We remove all the passengers too, recursively. Sometimes units are "killed twice", so be sure not to run all this twice. Also count up occupant deaths, being sure not to count the unit itself as an occupant. */ void kill_unit(Unit *unit, int reason) { int u = unit->type, selfdied = FALSE; int ux = unit->x, uy = unit->y; if (alive(unit)) { if (unit->side && unit->side->self_unit == unit) selfdied = TRUE; leave_cell(unit); /* A freebie for the unit's side. */ see_exact(unit->side, ux, uy); kill_unit_aux(unit, reason); if (selfdied) { if (u_self_resurrects(u)) { /* should find and designate a new self unit */ return; } else { /* Make sure this doesn't have serious consequences? */ if (unit->side->ingame) side_loses(unit->side, NULL, -2); /* (can't do all consequences just yet?) */ } } recent_dead_flushed = FALSE; } } /* Trash it now - occupant doesn't need to leave_cell. Also record the event, and update the apropriate display. The unit here should be known to be alive. */ void kill_unit_aux(Unit *unit, int reason) { int u = unit->type; Unit *occ; Side *side = unit->side; unit->hp = 0; /* Get rid of the unit's plan/tasks. This should be safe, because unit death should only happen during action execution and in between turns, and plans/tasks should not be in use at those times. */ /* Might not be anything to dispose of. */ if (unit->plan != NULL) { free_plan(unit->plan); unit->plan = NULL; } /* Maybe enter the loss into the historical record. */ if (reason >= 0) record_unit_death(unit, reason); remove_unit_from_vector(unit->side->actionvector, unit, -1); if (side != NULL) { /* Invalidate the side's point value cache. */ side->point_value_valid = FALSE; update_unit_display(side, unit, TRUE); } /* Kill all the occupants in turn. */ for_all_occupants(unit, occ) { if (alive(occ)) kill_unit_aux(occ, reason); } /* Advanced unit support. */ if (u_advanced(unit->type)) free_used_cells(unit); /* Update global counts. */ --numliveunits; --(numlivebytype[u]); } /* Free up all cells used by unit that passed away. */ void free_used_cells(Unit *unit) { int x, y; for_all_cells_within_reach(unit, x, y) { if (!inside_area(x, y)) continue; if (user_at(x, y) == unit->id) set_user_at(x, y, NOUSER); } unit->usedcells = 0; } /* Get rid of all dead units at once. (This routine is basically a garbage collector, and should not be called during a unit list traversal.) The process starts by finding the first live unit, making it the head, then linking around all in the middle. Dead units stay on the dead unit list for each side until that side has had a chance to move. Then they are finally flushed in a permanent fashion. */ static void flush_one_unit(Unit *unit); void flush_dead_units(void) { Unit *unit, *prevunit, *nextunit; if (unitlist == NULL) return; unit = unitlist; while (!alive(unit)) { nextunit = unit->unext; delete_unit(unit); flush_one_unit(unit); unit = nextunit; if (unit == NULL) break; } unitlist = unit; /* Since the first unit of unitlist is guaranteed live now, we know that prevunit will always be set correctly; but mollify insufficiently intelligent compilers. */ prevunit = NULL; for_all_units(unit) { if (!alive(unit)) { nextunit = unit->unext; prevunit->unext = unit->unext; delete_unit(unit); flush_one_unit(unit); unit = prevunit; } else { prevunit = unit; } } } /* Keep it clean - hit all links to other places. Some might not be strictly necessary, but this is not an area to take chances with. */ static void flush_one_unit(Unit *unit) { unit->id = -1; unit->occupant = NULL; unit->transport = NULL; unit->nexthere = NULL; unit->prev = NULL; unit->unext = NULL; /* Add it on the front of the list of available units. */ unit->next = freeunits; freeunits = unit; } /* Do a bubble sort. Data is generally coherent, so bubble sort not too bad if we allow early termination when everything is already in order. */ /* If slowness objectionable, replace with something clever, but be sure that average performance in real games is what's being improved. */ void sort_units(int byidonly) { int flips; int passes = 0; register Unit *unit, *nextunit; Side *side; for_all_sides(side) { passes = 0; flips = TRUE; while (flips) { flips = FALSE; for_all_side_units(side, unit) { if (unit->next != side->unithead && (byidonly ? ((unit->id - unit->next->id) > 0) : (compare_units(unit, unit->next) > 0))) { flips = TRUE; /* Reorder the units by fiddling with their links. */ nextunit = unit->next; unit->prev->next = nextunit; nextunit->next->prev = unit; nextunit->prev = unit->prev; unit->next = nextunit->next; nextunit->next = unit; unit->prev = nextunit; } ++passes; } } } Dprintf("Sorting passes = %d\n", passes); } static int compare_units(Unit *unit1, Unit *unit2) { if (unit1->type != unit2->type) return (unit1->type - unit2->type); if (unit1->name && unit2->name == NULL) return -1; if (unit1->name == NULL && unit2->name) return 1; if (unit1->name && unit2->name) return strcmp(unit1->name, unit2->name); if (unit1->number != unit2->number) return (unit1->number - unit2->number); /* Ids impose a total ordering. */ return (unit1->id - unit2->id); } #if 0 /* Unused. */ /* Useful for the machine player to know how long it can move this piece before it should go home. Assumes can't replenish from terrain. Result may be negative, in which case it's time to go! */ int moves_till_low_supplies(Unit *unit) { int u = unit->type, m, moves = 1234567, tmp; for_all_material_types(m) { if ((um_consumption_per_move(u, m) > 0)) { tmp = (unit->supply[m] - um_storage_x(u, m) / 2) / um_consumption_per_move(u, m); moves = min(moves, tmp); } } return moves; } #endif /* Short, unreadable, but greppable listing of unit. Primarily useful for debugging and warnings. We use several buffers and rotate between them so we can call this more than once in a single printf. */ char * unit_desig(Unit *unit) { int i; char *shortbuf; /* Allocate if not yet done so. */ for (i = 0; i < NUMSHORTBUFS; ++i) { if (shortbufs[i] == NULL) shortbufs[i] = xmalloc(BUFSIZE); } /* Note that we test here, so that unit_desig(NULL) can be used to allocate any space that this routine might need later. */ if (unit == NULL) return "no unit"; shortbuf = shortbufs[curshortbuf]; curshortbuf = (curshortbuf + 1) % NUMSHORTBUFS; if (unit->id == -1) { sprintf(shortbuf, "s%d head", side_number(unit->side)); return shortbuf; } else if (is_unit_type(unit->type)) { sprintf(shortbuf, "s%d %s %d (%d,%d", side_number(unit->side), shortest_unique_name(unit->type), unit->id, unit->x, unit->y); if (unit->z != 0) tprintf(shortbuf, ",%d", unit->z); if (unit->transport) tprintf(shortbuf, ",in%d", unit->transport->id); strcat(shortbuf, ")"); /* close out the unit location */ return shortbuf; } else { return "!garbage unit!"; } } /* Short, unreadable, but greppable listing of unit that omits anything that changes from turn to turn. */ char * unit_desig_no_loc(Unit *unit) { char *shortbuf; if (unit == NULL) return "no unit"; /* Allocate if not yet done so. */ if (shortbufs[curshortbuf] == NULL) shortbufs[curshortbuf] = xmalloc(BUFSIZE); shortbuf = shortbufs[curshortbuf]; curshortbuf = (curshortbuf + 1) % NUMSHORTBUFS; if (unit->id == -1) { sprintf(shortbuf, "s%d head", side_number(unit->side)); return shortbuf; } else if (is_unit_type(unit->type)) { sprintf(shortbuf, "s%d %-3.3s %d", side_number(unit->side), shortest_unique_name(unit->type), unit->id); return shortbuf; } else { return "!garbage unit!"; } } /* Come up with a unit type name that fits in the given space. */ char * utype_name_n(int u, int n) { char *utypename, *shortname, *rawname; utypename = u_type_name(u); if (n <= 0 || strlen(utypename) <= n) { return utypename; } else if (n == 1 && !empty_string(u_uchar(u))) { /* Use the unit char if possible. */ return u_uchar(u); } else if (!empty_string(u_short_name(u))) { shortname = u_short_name(u); if (strlen(shortname) <= n) { return shortname; } else { rawname = shortname; } } else { rawname = utypename; } /* Copy what will fit. */ if (utypenamen == NULL) utypenamen = xmalloc(BUFSIZE); if (n > BUFSIZE - 1) n = BUFSIZE - 1; strncpy(utypenamen, rawname, n); utypenamen[n] = '\0'; return utypenamen; } char * shortest_unique_name(int u) { char namebuf[BUFSIZE], *name1; int u1, u2, i, len, allhavechars, firstuniq[MAXUTYPES], firstuniq1; int shortestdone[MAXUTYPES]; /* Don't try to allocate shortestnames before numutypes has been defined. This will cause crashes later on as the program mistakenly believes that all shortestnames[u] have been allocated just because shortestnames != NULL. */ if (numutypes == 0) return NULL; if (shortestnames == NULL) { shortestnames = (char **) xmalloc(numutypes * sizeof(char *)); /* First use game definition's single chars if possible. */ allhavechars = TRUE; for_all_unit_types(u1) { shortestdone[u1] = FALSE; if (!empty_string(u_uchar(u1))) { namebuf[0] = (u_uchar(u1))[0]; namebuf[1] = '\0'; shortestnames[u1] = copy_string(namebuf); shortestdone[u1] = TRUE; firstuniq[u1] = 0; } else { allhavechars = FALSE; } } if (!allhavechars) { /* Start with copies of full name for all types not already named. */ for_all_unit_types(u1) { if (shortestnames[u1] == NULL) { shortestnames[u1] = copy_string(u_type_name(u1)); firstuniq[u1] = 0; } } for_all_unit_types(u1) { if (!shortestdone[u1]) { name1 = shortestnames[u1]; firstuniq1 = firstuniq[u1]; for_all_unit_types(u2) { if (u1 != u2) { /* Look through the supposedly minimal unique substring and see if it is the same. */ for (i = 0; i < firstuniq1; ++i ) { if (name1[i] != (shortestnames[u2])[i]) { break; } } /* If so, must extend the unique substring. */ if (i == firstuniq1) { /* Look for the first nonmatching char. */ while (name1[firstuniq1] == (shortestnames[u2])[firstuniq1]) { ++firstuniq1; } } } } firstuniq[u1] = firstuniq1; } } /* For any types where the unique short name is shorter than the seed name, truncate appropriately. */ longest_shortest = 0; for_all_unit_types(u1) { if (firstuniq[u1] + 1 < strlen(shortestnames[u1])) { (shortestnames[u1])[firstuniq[u1] + 1] = '\0'; } len = strlen(shortestnames[u1]); if (len > longest_shortest) longest_shortest = len; } } if (Debug) { for_all_unit_types(u1) { Dprintf("Shortest type name: %s for %s\n", shortestnames[u1], u_type_name(u1)); } } } return shortestnames[u]; } /* Similar to shortest_unique_name, but returns a generic name/char instead. */ char * shortest_generic_name(int u) { char namebuf[BUFSIZE], *name1; int u1, u2, i, allhavechars, firstuniq[MAXUTYPES], firstuniq1; int shortestdone[MAXUTYPES]; if (shortestgnames == NULL) { shortestgnames = (char **) xmalloc(numutypes * sizeof(char *)); /* First use game definition's single chars if possible. */ allhavechars = TRUE; for_all_unit_types(u1) { shortestdone[u1] = FALSE; if (!empty_string(u_gchar(u1))) { namebuf[0] = (u_gchar(u1))[0]; namebuf[1] = '\0'; shortestgnames[u1] = copy_string(namebuf); shortestdone[u1] = TRUE; firstuniq[u1] = 0; } else if (!empty_string(u_uchar(u1))) { namebuf[0] = (u_uchar(u1))[0]; namebuf[1] = '\0'; shortestgnames[u1] = copy_string(namebuf); shortestdone[u1] = TRUE; firstuniq[u1] = 0; } else { allhavechars = FALSE; } } if (!allhavechars) { /* Start with copies of full name for all types not already named. */ for_all_unit_types(u1) { if (shortestgnames[u1] == NULL) { name1 = (!empty_string(u_generic_name(u1)) ? u_generic_name(u1) : u_type_name(u1)); shortestgnames[u1] = copy_string(name1); firstuniq[u1] = 0; } } for_all_unit_types(u1) { if (!shortestdone[u1]) { name1 = shortestgnames[u1]; firstuniq1 = firstuniq[u1]; for_all_unit_types(u2) { if (u1 != u2) { /* Look through the supposedly minimal unique substring and see if it is the same. */ for (i = 0; i < firstuniq1; ++i ) { if (name1[i] != (shortestgnames[u2])[i]) { break; } } /* If so, must extend the unique substring. */ if (i == firstuniq1) { /* Look for the first nonmatching char. */ while (name1[firstuniq1] == (shortestgnames[u2])[firstuniq1]) { ++firstuniq1; } } } } firstuniq[u1] = firstuniq1; } } /* For any types where the unique short name is shorter than the seed name, truncate appropriately. */ for_all_unit_types(u1) { if (firstuniq[u1] + 1 < strlen(shortestgnames[u1])) { (shortestgnames[u1])[firstuniq[u1] + 1] = '\0'; } } } if (Debug) { for_all_unit_types(u1) { Dprintf("Shortest generic type name: %s for %s\n", shortestgnames[u1], u_type_name(u1)); } } } return shortestgnames[u]; } /* This formats an actorstate readably. */ char * actorstate_desig(ActorState *as) { if (actorstatebuf == NULL) actorstatebuf = xmalloc(BUFSIZE); if (as != NULL) { sprintf(actorstatebuf, "acp %d/%d %s", as->acp, as->initacp, action_desig(&(as->nextaction))); return actorstatebuf; } else { return "no act"; } } /* Search for a unit with the given id number. */ /* This is used a lot, it should be sped up. */ Unit * find_unit(int n) { Unit *unit; for_all_units(unit) { if (unit->id == n && alive(unit)) return unit; } return NULL; } /* Same, but don't by picky about liveness. */ Unit * find_unit_dead_or_alive(int n) { Unit *unit; for_all_units(unit) { if (unit->id == n) return unit; } return NULL; } /* Find a unit with the given name, either alive or dead. */ Unit * find_unit_by_name(char *nm) { Unit *unit; if (nm == NULL) return NULL; for_all_units(unit) { if (unit->name != NULL && strcmp(unit->name, nm) == 0) return unit; } return NULL; } /* Find a unit with the given number, either alive or dead. */ Unit * find_unit_by_number(int nb) { Unit *unit; for_all_units(unit) { if (unit->number == nb) return unit; } return NULL; } /* Find a unit with the given symbol, either alive or dead. */ Unit * find_unit_by_symbol(Obj *sym) { Unit *unit; if (sym == lispnil) return NULL; for_all_units(unit) { if (equal(unit_symbol(unit), sym)) return unit; } return NULL; } /* Insert the given unit after the other given unit. */ void insert_unit(Unit *unithead, Unit *unit) { unit->next = unithead->next; unit->prev = unithead; unithead->next->prev = unit; unithead->next = unit; } /* Delete the unit from its list. */ void delete_unit(Unit *unit) { unit->next->prev = unit->prev; unit->prev->next = unit->next; } #if 0 /* not used, although they seem useful... */ int num_occupants(Unit *unit) { int num = 0; Unit *occ; for_all_occupants(unit, occ) { num += 1; } return num; } int num_units_at(int x, int y) int x, y; { int num = 0; Unit *unit; x = wrapx(x); if (!in_area(x, y)) { run_warning("num_units_at %d,%d??", x, y); return 0; } for_all_stack(x, y, unit) { num += 1; } return num; } #endif /* Call this to doublecheck invariants on units. */ void check_all_units(void) { Unit *unit; for_all_units(unit) { check_unit(unit); } } void check_unit(Unit *unit) { if (alive(unit) && unit->transport && !alive(unit->transport)) { run_warning("%s is inside a dead transport", unit_desig(unit)); } /* etc */ } UnitVector * make_unit_vector(int initsize) { UnitVector *vec; vec = (UnitVector *) xmalloc(sizeof(UnitVector) + initsize * sizeof(UnitVectorEntry)); vec->size = initsize; vec->numunits = 0; return vec; } void clear_unit_vector(UnitVector *vec) { vec->numunits = 0; } UnitVector * add_unit_to_vector(UnitVector *vec, Unit *unit, int flag) { int i; UnitVector *newvec; /* Can't add to something that doesn't exist! */ if (vec == NULL) run_error("No actionvector!"); /* (should search to see if already present) */ if (vec->numunits >= vec->size) { newvec = make_unit_vector((3 * vec->size) / 2); newvec->numunits = vec->numunits; for (i = 0; i < vec->numunits; ++i) { newvec->units[i] = vec->units[i]; } free(vec); vec = newvec; } ((vec->units)[vec->numunits]).unit = unit; ((vec->units)[vec->numunits]).flag = flag; ++(vec->numunits); return vec; } void remove_unit_from_vector(UnitVector *vec, Unit *unit, int pos) { int j; /* It's probably a bug that the vector is null sometimes, but don't flip out over it. */ if (vec == NULL) return; /* Search for unit in vector. */ if (pos < 0) { for (j = 0; j < vec->numunits; ++j) { if (unit == vec->units[j].unit) { pos = j; break; } } } if (pos < 0) return; if (unit != vec->units[pos].unit) run_error("unit mismatch in remove_unit_from_vector, %s not at %d", unit_desig(unit), pos); for (j = pos + 1; j < vec->numunits; ++j) vec->units[j-1] = vec->units[j]; --(vec->numunits); } enum sortkeys tmpsortkeys[MAXSORTKEYS]; static int compare_units_by_keys(CONST void *e1, CONST void *e2) { int i; Unit *unit1 = ((UnitVectorEntry *) e1)->unit; Unit *unit2 = ((UnitVectorEntry *) e2)->unit; if (unit1 == unit2) return 0; if (unit1 == NULL) return 1; if (unit2 == NULL) return -1; for (i = 0; i < MAXSORTKEYS; ++i) { switch (tmpsortkeys[i]) { case byside: if (unit1->side != unit2->side) { int s1 = side_number(unit1->side); int s2 = side_number(unit2->side); /* Put independents at the end of any list. */ if (s1 == 0) s1 = numsides + 1; if (s2 == 0) s2 = numsides + 1; return (s1 - s2); } break; case bytype: if (unit1->type != unit2->type) { return (unit1->type - unit2->type); } break; case byname: if (unit1->name) { if (unit2->name) { return strcmp(unit1->name, unit2->name); } else { return -1; } } else if (unit1->number > 0) { if (unit2->name) { return 1; } else if (unit2->number > 0) { return (unit1->number - unit2->number); } else { return -1; } } else if (unit2->name) { return 1; } else if (unit2->number > 0) { return 1; } break; case byactorder: /* (should sort by action priority?) */ break; case bylocation: if (unit1->y != unit2->y) { return (unit2->y - unit1->y); } else if (unit1->x != unit2->x) { return (unit1->x - unit2->x); } else { /* Both units are at the same location. Sort by transport. */ if (unit1->transport) { if (unit2->transport) { } else { return 1; } } else { if (unit2->transport) { return -1; } else { } } } break; case bynothing: return (unit1->id - unit2->id); default: break; } } /* Unit ids are all unique, so this is a reliable default sort key. */ return (unit1->id - unit2->id); } void sort_unit_vector(UnitVector *vec) { qsort(vec->units, vec->numunits, sizeof(UnitVectorEntry), compare_units_by_keys); } #ifdef DESIGNERS /* A designer can call this to create an arbitrary unit during the game. */ Unit * designer_create_unit(Side *side, int u, int s, int x, int y) { Unit *newunit; Side *side2; if (!type_can_occupy_cell(u, x, y)) return NULL; side2 = side_n(s); /* Check this BEFORE creating new unit! */ if (!new_unit_allowed_on_side(u, side2)) return NULL; newunit = create_unit(u, TRUE); if (newunit == NULL) return NULL; if (s != 0) { side2 = side_n(s); set_unit_side(newunit, side2); set_unit_origside(newunit, side2); /* (should ensure that any changed counts are set correctly) */ } init_supply(newunit); if (can_occupy_cell(newunit, x, y)) { enter_cell(newunit, x, y); } else { /* (should undo creation of unit?) */ return NULL; } update_cell_display(side, x, y, UPDATE_ALWAYS); update_unit_display(side, newunit, TRUE); return newunit; } /* Move a unit to a given location instantly, with all sides observing. */ int designer_teleport(Unit *unit, int x, int y, Unit *other) { int oldx = unit->x, oldy = unit->y, rslt; Side *side2; if (other != NULL && can_occupy(unit, other)) { leave_cell(unit); enter_transport(unit, other); all_see_cell(x, y); rslt = TRUE; } else if (can_occupy_cell(unit, x, y)) { change_cell(unit, x, y); rslt = TRUE; } else { rslt = FALSE; } if (rslt) { /* Provide accurate info on affected cells. */ for_all_sides(side2) { if (is_designer(side2)) { see_exact(side2, oldx, oldy); see_exact(side2, x, y); } } } return rslt; } int designer_change_side(Unit *unit, Side *side) { Side *side2; change_unit_side(unit, side, -1, NULL); for_all_sides(side2) { if (1 /* side2 should see change */) { update_unit_display(side2, unit, TRUE); } } return TRUE; } int designer_disband(Unit *unit) { kill_unit(unit, -1); return TRUE; } #endif /* DESIGNERS */ /* Unit-related functions moved here from actions.c and plan.c. */ /* Functions returning general abilities of a unit. */ int can_develop(Unit *unit) { return type_can_develop(unit->type); } int type_can_develop(int u) { int u2; for_all_unit_types(u2) { if (uu_acp_to_develop(u, u2) > 0) return TRUE; } return FALSE; } int can_toolup(Unit *unit) { return type_can_toolup(unit->type); } int type_can_toolup(int u) { int u2; for_all_unit_types(u2) { if (uu_acp_to_toolup(u, u2) > 0) return TRUE; } return FALSE; } int can_create(Unit *unit) { return type_can_create(unit->type); } int type_can_create(int u) { int u2; for_all_unit_types(u2) { if (uu_acp_to_create(u, u2) > 0 && uu_tp_max(u, u2) >= uu_tp_to_build(u, u2)) return TRUE; } return FALSE; } int can_complete(Unit *unit) { return type_can_complete(unit->type); } int type_can_complete(int u) { int u2; for_all_unit_types(u2) { if (uu_acp_to_build(u, u2) > 0 && uu_tp_max(u, u2) >= uu_tp_to_build(u, u2)) return TRUE; } return FALSE; } /* This tests whether the given unit is capable of doing repair. */ int can_repair(Unit *unit) { return type_can_repair(unit->type); } int type_can_repair(int u) { int u2; for_all_unit_types(u2) { if (uu_acp_to_repair(u, u2) > 0) return TRUE; } return FALSE; } /* This is true if the given location has some kind of material for the unit to extract. */ int can_extract_at(Unit *unit, int x, int y, int *mp) { int m; Unit *unit2; /* Can't do anything with an unseen location. */ if (unit->side != NULL && terrain_view(unit->side, x, y) == UNSEEN) return FALSE; for_all_material_types(m) { if (um_acp_to_extract(unit->type, m) > 0) { /* Look for case of extraction from terrain. */ if (any_cell_materials_defined() && cell_material_defined(m) && material_at(x, y, m) > 0) { *mp = m; return TRUE; } /* Then look for extraction from independent unit. */ for_all_stack(x, y, unit2) { if (in_play(unit2) && indep(unit2) && unit2->supply[m] > 0) { *mp = m; return TRUE; } } } } return FALSE; } /* This is true if the given location has some kind of material for the unit to transfer. */ int can_load_at(Unit *unit, int x, int y, int *mp) { int m; Unit *unit2; /* Can't do anything with an unseen location. */ if (unit->side != NULL && terrain_view(unit->side, x, y) == UNSEEN) return FALSE; for_all_material_types(m) { if (um_acp_to_load(unit->type, m) > 0) { for_all_stack(x, y, unit2) { if (in_play(unit2) && unit2->side == unit->side && u_acp(unit2->type) == 0 && um_acp_to_unload(unit2->type, m) > 0) { *mp = m; return TRUE; } } } } return FALSE; } /* This tests whether the given unit is capable of doing repair. */ int can_change_type(Unit *unit) { return type_can_change_type(unit->type); } int type_can_change_type(int u) { int u2; for_all_unit_types(u2) { if (uu_acp_to_change_type(u, u2) > 0) return TRUE; } return FALSE; } int can_disband(Unit *unit) { return (type_can_disband(unit->type) || !completed(unit)); } int type_can_disband(int u) { return (u_acp_to_disband(u) > 0); } int side_can_disband(Side *side, Unit *unit) { if (is_designer(side)) return TRUE; return (side_controls_unit(side, unit) && can_disband(unit)); } /* This tests whether the given unit is capable of adding terrain. */ int can_add_terrain(Unit *unit) { return type_can_add_terrain(unit->type); } int type_can_add_terrain(int u) { int t; for_all_terrain_types(t) { if (ut_acp_to_add_terrain(u, t) > 0) return TRUE; } return FALSE; } /* This tests whether the given unit is capable of removing terrain. */ int can_remove_terrain(Unit *unit) { return type_can_remove_terrain(unit->type); } int type_can_remove_terrain(int u) { int t; for_all_terrain_types(t) { if (ut_acp_to_remove_terrain(u, t) > 0) return TRUE; } return FALSE; } /* These functions test if the given utype belonging to the given side can build various classes of units. */ int can_build_attackers(Side *side, int u) { int u2; for_all_unit_types(u2) { if (u_offensive_worth(u2) > 0 && could_create(u, u2) && side_can_build(side, u2)) return TRUE; } return FALSE; } int can_build_defenders(Side *side, int u) { int u2; for_all_unit_types(u2) { if (u_defensive_worth(u2) > 0 && could_create(u, u2) && side_can_build(side, u2)) return TRUE; } return FALSE; } int can_build_explorers(Side *side, int u) { int u2; for_all_unit_types(u2) { if (u_explorer_worth(u2) > 0 && could_create(u, u2) && side_can_build(side, u2)) return TRUE; } return FALSE; } int can_build_colonizers(Side *side, int u) { int u2; for_all_unit_types(u2) { if (u_colonizer_worth(u2) > 0 && could_create(u, u2) && side_can_build(side, u2)) return TRUE; } return FALSE; } int can_build_facilities(Side *side, int u) { int u2; for_all_unit_types(u2) { if (u_facility_worth(u2) > 0 && could_create(u, u2) && side_can_build(side, u2)) return TRUE; } return FALSE; } /* True if the given unit is a sort that can build other units. */ int can_build(Unit *unit) { int u2; if (indep(unit) && g_indepside_can_build() != TRUE) return FALSE; for_all_unit_types(u2) { if (could_create(unit->type, u2)) return TRUE; } return FALSE; } int can_build_or_help(Unit *unit) { int u2; if (indep(unit) && g_indepside_can_build() != TRUE) return FALSE; for_all_unit_types(u2) { if (could_create(unit->type, u2) || uu_acp_to_build(unit->type, u2) > 0 || uu_acp_to_develop(unit->type, u2) > 0) return TRUE; } return FALSE; } int can_research(Unit *unit) { /* Kind of crude, but works for now. */ if (u_advanced(unit->type)) return TRUE; return FALSE; } int can_produce(Unit *unit) { int m; for_all_material_types(m) { if (um_acp_to_produce(unit->type, m)) return TRUE; } return FALSE; } /* Test if unit can move out into adjacent cells. */ int can_move(Unit *unit) { int d, x, y; for_all_directions(d) { if (interior_point_in_dir(unit->x, unit->y, d, &x, &y)) { /* (should account for world-leaving options?) */ if (could_live_on(unit->type, terrain_at(x, y))) return TRUE; } } return FALSE; } /* This is the maximum distance from "home" that a unit can expect to get, travelling on its most hostile terrain type. */ int operating_range_worst(int u) { int m, t, prod, range, worstrange = area.maxdim; for_all_material_types(m) { if (um_base_consumption(u, m) > 0) { for_all_terrain_types(t) { if (!terrain_always_impassable(u, t)) { prod = (um_base_production(u, m) * ut_productivity(u, t)) / 100; if (prod < um_base_consumption(u, m)) { range = um_storage_x(u, m) / (um_base_consumption(u, m) - prod); if (range < worstrange) worstrange = range; } } } } } return worstrange; } /* Same, but for best terrain. */ int operating_range_best(int u) { int m, t, prod, range, tbestrange, tbest = 0, bestrange = 0; int moves, consump; for_all_terrain_types(t) { if (!terrain_always_impassable(u, t)) { tbestrange = area.maxdim; for_all_material_types(m) { consump = 0; moves = (u_acp(u) * u_speed(u)) / 100; if (um_consumption_per_move(u, m) > 0) { consump = moves * um_consumption_per_move(u, m); } if (moves <= 0) moves = 1; if (um_base_consumption(u, m) > 0) { consump = max(consump, um_base_consumption(u, m)); } prod = (um_base_production(u, m) * ut_productivity(u, t)) / 100; if (prod > 0) { consump = max(0, consump - prod); } if (consump > 0) { range = (um_storage_x(u, m) * moves) / consump; if (range < tbestrange) tbestrange = range; } } if (tbestrange > bestrange) { bestrange = tbestrange; tbest = t; } } } return bestrange; }