|
|
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 m
Length: 22132 (0x5674)
Types: TextFile
Names: »move.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
└─⟦this⟧ »EUUGD18/X/Xconq/move.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: move.c,v 1.1 88/06/21 12:30:23 shebs Exp $ */
/* Nothing happens in xconq without movement, and it involves some rather */
/* complicated control structures in order to get the details right. */
/* Units only actually move by following preset orders; when they appear */
/* to be moving under manual control, they are just following orders that */
/* only last for one move before new orders are requested from the player. */
/* Order of movement is sequential by sides, but non-moving sides are in */
/* survey mode and can do things. */
#include "config.h"
#include "misc.h"
#include "dir.h"
#include "period.h"
#include "side.h"
#include "unit.h"
#include "map.h"
#include "global.h"
/* Complaints about inability to do various things should only appear if */
/* a human player issues the order to an awake unit. */
#define human_order(u) ((u)->side->directorder && humanside((u)->side))
/* Unit can be awake permanently or temporarily. */
#define is_awake(u) ((u)->orders.type == AWAKE || (u)->awake)
bool delaymove; /* true when unit move has been delayed */
bool loopagain; /* flag to go with delaymove flag */
bool randomness; /* true if some unit types move randomly */
int movetries; /* Number of times unit tried to move (temp for mplay) */
Side *curside; /* current side for when turns are sequential */
/* Cache useful details about units (for optimization only). */
init_movement()
{
int u, t;
randomness = FALSE;
for_all_unit_types(u) {
for_all_terrain_types(t) {
if (utypes[u].randommove[t] > 0) randomness = TRUE;
}
}
}
/* The movement phases starts by precomputing theoretical maxima for moves, */
/* then does each side one-by-one. Before iterating, each human side must */
/* be wedged (to ignore input). */
movement_phase()
{
Unit *unit;
Side *side, *side2;
if (Debug) printf("Entering movement phase\n");
compute_moves();
for_all_sides(side) {
side->directorder = FALSE;
side->movunit = NULL;
side->curunit = NULL;
if (humanside(side)) {
} else {
init_machine_turn(side);
}
}
for_all_sides(side) {
if (Debug) printf("--- %s ---\n", side->name);
if (!side->lost) {
curside = side;
for_all_sides(side2) {
if (!humanside(side) && humanside(side2) && !side2->lost)
wedge_mode(side2);
update_sides(side2);
}
loopagain = TRUE;
while (loopagain && !side->lost) {
loopagain = FALSE;
for_all_units(unit) {
if (unit->side == side && !side->lost) {
if (!humanside(side) && change_machine_product(unit)) {
request_new_product(unit);
} else {
make_current(side, unit);
cancel_request(side);
move_mode(side);
move_1(side, unit);
}
}
}
}
}
}
/* For display hacking, also to find misc bugs :-) */
curside = NULL;
for_all_sides(side) {
if (humanside(side) && !side->lost) {
wedge_mode(side);
update_sides(side);
}
}
}
/* Compute moves for all the units at once. */
compute_moves()
{
Unit *unit;
for_all_units(unit) {
unit->movesleft = compute_move(unit);
unit->actualmoves = 0;
}
}
/* Compute number of moves available to the units. This is complicated by */
/* reduction of movement due to damage and the effect of occupants on */
/* mobility. Also, we never let a moving unit have a movement of zero, */
/* since this causes endless difficulties, despite the apparent improvement */
/* in realism. (or allow 0 but round up when possible?) */
/* Moves should be recomputed and possibly adjusted downward after a hit. */
compute_move(unit)
Unit *unit;
{
int u = unit->type, moves = 0;
Unit *occ;
if (!neutral(unit) && (moves = utypes[u].speed) > 0) {
if (cripple(unit)) {
moves = (moves * unit->hp) / (utypes[u].crippled + 1);
}
for_all_occupants(unit, occ) {
if (utypes[u].mobility[occ->type] != 100) {
moves = (moves * utypes[u].mobility[occ->type]) / 100;
break;
}
}
moves = max(1, moves);
}
if (idled(unit) && global.setproduct) moves++;
return moves;
}
/* To move a single unit, keep iterating until all its moves are used up, */
/* or it dies, or its movement has been postponed until the other units */
/* have been done. A single move is either under preset orders, or must be */
/* supplied by a human player, or computed by a machine player. */
move_1(side, unit)
Side *side;
Unit *unit;
{
Side *side2;
if (Debug) printf("%s going to move\n", unit_handle(NULL, unit));
movetries = 0;
while (alive(unit) && unit->movesleft > 0 && !delaymove && !side->lost) {
if (side->mode == MOVE) make_current(side, unit);
if (idled(unit) && global.setproduct) {
request_new_product(unit);
for_all_sides(side2) {
if (humanside(side2) && !side2->lost) {
if (side2 != curside) survey_mode(side2);
request_command(side2);
}
}
handle_requests();
} else if (!is_awake(unit)) {
follow_order(unit);
} else if (humanside(side) && under_control(unit)) {
side->directorder = TRUE;
for_all_sides(side2) {
if (humanside(side2) && !side2->lost) {
if (side2 != curside) {
if (side2->mode != SURVEY) goto_empty_hex(side2);
survey_mode(side2);
}
request_command(side2);
}
}
handle_requests();
} else {
show_info(side);
machine_move(unit);
}
}
if (delaymove) {
delaymove = FALSE;
loopagain = TRUE;
}
}
/* The display and interaction should be mostly shut down. */
wedge_mode(side)
Side *side;
{
int oldmode = side->mode;
if (Debug) printf("%s side in wedge mode\n", side->name);
side->mode = WEDGED;
cancel_request(side);
clear_info(side);
if (side->mode != oldmode) show_timemode(side);
}
/* Switching to move mode involves shifting from wherever the cursor is, */
/* back to the unit that was being moved earlier. */
move_mode(side)
Side *side;
{
int oldmode = side->mode;
if (Debug) printf("%s side in move mode\n", side->name);
side->mode = MOVE;
if (side->movunit != NULL) {
make_current(side, side->movunit);
put_on_screen(side, side->curx, side->cury);
side->movunit = NULL;
}
if (side->mode != oldmode) show_timemode(side);
}
/* Switching to survey mode */
survey_mode(side)
Side *side;
{
int oldmode = side->mode;
if (Debug) printf("%s side in survey mode\n", side->name);
side->mode = SURVEY;
if (side->movunit == NULL) side->movunit = side->curunit;
make_current(side, unit_at(side->curx, side->cury));
if (side->curunit != side->movunit) show_info(side);
if (side->mode != oldmode) show_timemode(side);
}
/* Test if a unit (on a human side) is actually under manual control. */
under_control(unit)
Unit *unit;
{
return probability(utypes[unit->type].control);
}
/* Set the "current" unit of a side - the one being displayed, moved, etc. */
make_current(side, unit)
Side *side;
Unit *unit;
{
if (unit != NULL && alive(unit) &&
(unit->side == side || Debug || Build)) {
side->curunit = unit;
side->curx = unit->x; side->cury = unit->y;
} else {
side->curunit = NULL;
}
}
unowned_p(x, y)
int x, y;
{
Unit *unit = unit_at(x, y);
return (unit == NULL || unit->side != tmpside);
}
goto_empty_hex(side)
Side *side;
{
int x, y;
tmpside = side;
if (search_area(side->curx, side->cury, 20, unowned_p, &x, &y)) {
side->curx = x; side->cury = y;
side->curunit = NULL;
}
}
/* Do single move of a single order for a given unit. */
follow_order(unit)
Unit *unit;
{
bool success = FALSE;
int u = unit->type;
Side *us = unit->side;
if (Debug) printf("%s doing %s with %d moves left\n",
unit_handle(NULL, unit), order_desig(&(unit->orders)),
unit->movesleft);
/* somewhere this has a right home */
/* unit->awake = FALSE; */
switch (unit->orders.type) {
case SENTRY:
if (unit->orders.rept-- > 0) {
unit->movesleft = 0;
success = TRUE;
}
break;
case MOVEDIR:
success = move_dir(unit, unit->orders.p.dir);
break;
case MOVETO:
success = move_to_dest(unit);
break;
case EDGE:
success = follow_coast(unit);
break;
case FOLLOW:
success = follow_leader(unit);
break;
case PATROL:
success = move_patrol(unit);
break;
case AWAKE:
case NONE:
default:
case_panic("order type", unit->orders.type);
}
if (alive(unit)) {
if (success || !humanside(us)) {
unit->movesleft -=
1 + utypes[u].moves[terrain_at(unit->x, unit->y)];
}
if (!us->directorder || success) {
maybe_wakeup(unit);
}
}
us->directorder = FALSE;
if (!success) movetries++;
}
/* Force unit to try to move in given direction. */
move_dir(unit, dir)
Unit *unit;
int dir;
{
if (unit->orders.rept-- > 0) {
return move_to_next(unit, dirx[dir], diry[dir], TRUE, TRUE);
}
return FALSE;
}
/* Have unit try to move to its ordered position. */
move_to_dest(unit)
Unit *unit;
{
return move_to(unit, unit->orders.p.pt[0].x, unit->orders.p.pt[0].y,
(unit->orders.flags & SHORTESTPATH));
}
/* Follow a coast line. This is a version of contour-following, but flaky */
/* because our terrain is discrete rather than continuous. The algorithm */
/* looks backward then goes around in a circle looks for an edge; a hex */
/* the unit can move into, which has an obstacle hex adjacent. */
/* The direction-munging things should be abstracted. */
follow_coast(unit)
Unit *unit;
{
int cw = 1, olddir = unit->orders.p.dir;
int testdir, k;
/* try to go in straight line first (why?) */
if (flip_coin() && direction_works(unit, olddir)) return olddir;
testdir = (olddir - 2 * cw + 6) % 6;
for_all_directions(k) {
if (direction_works(unit, testdir)) {
unit->orders.p.dir = testdir;
return TRUE;
}
testdir = (testdir + cw + 6) % 6;
}
/* If none of the directions work out... */
wake_unit(unit, FALSE);
notify(unit->side, "%s can't figure out where to move to!",
unit_handle(unit->side, unit));
return FALSE;
}
direction_works(unit, dir)
Unit *unit;
int dir;
{
int nx, ny;
nx = wrap(unit->x + dirx[dir]); ny = limit(unit->y + diry[dir]);
if (could_move(unit->type, terrain_at(nx, ny)) &&
adj_obstacle(unit->type, nx, ny)) {
move_dir(unit, dir);
return TRUE;
} else {
return FALSE;
}
}
adj_obstacle(type, x, y)
int type, x, y;
{
int d, x1, y1;
for_all_directions(d) {
x1 = wrap(x + dirx[d]); y1 = limit(y + diry[d]);
if (!could_move(type, terrain_at(x1, y1))) return TRUE;
}
return FALSE;
}
/* Unit attempts to follow its leader around, but not too closely. */
follow_leader(unit)
Unit *unit;
{
Unit *leader = unit->orders.p.leader;
if (!alive(leader)) {
wake_unit(unit, FALSE);
return FALSE;
} else if (abs(unit->x - leader->x) < 2 && abs(unit->y - leader->y) < 2) {
unit->movesleft = 0;
return TRUE;
} else {
return move_to(unit, leader->x, leader->y, FALSE);
}
}
/* Patrol just does move_to, but cycling waypoints around when the first */
/* one has been reached. */
move_patrol(unit)
Unit *unit;
{
int tx, ty;
if (unit->orders.rept-- > 0) {
if (unit->x == unit->orders.p.pt[0].x &&
unit->y == unit->orders.p.pt[0].y) {
tx = unit->orders.p.pt[0].x;
ty = unit->orders.p.pt[0].y;
unit->orders.p.pt[0].x = unit->orders.p.pt[1].x;
unit->orders.p.pt[0].y = unit->orders.p.pt[1].y;
unit->orders.p.pt[1].x = tx;
unit->orders.p.pt[1].y = ty;
}
return move_to(unit, unit->orders.p.pt[0].x, unit->orders.p.pt[0].y,
(unit->orders.flags & SHORTESTPATH));
}
return TRUE;
}
/* Retreat is a special kind of movement. */
/* Veterans should get several tries at retreating to a good place, perhaps */
/* one try per point of "veteranness"? */
retreat_unit(unit)
Unit *unit;
{
int dir;
bool success;
dir = random_dir();
success = move_to_next(unit, dirx[dir], diry[dir], FALSE, FALSE);
return success;
}
/* This weird-looking routine computes next directions for moving to a */
/* given spot. The basic strategy is to prefer to go in the x or y distance */
/* that is the greatest difference, so as to even the two displacements out. */
/* (This leaves more options open if blockage several hexes away.) Make it */
/* probabilistic, so repeated travel will eventually trace the envelope of */
/* possible moves. The number of directions ranges from 1 to 4, depending */
/* on whether there is a straight-line path to the dest, and whether we are */
/* required to take a direct path or are allowed to move in dirs that don't */
/* the unit any closer (we never increase our distance though). */
/* Some trickinesses: world is cylindrical, so must resolve ambiguity about */
/* getting to the same place going either direction (we pick shortest). */
#define left_of(d) (((d) + 5) % 6)
#define rite_of(d) (((d) + 1) % 6)
move_to(unit, tx, ty, shortest)
Unit *unit;
int tx, ty;
bool shortest;
{
bool closer, success;
int dx, dxa, dy, dist, d1, d2, d3, d4, axis = -1, hextant = -1, tmp;
dist = distance(unit->x, unit->y, tx, ty);
dx = tx - unit->x; dy = ty - unit->y;
dxa = (tx + world.width) - unit->x;
if (abs(dx) > abs(dxa)) dx = dxa;
dxa = (tx - world.width) - unit->x;
if (abs(dx) > abs(dxa)) dx = dxa;
if (dx == 0 && dy == 0) {
wake_unit(unit, FALSE);
return FALSE;
}
axis = hextant = -1;
if (dx == 0) {
axis = (dy > 0 ? NE : SW);
} else if (dy == 0) {
axis = (dx > 0 ? EAST : WEST);
} else if (dx == (0 - dy)) {
axis = (dy > 0 ? NW : SE);
} else if (dx > 0) {
hextant = (dy > 0 ? EAST : (abs(dx) > abs(dy) ? SE : SW));
} else {
hextant = (dy < 0 ? WEST : (abs(dx) > abs(dy) ? NW : NE));
}
if (axis >= 0) {
d1 = d2 = axis;
}
if (hextant >= 0) {
d1 = left_of(hextant);
d2 = hextant;
}
d3 = left_of(d1);
d4 = rite_of(d2);
closer = (shortest || dist == 1);
if (flip_coin()) {
tmp = d1; d1 = d2; d2 = tmp;
}
success = move_to_next(unit, dirx[d1], diry[d1], FALSE, 1);
if (!success)
success = move_to_next(unit, dirx[d2], diry[d2], closer, 1);
if (!success && !closer) {
if (opposite_dir(unit->lastdir) == d3) {
success = move_to_next(unit, dirx[d4], diry[d4], TRUE, 1);
} else if (opposite_dir(unit->lastdir) == d4) {
success = move_to_next(unit, dirx[d3], diry[d3], TRUE, 1);
} else {
success = move_to_next(unit, dirx[d3], diry[d3], FALSE, 1);
if (!success)
success = move_to_next(unit, dirx[d4], diry[d4], TRUE, 1);
}
}
return success;
}
/* This function and a couple auxes encode most of the rules about how */
/* units can move. Attempts to move onto other units are handled */
/* by other functions below. Unit will also refuse to move onto the edge of */
/* the map or into the wrong kind of terrain. Otherwise, it succeeds in its */
/* movement and we put it at the new spot. If at any time, the unit */
/* could not move and yet was supposed to, it will wake up. */
move_to_next(unit, dx, dy, mustgo, atk)
Unit *unit;
int dx, dy;
bool mustgo, atk;
{
bool offcourse = FALSE, success = FALSE;
int nx, ny, newdir, utype = unit->type;
Unit *unit2;
Side *us = unit->side;
if (randomness) {
offcourse = (random(10000) <
utypes[utype].randommove[terrain_at(unit->x, unit->y)]);
if (offcourse) {
newdir = random_dir();
dx = dirx[newdir]; dy = diry[newdir];
notify(us, "%s goes off course...", unit_handle(us, unit));
}
}
nx = wrap(unit->x + dx); ny = unit->y + dy;
if ((unit2 = unit_at(nx, ny)) != NULL) {
success = move_to_unit(unit, unit2, dx, dy, mustgo, atk, offcourse);
} else if (!between(1, ny, world.height-2)) {
if (global.leavemap) {
kill_unit(unit, DISBAND);
} else if (offcourse) {
notify(us, "%s has fallen off the edge of the world!",
unit_handle(us, unit));
kill_unit(unit, DISASTER);
} else if (human_order(unit) && mustgo) {
cmd_error(us, "%s can't leave this map!",
unit_handle(us, unit));
}
} else if (!could_move(utype, terrain_at(nx, ny))) {
if (offcourse) {
notify(us, "%s has met with disaster!", unit_handle(us, unit));
kill_unit(unit, DISASTER);
} else if (human_order(unit) && mustgo) {
cmd_error(us, "%s won't go into the %s!",
unit_handle(us, unit), ttypes[terrain_at(nx, ny)].name);
}
} else {
move_unit(unit, nx, ny);
unit->lastdir = find_dir(dx, dy);
success = TRUE;
}
/* Units don't get dead by failing to move, so test not needed here. */
if (!success && mustgo) {
/* unit->awake = TRUE; */
wake_unit(unit, FALSE);
}
return success;
}
/* An enemy unit will be attacked, unless unit is on a transport *and* it */
/* cannot move to that hex anyway. */
/* Also will refuse if hit prob < 10% (can still defend tho) */
/* If the attackee is destroyed, then unit will attempt to move in again. */
/* A friendly transport will be boarded unless it is full. */
/* Blank refusal to move if any other unit. */
/* Allies treat each other's units as their own. */
move_to_unit(unit, unit2, dx, dy, mustgo, atk, offcourse)
Unit *unit, *unit2;
int dx, dy;
bool mustgo, atk, offcourse;
{
int u = unit->type, u2 = unit2->type, u2x = unit2->x, u2y = unit2->y;
Side *us = unit->side;
if (!allied_side(us, unit2->side)) {
if (unit->transport != NULL && impassable(unit, u2x, u2y)) {
if (human_order(unit) && mustgo) {
cmd_error(us, "%s can't attack there!", unit_handle(us, unit));
}
} else if (side_view(us, u2x, u2y) == EMPTY) {
notify(us, "%s spots something!", unit_handle(us, unit));
see_exact(us, u2x, u2y);
draw_hex(us, u2x, u2y, TRUE);
} else if (atk && (unit->orders.flags & ATTACKUNIT)) {
if (!could_hit(u, u2) && !could_capture(u, u2)) {
if (human_order(unit) && mustgo) {
cmd_error(us, "%s refuses to attack a %s!",
unit_handle(us, unit), utypes[u2].name);
}
} else if (!enough_ammo(unit, unit2)) {
if (human_order(unit) && mustgo) {
cmd_error(us, "%s is out of ammo!", unit_handle(us, unit));
}
} else {
if (attack(unit, unit2)) {
/* if battle won, can try moving again */
return move_to_next(unit, dx, dy, FALSE, FALSE);
}
return TRUE;
}
} else {
/* here if aircraft blocked on return, etc - no action needed */
}
} else if (could_carry(u2, u)) {
if (!can_carry(unit2, unit)) {
if (human_order(unit) && mustgo) {
cmd_error(us, "%s is full already!", unit_handle(us, unit2));
}
} else {
move_unit(unit, u2x, u2y);
unit->movesleft -= utypes[u].entertime[u2];
unit->lastdir = find_dir(dx, dy);
return TRUE;
}
} else if (could_carry(u, u2)) {
if (impassable(unit, u2x, u2y)) {
if (human_order(unit) && mustgo) {
cmd_error(us, "%s can't pick up anybody there!",
unit_handle(us, unit));
}
} else if (!can_carry(unit, unit2)) {
if (human_order(unit) && mustgo) {
cmd_error(us, "%s is full already!", unit_handle(us, unit));
}
} else if (unit->orders.flags == 0) {
/* blow out at the bottom */
} else {
move_unit(unit, u2x, u2y);
unit->lastdir = find_dir(dx, dy);
return TRUE;
}
} else {
if (offcourse) {
notify(us, "%s is involved in a wreck!", unit_handle(us, unit));
kill_unit(unit, DISASTER);
kill_unit(unit2, DISASTER);
} else if (human_order(unit) && mustgo) {
cmd_error(us, "%s refuses to attack its friends!",
unit_handle(us, unit));
}
}
return FALSE;
}
/* Perform the act of moving proper. (very simple, never fails) */
/* This is also the right place to put in anything that happens if the *
/* unit actually changes its location. */
move_unit(unit, nx, ny)
Unit *unit;
int nx, ny;
{
Side *us = unit->side;
if (producing(unit) && !utypes[unit->type].maker) {
notify(us, "%s moved, cancelling its build!", unit_handle(us, unit));
if (active_display(us) && humanside(us)) beep(us);
set_product(unit, NOTHING);
unit->schedule = 0;
}
leave_hex(unit);
occupy_hex(unit, nx, ny);
consume_move_supplies(unit);
}
/* This routine is too strict, doesn't account for resupply at start of next */
/* turn. Hacked to check on transport at least. */
consume_move_supplies(unit)
Unit *unit;
{
int u = unit->type, r;
unit->actualmoves++;
for_all_resource_types(r) {
if (utypes[u].tomove[r] > 0) {
unit->supply[r] -= utypes[u].tomove[r];
if (unit->supply[r] <= 0 && unit->transport == NULL) {
exhaust_supply(unit);
return;
}
}
}
}
/* When doing a survey, you can move the cursor anywhere and it will show */
/* what is at that hex, but not give away too much! */
move_survey(side, nx, ny)
Side *side;
int nx, ny;
{
if (between(1, ny, world.height-2)) {
side->curx = nx; side->cury = ny;
make_current(side, unit_at(side->curx, side->cury));
} else {
if (active_display(side)) beep(side);
}
put_on_screen(side, side->curx, side->cury);
show_info(side);
}
/* This routine encodes nearly all of the conditions under which a unit */
/* following orders might wake up and request new instructions. */
maybe_wakeup(unit)
Unit *unit;
{
if (unit->orders.rept <= 0) {
wake_unit(unit, FALSE);
} else if ((unit->orders.flags & SUPPLYWAKE) && low_supplies(unit)) {
wake_unit(unit, TRUE);
unit->orders.flags &= ~SUPPLYWAKE;
} else if ((unit->orders.flags & ENEMYWAKE) && adj_enemy(unit)) {
wake_unit(unit, FALSE);
}
}
/* True if unit is adjacent to an unfriendly. */
adj_enemy(unit)
Unit *unit;
{
int d, x, y, view;
for_all_directions(d) {
x = wrap(unit->x + dirx[d]); y = unit->y + diry[d];
if (!neutral(unit)) {
view = side_view(unit->side, x, y);
if (view != EMPTY && enemy_side(side_n(vside(view)), unit->side))
return TRUE;
}
}
return FALSE;
}