DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T u

⟦83acd542a⟧ TextFile

    Length: 19503 (0x4c2f)
    Types: TextFile
    Names: »unit.c«

Derivation

└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
    └─⟦this⟧ »EUUGD18/X/Xconq/unit.c« 

TextFile

/* 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. */

/* RCS $Header: unit.c,v 1.1 88/06/21 12:30:44 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;
    
    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 *neworders;

    unit->nexthere = transport->occupant;
    transport->occupant = unit;
    unit->transport = transport;
    if (mobile(transport->type)) wake_unit(unit, FALSE);
    if (transport->standing != NULL) {
	neworders = (transport->standing->orders)[unit->type];
	if (neworders->type != NONE) {
	    if (Debug) printf("%s getting orders %s",
			      unit_handle(NULL, unit), order_desig(neworders));
	    copy_orders(&(unit->orders), neworders);
	}
    }
    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);
	see_exact(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;
{
    int schedule, prod = unit->product;

    if (producing(unit)) {
	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);
	unit->schedule = 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()
{
    bool flips = TRUE;
    int passes = 0;
    register Unit *prevunit, *unit, *nextunit;

    while (flips && 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;

    if (unit->standing != NULL) {
	sprintf(spbuf, "Orders:  ");
	for_all_unit_types(u) {
	    if (unit->standing->orders[u]->type != NONE) {
		sprintf(tmpbuf, "%s to %s;  ",
			utypes[u].name,
			order_desig(unit->standing->orders[u]));
		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 = utypes[unit->type].name;

    if (unit == NULL || !alive(unit)) return "???";
    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 (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);
}