home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (c) 1987, 1988 Stanley T. Shebs, University of Utah. */
- /* This program may be used, copied, modified, and redistributed freely */
- /* for noncommercial purposes, so long as this notice remains intact. */
-
- #pragma comment(exestr, "@(#) unit.c 12.1 95/05/09 ")
-
- /* RCS $Header: unit.c,v 1.5 88/07/20 15:23:42 shebs Exp $ */
-
- /* This file contains all code relating specifically to units. */
-
- /* Since units appear and disappear with distressing regularity (so it seems */
- /* to the player trying to keep up with all of them!), we have a little */
- /* storage manager for them. Since this sort of thing is tricky, there is a */
- /* two-level procedure for getting rid of units. First the hit points are */
- /* reduced to zero, at which point the unit is considered "dead". At the */
- /* end of a turn, we actually GC the dead ones. At this point their type */
- /* slot becomes NOTHING, and they are available for allocation. */
-
- #include "config.h"
- #include "misc.h"
- #include "period.h"
- #include "side.h"
- #include "unit.h"
- #include "map.h"
- #include "global.h"
-
- char *ordinal();
-
- Unit *units; /* array of all units */
- Unit *unitlist; /* pointer to head of list of units */
- Unit *tmpunit; /* global temporary used in several places */
-
- char unitbuf[BUFSIZE];
-
- int numunits; /* total number of units in existence */
- int maxunits; /* current size of array of units */
- int nextid; /* next number to be used for ids */
- int occdeath[MAXUTYPES]; /* buffer for remembering occupant death */
-
- /* Init all the unit entries so we can find them when allocating. */
-
- init_units()
- {
- int i;
-
- maxunits = INITMAXUNITS;
- units = (Unit *) malloc(maxunits * sizeof(Unit));
- for (i = 0; i < maxunits; ++i) units[i].type = NOTHING;
- unitlist = NULL;
- numunits = 0;
- nextid = 0;
- }
-
- /* Create a new unit of given type, with given name. Default all the other */
- /* slots - routines that need to will fill them in properly. This routine */
- /* will return a valid unit or NULL. Note that unit morale starts out at */
- /* max, signifying hopeful but inexperienced recruits marching off to war. */
-
- Unit *
- create_unit(type, name)
- int type;
- char *name;
- {
- int i, r;
- Unit *newunit;
-
- for (i = 0; i < maxunits; ++i) {
- if (units[i].type == NOTHING) break;
- }
- if (i == maxunits) {
- if (grow_unit_array()) {
- return create_unit(type, name);
- } else {
- return NULL;
- }
- }
- newunit = &units[i];
- newunit->type = type;
- if (name == NULL || strlen(name) == 0) {
- newunit->name = NULL;
- } else {
- newunit->name = copy_string(name);
- }
- newunit->x = newunit->y = -1;
- newunit->number = 0;
- newunit->side = NULL;
- newunit->id = nextid++;
- newunit->trueside = NULL;
- newunit->hp = utypes[type].hp;
- newunit->quality = 0;
- newunit->morale = utypes[type].maxmorale;
- newunit->fatigue = 0;
- newunit->product = NOTHING;
- newunit->schedule = 0;
- newunit->built = 0;
- for_all_resource_types(r) newunit->supply[r] = 0;
- newunit->transport = NULL;
- newunit->group = 0;
- newunit->goal = 0;
- newunit->awake = FALSE; /* i.e., is usually not *temporarily* awake */
- newunit->standing = NULL;
- newunit->occupant = NULL;
- newunit->nexthere = NULL;
- link_in_unit(newunit);
- wake_unit(newunit, FALSE);
- ++numunits;
- return newunit;
- }
-
- /* Add unit to front of list. It will be sorted into place shortly. */
-
- link_in_unit(unit)
- Unit *unit;
- {
- unit->next = unitlist;
- unitlist = unit;
- }
-
- /* Attempt to the number of units that can be played. We do the obvious - */
- /* malloc a new array and copy everything over. First copy byte-by-byte, */
- /* then adjust non-NULL pointers to other units. Must also take care of */
- /* any globals or other variables. (such as in side structs) */
-
- /* At the moment, this seems to cause weird changes in unit behavior which */
- /* I don't understand. One missing thing here is to change all the ptrs */
- /* on the world map itself... Another solution would be to do in between */
- /* turns, perhaps after flushing dead units. */
-
- grow_unit_array()
- {
- #ifdef GROWABLE
- char *base, *newbase;
- int newmax, i;
- Unit *newunits;
-
- newmax = maxunits + maxunits / 2;
- if (Debug) printf("Extending unit array to hold %d units.\n", newmax);
- newunits = (Unit *) malloc(newmax * sizeof(Unit));
- base = (char *) units;
- newbase = (char *) newunits;
- for (i = 0; i < maxunits * sizeof(Unit); ++i) newbase[i] = base[i];
- for (i = 0; i < maxunits; ++i) {
- if (newunits[i].next)
- newunits[i].next =
- (Unit *) ((char *) units[i].next + (newbase - base));
- if (newunits[i].nexthere)
- newunits[i].nexthere =
- (Unit *) ((char *) units[i].nexthere + (newbase - base));
- if (newunits[i].transport)
- newunits[i].transport =
- (Unit *) ((char *) units[i].transport + (newbase - base));
- if (newunits[i].occupant)
- newunits[i].occupant =
- (Unit *) ((char *) units[i].occupant + (newbase - base));
- }
- /* Need to patch the map as well, maybe other things? */
- for (i = maxunits; i < newmax; ++i) newunits[i].type = NOTHING;
- free(units);
- maxunits = newmax;
- units = newunits;
- if (unitlist) unitlist = (Unit *) ((char *) unitlist + (newbase - base));
- notify_all("The unit array has been grown.");
- return TRUE;
- #else
- notify_all("Can't make any more units!");
- return FALSE;
- #endif
- }
-
- /* Find a good initial unit for the given side. Only requirement these */
- /* days is that it be of the type specified in the period file, and not */
- /* already used by somebody. If enough failed tries to find one, */
- /* something may be wrong, but not necessarily. */
-
- Unit *
- random_start_unit()
- {
- int tries = numunits;
- Unit *unit;
-
- while (tries-- > 0) {
- unit = &units[random(numunits)];
- if (neutral(unit) && unit->type == period.firstutype) return unit;
- }
- for_all_units(unit) {
- if (neutral(unit) && unit->type == period.firstutype) return unit;
- }
- return NULL;
- }
-
- /* A unit occupies a hex either by entering a unit on that hex or by having */
- /* the occupant pointer filled in. If something goes wrong, return false. */
- /* This is heavily used. */
-
- occupy_hex(unit, x, y)
- Unit *unit;
- int x, y;
- {
- register int u = unit->type, o;
- register Unit *other = unit_at(x, y);
-
- if (other) {
- o = other->type;
- if (could_carry(o, u)) {
- occupy_unit(unit, other);
- } else if (could_carry(u, o)) {
- leave_hex(other);
- occupy_hex(unit, x, y);
- occupy_hex(other, x, y);
- } else {
- return FALSE;
- }
- } else {
- set_unit_at(x, y, unit);
- occupy_hex_aux(unit, x, y);
- all_see_occupy(unit, x, y);
- all_see_hex(x, y);
- }
- return TRUE;
- }
-
- /* Recursive helper to update everybody's position. This should be one of */
- /* two routine that modify unit positions (leaving is the other). */
- /* *Every* occupant will increment viewing coverage - strains realism, */
- /* but prevents strange bugs. */
-
- occupy_hex_aux(unit, x, y)
- Unit *unit;
- int x, y;
- {
- register Unit *occ;
-
- unit->x = x; unit->y = y;
- cover_area(unit, x, y, 1);
- for_all_occupants(unit, occ) occupy_hex_aux(occ, x, y);
- }
-
- /* Decide whether transport has the capability to house the given unit. */
- /* Check both basic capacity and relative volumes. */
-
- can_carry(transport, unit)
- Unit *transport, *unit;
- {
- int u = unit->type, u2 = transport->type, total = 0, volume = 0;
- int hold = utypes[transport->type].holdvolume;
- Unit *occ;
-
- if (transport == unit)
- return FALSE;
- for_all_occupants(transport, occ) {
- if (occ->type == u) total++;
- volume += utypes[occ->type].volume;
- }
- if (cripple(transport)) {
- hold = (hold * transport->hp) / (utypes[u2].crippled + 1);
- }
- return ((total + 1 <= utypes[u2].capacity[u]) &&
- (volume + utypes[u].volume <= hold));
- }
-
- /* Units become passengers by linking into front of transport's passenger */
- /* list. They only wake up if the transport is a moving type, but always */
- /* pick up standing orders if any defined. A passenger will get sorted to */
- /* move after transport, at end of turn. */
-
- occupy_unit(unit, transport)
- Unit *unit, *transport;
- {
- Order *newords;
-
- unit->nexthere = transport->occupant;
- transport->occupant = unit;
- unit->transport = transport;
- if (mobile(transport->type)) wake_unit(unit, FALSE);
- if (transport->standing != NULL) {
- newords = (transport->standing->orders)[unit->type];
- if (newords && newords->type != NONE) {
- if (Debug) printf("%s getting orders %s",
- unit_desig(unit), order_desig(newords));
- copy_orders(&(unit->orders), newords);
- }
- }
- occupy_hex_aux(unit, transport->x, transport->y);
- all_see_occupy(unit, transport->x, transport->y);
- }
-
- /* Unit departs from a hex by zeroing out pointer if in hex or by being */
- /* removed from the list of transport occupants. */
- /* Dead units (hp = 0) may be run through here, so don't error out. */
-
- leave_hex(unit)
- Unit *unit;
- {
- register int ux = unit->x, uy = unit->y;
-
- if (ux < 0 || uy < 0) {
- /* Sometimes called twice */
- } else if (unit->transport != NULL) {
- leave_unit(unit, unit->transport);
- leave_hex_aux(unit);
- all_see_leave(unit, ux, uy);
- } else {
- set_unit_at(ux, uy, NULL);
- if (see_exact(unit->side, ux, uy))
- draw_hex(unit->side, ux, uy);
- /* see_hex(unit->side, ux, uy);*/
- leave_hex_aux(unit);
- all_see_leave(unit, ux, uy);
- all_see_hex(ux, uy);
- }
- }
-
- /* Trash old coordinates (recursively) just in case. Catches many bugs... */
-
- leave_hex_aux(unit)
- Unit *unit;
- {
- register Unit *occ;
-
- cover_area(unit, unit->x, unit->y, -1);
- unit->x = -1; unit->y = -1;
- for_all_occupants(unit, occ) leave_hex_aux(occ);
- }
-
- /* Disembarking unlinks from the list of passengers. */
-
- leave_unit(unit, transport)
- Unit *unit, *transport;
- {
- Unit *occ;
-
- if (unit == transport->occupant) {
- transport->occupant = unit->nexthere;
- } else {
- for_all_occupants(transport, occ) {
- if (unit == occ->nexthere) {
- occ->nexthere = occ->nexthere->nexthere;
- break;
- }
- }
- }
- unit->transport = NULL;
- }
-
- /* Handle the general situation of a unit changing allegiance from one side */
- /* to another. This is a common internal routine, so no messages here. */
-
- unit_changes_side(unit, newside, reason1, reason2)
- Unit *unit;
- Side *newside;
- int reason1, reason2;
- {
- Side *oldside = unit->side;
- Unit *occ;
-
- if (oldside != NULL) {
- oldside->units[unit->type]--;
- if (producing(unit)) oldside->building[unit->product]--;
- if (reason2 >= 0) oldside->balance[unit->type][reason2]++;
- update_state(oldside, unit->type);
- }
- if (newside == NULL && !utypes[unit->type].isneutral) {
- kill_unit(unit, -1);
- } else {
- if (newside != NULL) {
- newside->units[unit->type]++;
- if (producing(unit)) newside->building[unit->product]++;
- if (reason1 >= 0) newside->balance[unit->type][reason1]++;
- update_state(newside, unit->type);
- }
- for_all_occupants(unit, occ) {
- unit_changes_side(occ, newside, reason1, reason2);
- }
- }
- if (alive(unit)) {
- cover_area(unit, unit->x, unit->y, -1);
- assign_unit_to_side(unit, newside);
- cover_area(unit, unit->x, unit->y, 1);
- }
- }
-
- /* Change the product of a unit. Displays will need appropriate mods. */
-
- set_product(unit, type)
- Unit *unit;
- int type;
- {
- if (!neutral(unit) && global.setproduct && type != unit->product) {
- if (unit->product != NOTHING) {
- unit->side->building[unit->product]--;
- update_state(unit->side, unit->product);
- }
- if (type != NOTHING) {
- unit->side->building[type]++;
- update_state(unit->side, type);
- }
- unit->product = type;
- unit->built = 0;
- unit->movesleft--;
- }
- }
-
- /* Set product completion time. Startup development cost incurred only */
- /* if directed. */
- /* Technology development cost only incurred for very first unit that the */
- /* side constructs. Both tech and startup are percent addons. */
-
- set_schedule(unit)
- Unit *unit;
- {
- if (producing(unit)) unit->schedule = build_time(unit, unit->product);
- }
-
- /* Basic routine to compute how long a unit will take to build something. */
-
- build_time(unit, prod)
- Unit *unit;
- int prod;
- {
- int schedule = utypes[unit->type].make[prod];
-
- if (unit->built == 0)
- schedule += ((schedule * utypes[prod].startup) / 100);
- if (unit->side->counts[prod] <= 1)
- schedule += ((schedule * utypes[prod].research) / 100);
- return schedule;
- }
-
- /* 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. */
-
- kill_unit(unit, reason)
- Unit *unit;
- int reason;
- {
- int u = unit->type, u2;
-
- if (alive(unit)) {
- for_all_unit_types(u2) occdeath[u2] = 0;
- leave_hex(unit);
- kill_unit_aux(unit, reason);
- occdeath[u]--;
- }
- }
-
- /* Trash it now - occupant doesn't need to leave_hex. Also record a reason */
- /* for statistics, and update the apropriate display. The unit here should */
- /* be known to be alive. */
-
- kill_unit_aux(unit, reason)
- Unit *unit;
- int reason;
- {
- int u = unit->type;
- Unit *occ;
-
- unit->hp = 0;
- occdeath[u]++;
- if (unit->side != NULL && reason >= 0) {
- unit->side->balance[u][reason]++;
- unit->side->units[u]--;
- if (producing(unit)) unit->side->building[unit->product]--;
- update_state(unit->side, u);
- }
- for_all_occupants(unit, occ) if (alive(occ)) kill_unit_aux(occ, reason);
- }
-
- /* Get rid of all dead units at once. It's important to get rid of all */
- /* of the dead units, so they don't come back and haunt us. */
- /* (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. */
-
- flush_dead_units()
- {
- Unit *unit, *unitprev;
-
- while (unitlist != NULL && !alive(unitlist)) {
- unit = unitlist->next;
- flush_one_unit(unitlist);
- unitlist = unit;
- }
- for_all_units(unitprev) {
- unit = unitprev->next;
- if (unit != NULL && !alive(unit)) {
- unitprev->next = unit->next;
- flush_one_unit(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. */
-
- flush_one_unit(unit)
- Unit *unit;
- {
- unit->type = NOTHING;
- unit->occupant = NULL;
- unit->transport = NULL;
- unit->nexthere = NULL;
- unit->next = NULL;
- --numunits;
- }
-
- /* Do multiple passes of bubble sort. This is intended to improve locality */
- /* among unit positions and reduce the amount of scrolling around. */
- /* Data is generally coherent, so bubble sort not too bad if we allow */
- /* early termination when everything in order. */
- /* If slowness objectionable, replace with something clever. */
-
- sort_units(doall)
- bool doall;
- {
- bool flips = TRUE;
- int passes = 0;
- register Unit *prevunit, *unit, *nextunit;
-
- while (flips && (doall || passes < numunits / 10)) {
- flips = FALSE;
- prevunit = NULL;
- unit = unitlist;
- while (unit != NULL) {
- if (unit->next != NULL && !in_order(unit, unit->next)) {
- flips = TRUE;
- nextunit = unit->next;
- if (prevunit != NULL) prevunit->next = nextunit;
- unit->next = nextunit->next;
- nextunit->next = unit;
- prevunit = nextunit;
- if (unit == unitlist) unitlist = nextunit;
- } else {
- prevunit = unit;
- unit = unit->next;
- }
- }
- passes++;
- }
- if (Debug) printf("Sorting passes = %d\n", passes);
- }
-
- /* This can be pretty lax, with the exception that passengers *must* come */
- /* after their transports, or game saving will screw up. */
-
- in_order(unit1, unit2)
- Unit *unit1, *unit2;
- {
- int d1, d2;
-
- if (side_number(unit1->side) < side_number(unit2->side)) return TRUE;
- if (side_number(unit1->side) > side_number(unit2->side)) return FALSE;
- if (!neutral(unit1)) {
- d1 = distance(unit1->side->cx, unit1->side->cy, unit1->x, unit1->y);
- d2 = distance(unit2->side->cx, unit2->side->cy, unit2->x, unit2->y);
- if (d1 < d2) return TRUE;
- if (d1 > d2) return FALSE;
- }
- if (unit1->y > unit2->y) return TRUE;
- if (unit1->y < unit2->y) return FALSE;
- if (unit1->x < unit2->x) return TRUE;
- if (unit1->x > unit2->x) return FALSE;
- if (unit1->transport == NULL && unit2->transport != NULL) return TRUE;
- if (unit1->transport != NULL && unit2->transport == NULL) return FALSE;
- if (unit1 == unit2->transport) return TRUE;
- if (unit1->transport == unit2) return FALSE;
- return TRUE;
- }
-
- /* A unit runs low on supplies at the halfway point, but only worries about */
- /* the essential items. Formula is the same no matter how/if occupants eat */
- /* transports' supplies. */
-
- low_supplies(unit)
- Unit *unit;
- {
- int u = unit->type, r;
-
- for_all_resource_types(r) {
- if ((utypes[u].consume[r] > 0) || (utypes[u].tomove[r] > 0)) {
- if (2 * unit->supply[r] <= utypes[u].storage[r]) return TRUE;
- }
- }
- return FALSE;
- }
-
- /* Display the standing orders of the given unit. */
-
- show_standing_orders(side, unit)
- Side *side;
- Unit *unit;
- {
- int u;
- Order *ords;
-
- if (unit->standing != NULL) {
- sprintf(spbuf, "Orders: ");
- for_all_unit_types(u) {
- ords = unit->standing->orders[u];
- if (ords && ords->type != NONE) {
- sprintf(tmpbuf, "%s to %s; ",
- utypes[u].name, order_desig(ords));
- strcat(spbuf, tmpbuf);
- }
- }
- notify(side, "%s", spbuf);
- } else {
- notify(side, "No standing orders defined yet.");
- }
- }
-
- /* Build a short phrase describing a given unit to a given side. */
- /* First we supply identification of side, then of unit itself. */
-
- char *
- unit_handle(side, unit)
- Side *side;
- Unit *unit;
- {
- char *utypename;
-
- if (unit == NULL || !alive(unit)) return "???";
- utypename = utypes[unit->type].name;
- if (utypes[unit->type].named == 2 && unit->name) return unit->name;
- if (unit->side == NULL) {
- sprintf(unitbuf, "the neutral ");
- } else if (unit->side == side) {
- sprintf(unitbuf, "your ");
- } else {
- sprintf(unitbuf, "the %s ", unit->side->name);
- }
- if (unit->name != NULL) {
- sprintf(tmpbuf, "%s %s", utypename, unit->name);
- } else if (unit->number > 0) {
- sprintf(tmpbuf, "%s %s", ordinal(unit->number), utypename);
- } else {
- sprintf(tmpbuf, "%s", utypename);
- }
- strcat(unitbuf, tmpbuf);
- return unitbuf;
- }
-
- /* Shorter unit description omits side name, but uses same buffer. */
-
- char *
- short_unit_handle(unit)
- Unit *unit;
- {
- if (unit->name == NULL) {
- sprintf(unitbuf, "%s %s",
- ordinal(unit->number), utypes[unit->type].name);
- } else {
- sprintf(unitbuf, "%s", unit->name);
- }
- return unitbuf;
- }
-
- /* General-purpose routine to take an array of anonymous unit types and */
- /* summarize what's in it, using numbers and unit chars. */
-
- char *
- summarize_units(buf, ucnts)
- char *buf;
- int *ucnts;
- {
- char tmp[BUFSIZE];
- int u;
-
- sprintf(buf, "");
- for_all_unit_types(u) {
- if (ucnts[u] > 0) {
- sprintf(tmp, " %d %c", ucnts[u], utypes[u].uchar);
- strcat(buf, tmp);
- }
- }
- return buf;
- }
-
- /* Search for a unit with the given id number. */
-
- Unit *
- find_unit(n)
- int n;
- {
- Unit *unit;
-
- for_all_units(unit) if (alive(unit) && unit->id == n) return unit;
- return NULL;
- }
-
- /* Given a unit character, find the type. */
-
- find_unit_char(ch)
- char ch;
- {
- int u;
-
- for_all_unit_types(u) if (utypes[u].uchar == ch) return u;
- return NOTHING;
- }
-
- /* Generate a name for a unit, using best acrynomese. This is invoked when */
- /* a new named unit has been created. */
-
- char *
- make_unit_name(unit)
- Unit *unit;
- {
- sprintf(spbuf, "%c%c-%c-%02d",
- uppercase(unit->side->name[0]), uppercase(unit->side->name[1]),
- utypes[unit->type].uchar, unit->number);
- return copy_string(spbuf);
- }
-