|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T u
Length: 19503 (0x4c2f) Types: TextFile Names: »unit.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/X/Xconq/unit.c«
/* 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); }