|
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; }