src/lib/subs/attsub.c

Go to the documentation of this file.
00001 /*
00002  *  Empire - A multi-player, client/server Internet based war game.
00003  *  Copyright (C) 1986-2007, Dave Pare, Jeff Bailey, Thomas Ruschak,
00004  *                           Ken Stevens, Steve McClure
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00019  *
00020  *  ---
00021  *
00022  *  See files README, COPYING and CREDITS in the root of the source
00023  *  tree for related information and legal notices.  It is expected
00024  *  that future projects/authors will amend these files as needed.
00025  *
00026  *  ---
00027  *
00028  *  attsub.c: Attack subroutines
00029  * 
00030  *  Known contributors to this file:
00031  *     Ken Stevens, 1995
00032  *     Steve McClure, 1996-2000
00033  *     Markus Armbruster, 2006
00034  */
00035 
00036 #include <config.h>
00037 
00038 #include <ctype.h>
00039 #include <math.h>
00040 #include "combat.h"
00041 #include "file.h"
00042 #include "map.h"
00043 #include "misc.h"
00044 #include "mission.h"
00045 #include "nsc.h"
00046 #include "optlist.h"
00047 #include "path.h"
00048 #include "plague.h"
00049 #include "player.h"
00050 #include "prototypes.h"
00051 #include "xy.h"
00052 #include "empobj.h"
00053 #include "unit.h"
00054 
00055 #define CASUALTY_LUMP   1       /* How big casualty chunks should be */
00056 
00057 static void ask_olist(int combat_mode, struct combat *off,
00058                       struct combat *def, struct emp_qelem *olist,
00059                       char *land_answer, int *a_spyp, int *a_engineerp);
00060 static void take_move_in_mob(int combat_mode, struct ulist *llp,
00061                              struct combat *off, struct combat *def);
00062 static void move_in_land(int combat_mode, struct combat *off,
00063                          struct emp_qelem *olist, struct combat *def);
00064 static void ask_move_in(struct combat *off, struct emp_qelem *olist,
00065                         struct combat *def);
00066 static void ask_move_in_off(struct combat *off, struct combat *def);
00067 
00068 static int board_abort(struct combat *off, struct combat *def);
00069 static int land_board_abort(struct combat *off, struct combat *def);
00070 static int ask_off(int combat_mode, struct combat *off,
00071                    struct combat *def);
00072 static void get_dlist(struct combat *def, struct emp_qelem *list, int a_spy,
00073                       int *d_spyp);
00074 static int get_ototal(int combat_mode, struct combat *off,
00075                       struct emp_qelem *olist, double osupport, int check);
00076 static int get_dtotal(struct combat *def, struct emp_qelem *list,
00077                       double dsupport, int check);
00078 static double att_calcodds(int, int);
00079 static int take_casualty(int combat_mode, struct combat *off,
00080                          struct emp_qelem *olist);
00081 
00082 static void send_reacting_units_home(struct emp_qelem *list);
00083 static int take_def(int combat_mode, struct emp_qelem *list,
00084                     struct combat *off, struct combat *def);
00085 
00086 static int get_land(int combat_mode, struct combat *def, int uid,
00087                     struct ulist *llp, int victim_land);
00088 
00089 char *att_mode[] = {
00090     /* must match combat types in combat.h */
00091     "defend", "attack", "assault", "paradrop", "board", "lboard"
00092 };
00093 
00094 
00095 /*
00096  * The principal object in this code is the "combat" object.  A combat object
00097  * is either a sector or ship.  There are
00098  * usually two instances of this, the "def" or defense combat object, and
00099  * the array of "off" or offense objects.  The number of offense objects is
00100  * determined by the value of off->last (e.g. more than one attacking sector).
00101  * the type of the object is determined by combat->type which can take the
00102  * values EF_SECTOR, EF_SHIP, EF_PLANE, or EF_BAD.  Another important parameter
00103  * which is often passed to these functions is combat_mode.  This can take
00104  * the value A_DEFEND, A_ATTACK, A_ASSAULT, A_PARA, A_BOARD and A_LBOARD.
00105  * As these six modes of being in combat affect things like mobcost and combat
00106  * value, there are often switches made on combat_mode.  Note that in all cases
00107  * no mobility is taken from sectors, ships, or land units until the player
00108  * has committed to a fight.  Instead, the cost is temporarily placed in
00109  * combat->mobcost, or llp->mobil as the case may be, and then when the object
00110  * is "put" back onto disk, then the amounts in these variables are subtracted
00111  * from the object's mobility.  It needs to be done this way as the objects
00112  * are constantly being re-read from disk, and we don't want to take any mob
00113  * unless a fight actually occurrs.
00114  * -Ken Stevens
00115  */
00116 
00117 /* initialize combat object */
00118 
00119 int
00120 att_combat_init(struct combat *com, int type)
00121 {
00122     memset(com, 0, sizeof(*com));
00123     com->type = type;
00124     return type;
00125 }
00126 
00127 /* print a combat object with optional preposition */
00128 
00129 static char *
00130 pr_com(int inon, struct combat *com, natid who)
00131 {
00132     if (com->type == EF_SECTOR) {
00133         return prbuf("%s%s",
00134                      inon ? inon == 1 ? "in " : "into " : "",
00135                      xyas(com->x, com->y, who));
00136     } else if (com->type == EF_SHIP) {
00137         return prbuf("%s%s %s(#%d)",
00138                      inon ? inon == 1 ? "on " : "onto " : "",
00139                      com->shp_mcp->m_name, com->shp_name,
00140                      com->shp_uid);
00141     } else if (com->type == EF_LAND) {
00142         return prbuf("%s%s #%d",
00143                      inon ? inon == 1 ? "on " : "onto " : "",
00144                      com->lnd_lcp->l_name, com->lnd_uid);
00145     } else {
00146         return "your forces";
00147     }
00148 }
00149 
00150 static char *
00151 prcom(int inon, struct combat *com)
00152 {
00153     return pr_com(inon, com, player->cnum);
00154 }
00155 
00156 /*
00157  * This is the combat object "type" based integrity check.  It basically
00158  * splits along three divisions: ship/sector, attacker/defender, 
00159  * first time/not first time.
00160  */
00161 
00162 int
00163 att_get_combat(struct combat *com, int isdef)
00164 {
00165     struct sctstr sect;
00166     struct shpstr ship;
00167     struct lndstr land;
00168     int pstage;
00169     natid owner;
00170     int mil;
00171     int eff;
00172     int mob;
00173     coord x, y;
00174 
00175     switch (com->type) {
00176     case EF_SECTOR:
00177         if (!getsect(com->x, com->y, &sect)) {
00178             pr("Bad sector: %s\n", xyas(com->x, com->y, player->cnum));
00179             return att_combat_init(com, EF_BAD);
00180         }
00181         com->sct_type = sect.sct_type;
00182         com->sct_dcp = &dchr[sect.sct_type];
00183         mil = sect.sct_item[I_MILIT];
00184         pstage = sect.sct_pstage;
00185         owner = sect.sct_own;
00186         eff = sect.sct_effic;
00187         mob = sect.sct_mobil;
00188         x = com->x;
00189         y = com->y;
00190         break;
00191     case EF_LAND:
00192         if (!getland(com->lnd_uid, &land) || !land.lnd_own) {
00193             if (isdef)
00194                 pr("Land unit #%d is not in the same sector!\n",
00195                    com->lnd_uid);
00196             return att_combat_init(com, EF_BAD);
00197         }
00198         if (isdef && player->owner) {
00199             pr("Boarding yourself?  Try using the 'load' command.\n");
00200             return att_combat_init(com, EF_BAD);
00201         }
00202         com->lnd_lcp = &lchr[(int)land.lnd_type];
00203         mil = land.lnd_item[I_MILIT];
00204         pstage = land.lnd_pstage;
00205         owner = land.lnd_own;
00206         eff = land.lnd_effic;
00207         mob = land.lnd_mobil;
00208         x = land.lnd_x;
00209         y = land.lnd_y;
00210         break;
00211     case EF_SHIP:
00212         if (!getship(com->shp_uid, &ship) || !ship.shp_own) {
00213             if (isdef)
00214                 pr("Ship #%d is not in the same sector!\n", com->shp_uid);
00215             else
00216                 pr("Ship #%d is not your ship!\n", com->shp_uid);
00217             return att_combat_init(com, EF_BAD);
00218         }
00219         if (opt_MARKET) {
00220             if (isdef && player->owner &&
00221                 ontradingblock(EF_SHIP, &ship)) {
00222                 pr("%s is on the trading block.\n", prcom(0, com));
00223                 return att_combat_init(com, EF_BAD);
00224             }
00225         }
00226         if (isdef && player->owner) {
00227             pr("Boarding yourself?  Try using the 'tend' command.\n");
00228             return att_combat_init(com, EF_BAD);
00229         }
00230         com->shp_mcp = &mchr[(int)ship.shp_type];
00231         strncpy(com->shp_name, ship.shp_name, MAXSHPNAMLEN);
00232         if (!isdef && !player->owner) {
00233             if (com->set)
00234                 pr("%s was just sunk!\n", prcom(0, com));
00235             else
00236                 pr("Ship #%d is not your ship!\n", com->shp_uid);
00237             return att_combat_init(com, EF_BAD);
00238         }
00239         mil = ship.shp_item[I_MILIT];
00240         pstage = ship.shp_pstage;
00241         owner = ship.shp_own;
00242         eff = ship.shp_effic;
00243         mob = ship.shp_mobil;
00244         x = ship.shp_x;
00245         y = ship.shp_y;
00246         break;
00247     case EF_PLANE:
00248         return com->mil;
00249     case EF_BAD:
00250         return EF_BAD;
00251     default:
00252         return att_combat_init(com, EF_BAD);
00253     }
00254 
00255     if (!com->set) {            /* first time */
00256         if (isdef) {            /* defender */
00257             com->troops = mil;
00258         } else {                /* attacker */
00259             if (!mil)
00260                 pr("No mil %s\n", prcom(1, com));
00261             else if (mil == 1)
00262                 pr("Only 1 mil %s\n", prcom(1, com));
00263             /* don't abandon attacking sectors or ships */
00264             com->troops = MAX(0, mil - 1);
00265         }
00266         com->plague = pstage == PLG_INFECT;
00267     } else {                    /* not first time */
00268         if (isdef) {            /* defender */
00269             if (com->x != x || com->y != y) {
00270                 pr("%s has moved!\n", prcom(0, com));
00271                 return att_combat_init(com, EF_BAD);
00272             }
00273             if (owner != com->own) {
00274                 if (owner) {
00275                     pr("WARNING: The ownership of %s just changed from %s to %s!\n",
00276                        prcom(0, com), cname(com->own), cname(owner));
00277                 } else if (com->type == EF_SECTOR) {
00278                     pr("WARNING: %s just abandoned sector %s!\n",
00279                        cname(com->own),
00280                        xyas(com->x, com->y, player->cnum));
00281                 }
00282             }
00283             if (com->mil != mil)
00284                 pr("WARNING: The enemy mil %s just %s from %d to %d!\n",
00285                    prcom(1, com),
00286                    com->mil < mil ? "increased" : "decreased", com->mil,
00287                    mil);
00288             com->troops = mil;
00289         } else {                /* attacker */
00290             if (owner != player->cnum && getrel(getnatp(owner), player->cnum) != ALLIED) {
00291                 /* must be EF_SECTOR */
00292                 if (com->mil)
00293                     pr("WARNING: Your %d mil in %s were destroyed because %s just took the sector!\n",
00294                        com->mil, xyas(com->x, com->y, player->cnum),
00295                        cname(owner));
00296                 else
00297                     pr("You no longer own %s\n",
00298                        xyas(com->x, com->y, player->cnum));
00299                 return att_combat_init(com, EF_BAD);
00300             }
00301             if (com->troops && com->troops + 1 > mil) {
00302                 if (com->own == owner && player->cnum == owner)
00303                     /* not a takeover */
00304                     pr("WARNING: Your mil %s has been reduced from %d to %d!\n",
00305                        prcom(1, com), com->troops, MAX(0, mil - 1));
00306                 com->troops = MAX(0, mil - 1);
00307             }
00308         }
00309     }
00310     com->set = 1;
00311     com->mil = mil;
00312     com->own = owner;
00313     com->x = x;
00314     com->y = y;
00315     com->eff = eff;
00316     com->mob = mob;
00317     return com->troops;
00318 }
00319 
00320 /*
00321  * In the course of the fight, the combat object may have lost mil, eff, or
00322  * mobility.  This is the place where the data in the object gets flushed to
00323  * disk to make it "real".
00324  */
00325 
00326 static void
00327 put_combat(struct combat *com)
00328 {
00329     struct sctstr sect;
00330     struct shpstr ship;
00331     struct lndstr land;
00332     int deff;
00333 
00334     switch (com->type) {
00335     case EF_SECTOR:
00336         getsect(com->x, com->y, &sect);
00337         sect.sct_type = com->sct_type;
00338         deff = sect.sct_effic - com->eff;
00339         if (deff > 0) {
00340             sect.sct_road -= sect.sct_road * deff / 100.0;
00341             sect.sct_rail -= sect.sct_rail * deff / 100.0;
00342             sect.sct_defense -= sect.sct_defense * deff / 100.0;
00343             if (sect.sct_road <= 0)
00344                 sect.sct_road = 0;
00345             if (sect.sct_rail <= 0)
00346                 sect.sct_rail = 0;
00347             if (sect.sct_defense <= 0)
00348                 sect.sct_defense = 0;
00349         }
00350         sect.sct_effic = com->eff;
00351         if (com->mobcost) {
00352             if (opt_MOB_ACCESS) {
00353                 if ((com->mob - com->mobcost) < -127)
00354                     sect.sct_mobil = -127;
00355                 else
00356                     sect.sct_mobil = (short)(com->mob - com->mobcost);
00357             } else {
00358                 if ((com->mob - com->mobcost) < 0)
00359                     sect.sct_mobil = 0;
00360                 else
00361                     sect.sct_mobil = (short)(com->mob - com->mobcost);
00362             }
00363         }
00364         makelost(EF_SECTOR, sect.sct_own, 0, sect.sct_x, sect.sct_y);
00365         makenotlost(EF_SECTOR, com->own, 0, sect.sct_x, sect.sct_y);
00366         sect.sct_own = com->own;
00367         if (com->plague) {
00368             if (sect.sct_pstage == PLG_HEALTHY)
00369                 sect.sct_pstage = PLG_EXPOSED;
00370         }
00371         sect.sct_item[I_MILIT] = com->mil;
00372         putsect(&sect);
00373         com->own = sect.sct_own;        /* avoid WARNING if sector reverts */
00374         break;
00375     case EF_LAND:
00376         getland(com->lnd_uid, &land);
00377         land.lnd_effic = com->eff;
00378         if (com->mobcost) {
00379             if (com->mob - com->mobcost < -127)
00380                 land.lnd_mobil = -127;
00381             else
00382                 land.lnd_mobil = (signed char)(com->mob - com->mobcost);
00383         }
00384         makelost(EF_LAND, land.lnd_own, land.lnd_uid,
00385                  land.lnd_x, land.lnd_y);
00386         land.lnd_own = com->own;
00387         makenotlost(EF_LAND, land.lnd_own, land.lnd_uid,
00388                     land.lnd_x, land.lnd_y);
00389         if (com->plague) {
00390             if (land.lnd_pstage == PLG_HEALTHY)
00391                 land.lnd_pstage = PLG_EXPOSED;
00392         }
00393         if (!(com->lnd_lcp->l_flags & L_SPY))
00394             land.lnd_item[I_MILIT] = com->mil;
00395         lnd_count_units(&land);
00396         if (com->own == player->cnum) {
00397             land.lnd_mission = 0;
00398             land.lnd_rflags = 0;
00399             memset(land.lnd_rpath, 0, sizeof(land.lnd_rpath));
00400         }
00401         putland(com->lnd_uid, &land);
00402         break;
00403     case EF_SHIP:
00404         getship(com->shp_uid, &ship);
00405         ship.shp_effic = com->eff;
00406         if (com->mobcost) {
00407             if (com->mob - com->mobcost < -127)
00408                 ship.shp_mobil = -127;
00409             else
00410                 ship.shp_mobil = (signed char)(com->mob - com->mobcost);
00411         }
00412         makelost(EF_SHIP, ship.shp_own, ship.shp_uid,
00413                  ship.shp_x, ship.shp_y);
00414         ship.shp_own = com->own;
00415         makenotlost(EF_SHIP, ship.shp_own, ship.shp_uid,
00416                     ship.shp_x, ship.shp_y);
00417         if (com->plague) {
00418             if (ship.shp_pstage == PLG_HEALTHY)
00419                 ship.shp_pstage = PLG_EXPOSED;
00420         }
00421         ship.shp_item[I_MILIT] = com->mil;
00422         count_units(&ship);
00423         if (com->own == player->cnum) {
00424             ship.shp_mission = 0;
00425             ship.shp_rflags = 0;
00426             memset(ship.shp_rpath, 0, sizeof(ship.shp_rpath));
00427         }
00428         putship(com->shp_uid, &ship);
00429     }
00430     com->mobcost = 0;
00431     att_get_combat(com, com->own != player->cnum);
00432 }
00433 
00434 /* If pre-attack, abort fight.  If post-attack, don't move anything in */
00435 
00436 static int
00437 abort_attack(void)
00438 {
00439     return player->aborted = 1;
00440 }
00441 
00442 /*
00443  * This is the combat_mode based integrity check.  It splits among two main
00444  * divisions: first time/not first time, and attack/assault/para/board.
00445  */
00446 
00447 int
00448 att_abort(int combat_mode, struct combat *off, struct combat *def)
00449 {
00450     struct sctstr sect;
00451     int rel;
00452     char y_or_n[512];
00453     struct natstr *natp;
00454 
00455     if (player->aborted)
00456         return 1;
00457     if (att_get_combat(def, 1) < 0)
00458         return abort_attack();
00459 
00460     if (off && combat_mode != A_ATTACK) {
00461         if (att_get_combat(off, 0) < 0)
00462             return abort_attack();
00463         if (off->type == EF_SHIP &&
00464             (!getsect(off->x, off->y, &sect) ||
00465              sect.sct_type != SCT_WATER)) {
00466             pr("%s can not %s from that far inland!\n",
00467                prcom(0, off), att_mode[combat_mode]);
00468             return abort_attack();
00469         }
00470     }
00471     switch (combat_mode) {
00472     case A_ATTACK:
00473         if (!neigh(def->x, def->y, player->cnum) &&
00474             !adj_units(def->x, def->y, player->cnum)) {
00475             pr("You are not adjacent to %s\n",
00476                xyas(def->x, def->y, player->cnum));
00477             return abort_attack();
00478         }
00479         if (def->own == player->cnum) {
00480             pr("You can't attack your own sector.\n");
00481             return abort_attack();
00482         }
00483         break;
00484     case A_ASSAULT:
00485         if (off && mapdist(off->x, off->y, def->x, def->y) > 1) {
00486             pr("You'll have to get there first...\n");
00487             return abort_attack();
00488         }
00489         if (off && def->sct_type == SCT_MOUNT) {
00490             pr("You can't assault a %s sector!\n", def->sct_dcp->d_name);
00491             return abort_attack();
00492         }
00493         break;
00494     case A_PARA:
00495         if (def->own == player->cnum) {
00496             pr("You can't air-assault your own sector.\n");
00497             return abort_attack();
00498         }
00499         if (off && (def->sct_type == SCT_MOUNT ||
00500                     def->sct_type == SCT_WATER ||
00501                     def->sct_type == SCT_CAPIT ||
00502                     def->sct_type == SCT_FORTR ||
00503                     def->sct_type == SCT_WASTE)) {
00504             pr("You can't air-assault a %s sector!\n",
00505                def->sct_dcp->d_name);
00506             return abort_attack();
00507         }
00508         break;
00509     case A_BOARD:
00510         return board_abort(off, def);
00511     case A_LBOARD:
00512         return land_board_abort(off, def);
00513     }
00514 
00515     if (off && def->sct_dcp->d_mob0 < 0) {
00516         pr("You can't %s a %s sector!\n",
00517            att_mode[combat_mode], def->sct_dcp->d_name);
00518         return abort_attack();
00519     }
00520     if (!off || off->relations_checked)
00521         return 0;
00522     off->relations_checked = 1;
00523 
00524     if (opt_HIDDEN) {
00525         setcont(player->cnum, def->own, FOUND_SPY);
00526         setcont(def->own, player->cnum, FOUND_SPY);
00527     }
00528     if (opt_SLOW_WAR && def->own != player->cnum) {
00529         natp = getnatp(player->cnum);
00530         rel = getrel(natp, def->own);
00531 
00532         if (rel == ALLIED) {
00533             sprintf(y_or_n, "Sector is owned by %s, your ally, %s [yn]? ",
00534                     cname(def->own), att_mode[combat_mode]);
00535             if (!confirm(y_or_n))
00536                 return abort_attack();
00537 
00538         }
00539         if ((rel != AT_WAR) && (def->own) &&
00540             (sect.sct_oldown != player->cnum)) {
00541             pr("You're not at war with them!\n");
00542             return abort_attack();
00543         }
00544     }
00545     return 0;
00546 }
00547 
00548 /*
00549  * Lots of special things need to be checked for boarding, so I put it in
00550  * it's own function.
00551  */
00552 
00553 static int
00554 board_abort(struct combat *off, struct combat *def)
00555 {
00556     struct shpstr aship, dship; /* for tech levels */
00557     struct sctstr sect;
00558 
00559     if (att_get_combat(def, 1) < 0)
00560         return abort_attack();
00561 
00562     if (!off)
00563         return 0;
00564 
00565     if (att_get_combat(off, 0) < 0)
00566         return abort_attack();
00567 
00568     if (off->x != def->x || off->y != def->y) {
00569         pr("Ship #%d is not in the same sector!\n", def->shp_uid);
00570         return abort_attack();
00571     }
00572     if (off->type == EF_SHIP) {
00573         if (off->mob <= 0) {
00574             pr("%s has no mobility!\n", prcom(0, off));
00575             return abort_attack();
00576         }
00577         getship(off->shp_uid, &aship);
00578         getship(def->shp_uid, &dship);
00579         if (techfact(aship.shp_tech, 1.0) * aship.shp_speed * off->eff
00580             <= techfact(dship.shp_tech, 1.0) * dship.shp_speed * def->eff) {
00581             pr("Victim ship moves faster than you do!\n");
00582             if (def->own)
00583                 wu(0, def->own,
00584                    "%s (#%d) %s failed to catch %s\n",
00585                    cname(aship.shp_own), aship.shp_own,
00586                    pr_com(0, off, def->own), pr_com(0, def, def->own));
00587             return abort_attack();
00588         }
00589     } else if (off->type != EF_SECTOR) {
00590         pr("Please tell the deity that you got the 'banana boat' error\n");
00591         return abort_attack();
00592     }
00593     if (def->shp_mcp->m_flags & M_SUB) {
00594         getsect(def->x, def->y, &sect);
00595         if (sect.sct_type == SCT_WATER) {
00596             pr("You can't board a submarine!\n");
00597             return abort_attack();
00598         }
00599     }
00600     return 0;
00601 }
00602 
00603 /*
00604  * Lots of special things need to be checked for boarding, so I put it in
00605  * it's own function.
00606  * STM - I copied it for land unit boarding. :)
00607  */
00608 
00609 static int
00610 land_board_abort(struct combat *off, struct combat *def)
00611 {
00612     if (att_get_combat(def, 1) < 0)
00613         return abort_attack();
00614 
00615     if (!off)
00616         return 0;
00617 
00618     if (att_get_combat(off, 0) < 0)
00619         return abort_attack();
00620 
00621     if (off->x != def->x || off->y != def->y) {
00622         pr("Land unit #%d is not in the same sector!\n", def->lnd_uid);
00623         return abort_attack();
00624     }
00625 
00626     return 0;
00627 }
00628 
00629 /* If we are boarding, then the defending ship gets a chance to fire back */
00630 int
00631 att_approach(struct combat *off, struct combat *def)
00632 {
00633     int dam;
00634     struct sctstr sect;
00635     struct shpstr ship;
00636 
00637     pr("Approaching %s...\n", prcom(0, def));
00638     if (def->own)
00639         wu(0, def->own,
00640            "%s is being approached by %s...\n",
00641            pr_com(0, def, def->own), pr_com(0, off, def->own));
00642     if (!(dam = shipdef(player->cnum, def->own, def->x, def->y)))
00643         return 0;
00644 
00645     pr("They're firing at us sir!\n");
00646     if (def->own) {
00647         wu(0, def->own,
00648            "Your fleet at %s does %d damage to %s\n",
00649            xyas(def->x, def->y, def->own), dam, pr_com(0, off, def->own));
00650     }
00651     if (off->type == EF_SECTOR) {
00652         getsect(off->x, off->y, &sect);
00653         sectdamage(&sect, dam, 0);
00654         putsect(&sect);
00655         pr("Enemy fleet at %s does %d damage to %s\n",
00656            xyas(def->x, def->y, player->cnum), dam, prcom(0, off));
00657     } else if (off->type == EF_SHIP) {
00658         getship(off->shp_uid, &ship);
00659         shipdamage(&ship, dam);
00660         putship(off->shp_uid, &ship);
00661         if (def->own && ship.shp_effic < SHIP_MINEFF) {
00662             wu(0, def->own, "%s sunk!\n", pr_com(0, off, def->own));
00663             nreport(player->cnum, N_SHP_LOSE, def->own, 1);
00664         }
00665     }
00666     if (att_get_combat(off, 0) < 0)
00667         return abort_attack();
00668     return 0;
00669 }
00670 
00671 /* The attack is valid.  Tell the attacker about what they're going to hit */
00672 
00673 int
00674 att_show(struct combat *def)
00675 {
00676     /* Note that we tell the player about the treaty BEFORE we tell them
00677        about the item.  If we didn't, then they gain free information */
00678     if (def->type == EF_SECTOR) {
00679         if (!trechk(player->cnum, def->own, LANATT))
00680             return abort_attack();
00681         pr("%s is a %d%% %s %s with approximately %d military.\n",
00682            xyas(def->x, def->y, player->cnum),
00683            roundintby((int)def->eff, 10),
00684            cname(def->own), def->sct_dcp->d_name,
00685            roundintby(def->troops, 10));
00686         if (map_set(player->cnum, def->x, def->y, def->sct_dcp->d_mnem, 0))
00687             writemap(player->cnum);
00688     } else if (def->type == EF_SHIP || def->type == EF_LAND) {
00689         if (def->type == EF_SHIP) {
00690             if (!trechk(player->cnum, def->own, SEAATT))
00691                 return abort_attack();
00692         } else {
00693             if (!trechk(player->cnum, def->own, LNDATT))
00694                 return abort_attack();
00695         }
00696         pr("%s is about %d%% efficient and has approximately %d mil on board.\n", prcom(0, def), roundintby((int)def->eff, 10), roundintby(def->troops, 10));
00697     }
00698     /* Ok, everything is fine */
00699     return 0;
00700 }
00701 
00702 /* Attack and assault ask the user which kind of support they want */
00703 
00704 int
00705 att_ask_support(int offset, int *fortp, int *shipp, int *landp,
00706                 int *planep)
00707 {
00708     char buf[1024];
00709     char *p;
00710     *fortp = *shipp = *landp = *planep = 1;
00711 
00712     if (player->argp[offset] != NULL) {
00713         if ((player->argp[offset + 1] == NULL) ||
00714             (player->argp[offset + 2] == NULL) ||
00715             (player->argp[offset + 3] == NULL)) {
00716             pr("If any support arguments are used, all must be!\n");
00717             return RET_SYN;
00718         }
00719 
00720         *fortp = *shipp = 0;
00721         *landp = *planep = 0;
00722 
00723         if (!(p = getstarg(player->argp[offset], "Use fort support? ",
00724                            buf)))
00725             return RET_SYN;
00726 
00727         if ((*p == 'y') || (*p == 'Y'))
00728             *fortp = 1;
00729 
00730         if (!(p = getstarg(player->argp[offset + 1], "Use ship support? ",
00731                            buf)))
00732             return RET_SYN;
00733 
00734         if ((*p == 'y') || (*p == 'Y'))
00735             *shipp = 1;
00736 
00737         if (!(p = getstarg(player->argp[offset + 2], "Use land support? ",
00738                            buf)))
00739             return RET_SYN;
00740 
00741         if ((*p == 'y') || (*p == 'Y'))
00742             *landp = 1;
00743 
00744         if (!(p = getstarg(player->argp[offset + 3], "Use plane support? ",
00745                            buf)))
00746             return RET_SYN;
00747 
00748         if ((*p == 'y') || (*p == 'Y'))
00749             *planep = 1;
00750     }
00751     return RET_OK;
00752 }
00753 
00754 /*
00755  * Attack, assault, and board ask the attacker what they'd like to attack
00756  * with.  This includes mil and land units from each "off" object.  Note that
00757  * after each sub-prompt, we check to make sure that the attack is still
00758  * valid, and if it's not, then we abort the attack.
00759  */
00760 
00761 int
00762 att_ask_offense(int combat_mode, struct combat *off, struct combat *def,
00763                 struct emp_qelem *olist, int *a_spyp, int *a_engineerp)
00764 {
00765     int n;
00766     char land_answer[256];
00767 
00768     emp_initque(olist);
00769     if (att_abort(combat_mode, off, def))
00770         return 0;
00771     memset(land_answer, 0, sizeof(land_answer));
00772     for (n = 0; n <= off->last; ++n) {
00773         off[n].troops = ask_off(combat_mode, off + n, def);
00774         if (att_abort(combat_mode, off, def))
00775             return 0;
00776         ask_olist(combat_mode, off + n, def, olist, land_answer,
00777                   a_spyp, a_engineerp);
00778         if (att_abort(combat_mode, off, def))
00779             return 0;
00780     }
00781     return 0;
00782 }
00783 
00784 /*
00785  * Return path cost for ATTACKER to enter sector given by DEF.
00786  * MOBTYPE is a mobility type accepted by sector_mcost().
00787  */
00788 static double
00789 att_mobcost(natid attacker, struct combat *def, int mobtype)
00790 {
00791     struct sctstr sect;
00792     int ok;
00793 
00794     if (CANT_HAPPEN(def->type != EF_SECTOR))
00795         return -1.0;
00796     ok = getsect(def->x, def->y, &sect);
00797     if (CANT_HAPPEN(!ok))
00798         return -1.0;
00799 
00800     /*
00801      * We want the cost to move/march into the sector.  If we just
00802      * called sector_mcost(), we'd get the defender's cost.  The
00803      * attacker's cost is higher unless he's the old-owner.  Note: if
00804      * there are no civilians, a victorious attacker will become the
00805      * old-owner.  But he isn't now.
00806      */
00807     sect.sct_own = attacker;
00808     sect.sct_mobil = 0;
00809     return sector_mcost(&sect, mobtype);
00810 }
00811 
00812 /* How many mil is off allowed to attack with when it attacks def? */
00813 
00814 static int
00815 get_mob_support(int combat_mode, struct combat *off, struct combat *def)
00816 {
00817     int mob_support;
00818     double mobcost;
00819 
00820     switch (combat_mode) {
00821     case A_ATTACK:
00822         mobcost = att_mobcost(off->own, def, MOB_MOVE);
00823         if (mobcost < 0 || off->mob <= 0)
00824             return 0;
00825         mob_support = off->mob / mobcost;
00826         if (mob_support < off->troops)
00827             pr("Sector %s has %d mobility which can only support %d mil,\n",
00828                xyas(off->x, off->y, player->cnum), off->mob, mob_support);
00829         else
00830             mob_support = off->troops;
00831         return mob_support;
00832     case A_ASSAULT:
00833         if (def->own != player->cnum && def->mil) {
00834             if (off->shp_mcp->m_flags & M_SEMILAND)
00835                 return off->troops / 4;
00836             else if (!(off->shp_mcp->m_flags & M_LAND))
00837                 return off->troops / 10;
00838         }
00839         break;
00840     case A_BOARD:
00841         if (off->type == EF_SECTOR && off->mob <= 0)
00842             return 0;
00843         mob_support = def->shp_mcp->m_item[I_MILIT];
00844         if (mob_support < off->troops)
00845             pr("The size of the ship you are trying to board limits your party to %d mil,\n", mob_support);
00846         else
00847             mob_support = off->troops;
00848         return mob_support;
00849     case A_LBOARD:
00850         if (off->mob <= 0)
00851             return 0;
00852         if (def->lnd_lcp->l_flags & L_SPY)
00853             return 1;
00854         mob_support = def->lnd_lcp->l_item[I_MILIT];
00855         if (mob_support < off->troops)
00856             pr("The size of the unit you are trying to board limits your party to %d mil,\n", mob_support);
00857         else
00858             mob_support = off->troops;
00859         return mob_support;
00860     }
00861     return off->troops;
00862 }
00863 
00864 /*
00865  * If the attacker decides to go through with the attack, then the
00866  * sectors/ships they are attacking with may be charged some mobility.
00867  * This is where that amount of mobility is calculated.  It is actually
00868  * subtracted "for real" from the object's mobility in put_combat().
00869  */
00870 
00871 static void
00872 calc_mobcost(int combat_mode, struct combat *off, struct combat *def,
00873              int attacking_mil)
00874 {
00875     struct shpstr ship;
00876 
00877     if (!attacking_mil)
00878         return;
00879     switch (combat_mode) {
00880     case A_ATTACK:
00881         off->mobcost += MAX(1,
00882                             (int)(attacking_mil
00883                                   * att_mobcost(off->own, def, MOB_MOVE)));
00884         break;
00885     case A_LBOARD:
00886         off->mobcost += MAX(1, attacking_mil / 5);
00887         break;
00888     case A_BOARD:
00889         switch (off->type) {
00890         case EF_SECTOR:
00891             off->mobcost += MAX(1, attacking_mil / 5);
00892             break;
00893         case EF_SHIP:
00894             /* the 2 in the formula below is a fudge factor */
00895             getship(def->shp_uid, &ship);
00896             off->mobcost += (def->eff / 100) * (ship.shp_speed / 2);
00897         }
00898     }
00899 }
00900 
00901 /* How many mil to we want to attack from off against def? */
00902 
00903 static int
00904 ask_off(int combat_mode, struct combat *off, struct combat *def)
00905 {
00906     int attacking_mil;
00907     int mob_support;
00908     char prompt[512];
00909 
00910     if (att_get_combat(off, 0) <= 0)
00911         return 0;
00912     if ((off->type == EF_SECTOR) && (off->own != player->cnum))
00913         return 0;
00914     if ((mob_support = get_mob_support(combat_mode, off, def)) <= 0)
00915         return 0;
00916     if (off->type == EF_SECTOR) {
00917         if (off->own != player->cnum)
00918             return 0;
00919         sprintf(prompt, "Number of mil from %s at %s (max %d) : ",
00920                 off->sct_dcp->d_name,
00921                 xyas(off->x, off->y, player->cnum), mob_support);
00922     } else {
00923         sprintf(prompt, "Number of mil from %s (max %d) : ",
00924                 prcom(0, off), mob_support);
00925     }
00926     if ((attacking_mil = onearg(0, prompt)) < 0)
00927         abort_attack();
00928     if (att_abort(combat_mode, off, def))
00929         return 0;
00930     if (att_get_combat(off, 0) <= 0)
00931         return 0;
00932     if ((attacking_mil =
00933          MIN(attacking_mil, MIN(mob_support, off->troops))) <= 0)
00934         return 0;
00935 
00936     calc_mobcost(combat_mode, off, def, attacking_mil);
00937     return attacking_mil;
00938 }
00939 
00940 /*
00941  * Which units would you like to attack with or move in with [ynYNq?]
00942  */
00943 
00944 static char
00945 att_prompt(char *prompt, char army)
00946 {
00947     char buf[1024];
00948     char *p;
00949 
00950     if (!army)
00951         army = '~';
00952     for (;;) {
00953         p = getstring(prompt, buf);
00954         if (player->aborted || (p && *p == 'q')) {
00955             abort_attack();
00956             return 'N';
00957         }
00958         if (!p || !*p)
00959             return 'n';
00960         if (tolower(*p) == 'y' || tolower(*p) == 'n')
00961             return *p;
00962         pr("y - yes this unit\n"
00963            "n - no this unit\n"
00964            "Y - yes to all units in army '%c'\n"
00965            "N - no to all units in army '%c'\n"
00966            "q - quit\n? - this help message\n\n",
00967            army, army);
00968     }
00969 }
00970 
00971 /* Ask the attacker which units they want to attack/assault/board with */
00972 
00973 static void
00974 ask_olist(int combat_mode, struct combat *off, struct combat *def,
00975           struct emp_qelem *olist, char *land_answer, int *a_spyp,
00976           int *a_engineerp)
00977 {
00978     struct nstr_item ni;
00979     struct lndstr land;
00980     double mobcost;
00981     struct ulist *llp;
00982     struct lchrstr *lcp;
00983     double att_val;
00984     int count = 0;
00985     int maxland = 0;
00986     int first_time = 1;
00987     char prompt[512];
00988 
00989     if (def->type == EF_LAND)
00990         return;
00991     if (def->type == EF_SHIP)
00992         maxland = def->shp_mcp->m_nland;
00993 
00994     snxtitem_xy(&ni, EF_LAND, off->x, off->y);
00995     while (nxtitem(&ni, &land)) {
00996         if (land.lnd_own != player->cnum)
00997             continue;
00998         if (land.lnd_effic < LAND_MINEFF)
00999             continue;
01000         if (land_answer[(int)land.lnd_army] == 'N')
01001             continue;
01002         if (!lnd_can_attack(&land))
01003             continue;
01004         lcp = &lchr[(int)land.lnd_type];
01005 
01006         if (def->type == EF_SHIP && !maxland) {
01007             pr("Land units are not able to board this kind of ship\n");
01008             return;
01009         }
01010         if (land.lnd_mobil <= 0) {
01011             pr("%s is out of mobility, and cannot %s\n",
01012                prland(&land), att_mode[combat_mode]);
01013             continue;
01014         }
01015 
01016         if (opt_MARKET) {
01017             if (ontradingblock(EF_LAND, &land)) {
01018                 pr("%s is on the trading block, and cannot %s\n",
01019                    prland(&land), att_mode[combat_mode]);
01020                 continue;
01021             }
01022         }
01023 
01024         if (off->type == EF_SECTOR && land.lnd_ship >= 0) {
01025             pr("%s is on ship #%d, and cannot %s\n",
01026                prland(&land), land.lnd_ship, att_mode[combat_mode]);
01027             continue;
01028         } else if (off->type == EF_SHIP) {
01029             if (land.lnd_ship != off->shp_uid)
01030                 continue;
01031         } else if (land.lnd_land >= 0) {
01032             pr("%s is on unit #%d, and cannot %s\n",
01033                prland(&land), land.lnd_land, att_mode[combat_mode]);
01034             continue;
01035         }
01036         switch (combat_mode) {
01037         case A_ATTACK:
01038             /*
01039              * We used to let land units attack only if they have the
01040              * mobility consumed by the attack, not counting combat
01041              * and moving in to occupy.  Making sure your land units
01042              * reach attack positions with enough mobility left is a
01043              * pain in the neck.  We now require positive mobility,
01044              * just like for marching.  Except we don't allow rushing
01045              * of high-mobility sectors (mountains): for those we
01046              * still require attack mobility.
01047              */
01048             mobcost = att_mobcost(off->own, def, lnd_mobtype(&land));
01049             if (mobcost < 1.0) {
01050                 if (land.lnd_mobil <= 0) {
01051                     pr("%s is out of mobility\n", prland(&land));
01052                     continue;
01053                 }
01054             } else {
01055                 mobcost = lnd_pathcost(&land, mobcost);
01056                 if (land.lnd_mobil < mobcost) {
01057                     pr("%s does not have enough mobility (%d needed)\n",
01058                        prland(&land), (int)ceil(mobcost));
01059                     continue;
01060                 }
01061             }
01062             break;
01063         case A_ASSAULT:
01064         case A_BOARD:
01065             mobcost = 0;
01066             if (!(lcp->l_flags & L_ASSAULT))
01067                 continue;
01068             break;
01069         default:
01070             CANT_REACH();
01071             return;
01072         }
01073         att_val = attack_val(combat_mode, &land);
01074         if (att_val < 1.0) {
01075             pr("%s has no offensive strength\n", prland(&land));
01076             continue;
01077         }
01078         resupply_all(&land);
01079         putland(land.lnd_uid, &land);
01080         if (!has_supply(&land)) {
01081             pr("%s is out of supply, and cannot %s\n",
01082                prland(&land), att_mode[combat_mode]);
01083             continue;
01084         }
01085         if (def->type == EF_SHIP && first_time) {
01086             first_time = 0;
01087             pr("You may board with a maximum of %d land units\n", maxland);
01088         }
01089         pr("%s has a base %s value of %.0f\n",
01090            prland(&land), att_mode[combat_mode], att_val);
01091         if (land_answer[(int)land.lnd_army] != 'Y') {
01092             sprintf(prompt,
01093                     "%s with %s %s (%c %d%%) [ynYNq?] ",
01094                     att_mode[combat_mode],
01095                     prland(&land),
01096                     prcom(1, off),
01097                     land.lnd_army ? land.lnd_army : '~',
01098                     land.lnd_effic);
01099             land_answer[(int)land.lnd_army] =
01100                 att_prompt(prompt, land.lnd_army);
01101             if (att_abort(combat_mode, off, def))
01102                 return;
01103             if (land_answer[(int)land.lnd_army] != 'y' &&
01104                 land_answer[(int)land.lnd_army] != 'Y')
01105                 continue;
01106         }
01107         if (!(llp = malloc(sizeof(struct ulist)))) {
01108             logerror("Malloc failed in attack!\n");
01109             abort_attack();
01110             return;
01111         }
01112         memset(llp, 0, sizeof(struct ulist));
01113         emp_insque(&llp->queue, olist);
01114         llp->mobil = mobcost;
01115         if (!get_land(combat_mode, def, land.lnd_uid, llp, 0))
01116             continue;
01117         if (lnd_spyval(&land) > *a_spyp)
01118             *a_spyp = lnd_spyval(&land);
01119         if (((struct lchrstr *)llp->chrp)->l_flags & L_ENGINEER)
01120             ++*a_engineerp;
01121         if (def->type == EF_SHIP && ++count >= maxland)
01122             break;
01123     }
01124 }
01125 
01126 /* What's the offense or defense multiplier? */
01127 
01128 double
01129 att_combat_eff(struct combat *com)
01130 {
01131     double eff = 1.0;
01132     double str;
01133     struct shpstr ship;
01134 
01135     if (com->type == EF_SECTOR) {
01136         eff = com->eff / 100.0;
01137         if (com->own == player->cnum) {
01138             str = com->sct_dcp->d_ostr;
01139             eff = 1.0 + (str - 1.0) * eff;
01140         } else
01141             eff = sector_strength(getsectp(com->x, com->y));
01142     } else if (com->type == EF_SHIP && com->own != player->cnum) {
01143         getship(com->shp_uid, &ship);
01144         eff = 1.0 + ship.shp_armor / 100.0;
01145     }
01146     return eff;
01147 }
01148 
01149 int
01150 att_get_offense(int combat_mode, struct combat *off,
01151                 struct emp_qelem *olist, struct combat *def)
01152 {
01153     int ototal;
01154 
01155     /*
01156      * Get the attacker units & mil again in case they changed while the
01157      * attacker was answering sub-prompts.
01158      */
01159 
01160     ototal = get_ototal(combat_mode, off, olist, 1.0, 1);
01161     if (att_empty_attack(combat_mode, ototal, def))
01162         return abort_attack();
01163     if (combat_mode == A_PARA)
01164         return ototal;
01165     pr("\n             Initial attack strength: %8d\n", ototal);
01166     return ototal;
01167 }
01168 
01169 /* Get the defensive units and reacting units */
01170 int
01171 att_get_defense(struct emp_qelem *olist, struct combat *def,
01172                 struct emp_qelem *dlist, int a_spy, int ototal)
01173 {
01174     int d_spy = 0;
01175     struct emp_qelem *qp;
01176     struct ulist *llp;
01177     int dtotal;
01178     int old_dtotal;
01179 
01180     emp_initque(dlist);
01181     get_dlist(def, dlist, 0, &d_spy);
01182     dtotal = get_dtotal(def, dlist, 1.0, 0);
01183 
01184     /*
01185      * Call in reacting units
01186      */
01187 
01188     if (def->type == EF_SECTOR && def->sct_type != SCT_MOUNT)
01189         att_reacting_units(def, dlist, a_spy, &d_spy, ototal);
01190 
01191     for (qp = olist->q_forw; qp != olist; qp = qp->q_forw) {
01192         llp = (struct ulist *)qp;
01193         intelligence_report(def->own, &llp->unit.land, d_spy,
01194                             "Scouts report attacking unit:");
01195     }
01196 
01197     old_dtotal = dtotal;
01198     dtotal = get_dtotal(def, dlist, 1.0, 0);
01199     if (dtotal != old_dtotal)
01200         pr("Defense strength with reacting units: %8d\n", dtotal);
01201 
01202     return dtotal;
01203 }
01204 
01205 /* Get the defensive land units in the sector or on the ship */
01206 
01207 static void
01208 get_dlist(struct combat *def, struct emp_qelem *list, int a_spy,
01209           int *d_spyp)
01210 {
01211     struct nstr_item ni;
01212     struct ulist *llp;
01213     struct lndstr land;
01214 
01215 /* In here is where you need to take out spies and trains from the defending
01216    lists.  Spies try to hide, trains get trapped and can be boarded. */
01217 
01218     snxtitem_xy(&ni, EF_LAND, def->x, def->y);
01219     while (nxtitem(&ni, &land)) {
01220         if (!land.lnd_own)
01221             continue;
01222         if (land.lnd_own != def->own)
01223             continue;
01224         if (def->type == EF_SECTOR && land.lnd_ship >= 0)
01225             continue;
01226         if (def->type == EF_SECTOR && land.lnd_land >= 0)
01227             continue;
01228         if (def->type == EF_SHIP && land.lnd_ship != def->shp_uid)
01229             continue;
01230         if (def->type == EF_LAND && land.lnd_land != def->lnd_uid)
01231             continue;
01232         if (!list) {            /* Just estimating the enemy strength */
01233             intelligence_report(player->cnum, &land, a_spy,
01234                                 "Scouts report defending unit:");
01235             continue;
01236         }
01237         if (!(llp = malloc(sizeof(struct ulist)))) {
01238             logerror("Malloc failed in attack!\n");
01239             abort_attack();
01240             return;
01241         }
01242         memset(llp, 0, sizeof(struct ulist));
01243         emp_insque(&llp->queue, list);
01244         llp->supplied = has_supply(&land);
01245         if (!get_land(A_DEFEND, def, land.lnd_uid, llp, 1))
01246             continue;
01247         if (lnd_spyval(&land) > *d_spyp)
01248             *d_spyp = lnd_spyval(&land);
01249     }
01250 }
01251 
01252 /* Calculate the total offensive strength */
01253 
01254 static int
01255 get_ototal(int combat_mode, struct combat *off, struct emp_qelem *olist,
01256            double osupport, int check)
01257 {
01258     double ototal = 0.0;
01259     struct emp_qelem *qp, *next;
01260     struct ulist *llp;
01261     int n, w;
01262 
01263     /*
01264      * first, total the attacking mil
01265      */
01266 
01267     for (n = 0; n <= off->last; ++n) {
01268         if (off[n].type == EF_BAD || (check &&
01269                                       att_get_combat(&off[n], 0) <= 0))
01270             continue;
01271         ototal += off[n].troops * att_combat_eff(off + n);
01272     }
01273 
01274     /*
01275      * next, add in the attack_values of all
01276      * the attacking units
01277      */
01278 
01279     for (qp = olist->q_forw; qp != olist; qp = next) {
01280         next = qp->q_forw;
01281         llp = (struct ulist *)qp;
01282         if (check && !get_land(combat_mode, 0, llp->unit.land.lnd_uid, llp, 0))
01283             continue;
01284         if (combat_mode == A_ATTACK) {
01285             w = -1;
01286             for (n = 0; n <= off->last; ++n) {
01287                 if (off[n].type == EF_BAD)
01288                     continue;
01289                 if ((off[n].x == llp->unit.land.lnd_x) &&
01290                     (off[n].y == llp->unit.land.lnd_y))
01291                     w = n;
01292             }
01293             if (w < 0) {
01294                 lnd_delete(llp, "is in a sector not owned by you");
01295                 continue;
01296             }
01297             ototal += attack_val(combat_mode, &llp->unit.land) *
01298                 att_combat_eff(off + w);
01299         } else {
01300             ototal += attack_val(combat_mode, &llp->unit.land);
01301         }
01302     }
01303     ototal *= osupport;
01304 
01305     return ldround(ototal, 1);
01306 }
01307 
01308 /* Calculate the total defensive strength */
01309 
01310 static int
01311 get_dtotal(struct combat *def, struct emp_qelem *list, double dsupport,
01312            int check)
01313 {
01314     double dtotal = 0.0, eff = 1.0, d_unit;
01315     struct emp_qelem *qp, *next;
01316     struct ulist *llp;
01317 
01318     if (check && att_get_combat(def, 1) < 0)
01319         return 0;
01320     eff = att_combat_eff(def);
01321     dtotal = def->troops * eff;
01322 
01323     /*
01324      * next, add in the defense_values of all
01325      * the defending non-retreating units
01326      */
01327 
01328     for (qp = list->q_forw; qp != list; qp = next) {
01329         next = qp->q_forw;
01330         llp = (struct ulist *)qp;
01331         if (check && !get_land(A_DEFEND, def, llp->unit.land.lnd_uid, llp, 1))
01332             continue;
01333         d_unit = defense_val(&llp->unit.land);
01334         if (!llp->supplied)
01335             d_unit /= 2.0;
01336         dtotal += d_unit * eff;
01337     }
01338 
01339     dtotal *= dsupport;
01340 
01341     return ldround(dtotal, 1);
01342 }
01343 
01344 /*
01345  * This is the land unit integrity check.  Note that we don't print
01346  * warnings about victim land units because the attacker may not have seen them
01347  */
01348 
01349 static int
01350 get_land(int combat_mode, struct combat *def, int uid, struct ulist *llp,
01351          int victim_land)
01352 {
01353     struct lndstr *lp = &llp->unit.land;
01354     char buf[512];
01355 
01356     getland(uid, lp);
01357 
01358     if (!llp->chrp) {           /* first time */
01359         llp->x = llp->unit.land.lnd_x;
01360         llp->y = llp->unit.land.lnd_y;
01361         llp->chrp = (struct empobj_chr *)&lchr[(int)llp->unit.land.lnd_type];
01362     } else {                    /* not first time */
01363         if (lp->lnd_effic < LAND_MINEFF) {
01364             sprintf(buf, "was destroyed and is no longer a part of the %s",
01365                     att_mode[combat_mode]);
01366             lnd_delete(llp, buf);
01367             return 0;
01368         }
01369         if (victim_land) {
01370             if (lp->lnd_x != def->x || lp->lnd_y != def->y) {
01371                 lnd_delete(llp,
01372                            "left to go fight another battle and is no longer a part of the defense");
01373                 return 0;
01374             }
01375         } else {
01376             if (lp->lnd_own != player->cnum) {
01377                 sprintf(buf,
01378                         "was destroyed and is no longer a part of the %s",
01379                         att_mode[combat_mode]);
01380                 lnd_delete(llp, buf);
01381                 return 0;
01382             }
01383             if (lp->lnd_x != llp->x || lp->lnd_y != llp->y) {
01384                 sprintf(buf,
01385                         "left to fight another battle and is no longer a part of the %s",
01386                         att_mode[combat_mode]);
01387                 lnd_delete(llp, buf);
01388                 return 0;
01389             }
01390             if (lp->lnd_effic < llp->eff) {
01391                 sprintf(buf, "damaged from %d%% to %d%%",
01392                         llp->eff, lp->lnd_effic);
01393                 lnd_print(llp, buf);
01394             }
01395         }
01396     }
01397     llp->eff = llp->unit.land.lnd_effic;
01398 
01399     return 1;
01400 }
01401 
01402 /*
01403  * Put the land unit on the disk.  If there was some mobility cost, then
01404  * subtract it from the units mobility.  Note that this works the same way
01405  * as sectors & ships in that no mobility is actually taken until the attacker
01406  * has committed to attacking.
01407  */
01408 
01409 static void
01410 kill_land(struct emp_qelem *list)
01411 {
01412     struct emp_qelem *qp, *next;
01413     struct ulist *llp;
01414 
01415     for (qp = list->q_forw; qp != list; qp = next) {
01416         next = qp->q_forw;
01417         llp = (struct ulist *)qp;
01418         if (llp->unit.land.lnd_ship >= 0) {
01419             llp->unit.land.lnd_effic = 0;
01420             lnd_delete(llp, "cannot return to the ship, and dies!");
01421         }
01422     }
01423 }
01424 
01425 static void
01426 att_infect_units(struct emp_qelem *list, int plague)
01427 {
01428     struct emp_qelem *qp, *next;
01429     struct ulist *llp;
01430 
01431     if (!plague)
01432         return;
01433     for (qp = list->q_forw; qp != list; qp = next) {
01434         next = qp->q_forw;
01435         llp = (struct ulist *)qp;
01436         if (llp->unit.land.lnd_pstage == PLG_HEALTHY)
01437             llp->unit.land.lnd_pstage = PLG_EXPOSED;
01438     }
01439 }
01440 
01441 static void
01442 put_land(struct emp_qelem *list)
01443 {
01444     struct emp_qelem *qp, *next;
01445     struct ulist *llp;
01446 
01447     for (qp = list->q_forw; qp != list; qp = next) {
01448         next = qp->q_forw;
01449         llp = (struct ulist *)qp;
01450         llp->unit.land.lnd_mission = 0;
01451         llp->unit.land.lnd_harden = 0;
01452         llp->unit.land.lnd_mobil -= (int)llp->mobil;
01453         llp->mobil = 0.0;
01454         putland(llp->unit.land.lnd_uid, &llp->unit.land);
01455         if (llp->unit.land.lnd_own != player->cnum) {
01456             emp_remque((struct emp_qelem *)llp);
01457             free(llp);
01458         } else
01459             get_land(A_ATTACK, 0, llp->unit.land.lnd_uid, llp, 0);
01460     }
01461 }
01462 
01463 /*
01464  * Keep sending in reinforcements until it looks like we're going to win.
01465  * Note that the "strength" command also calls this routine.
01466  */
01467 
01468 double
01469 att_reacting_units(struct combat *def, struct emp_qelem *list, int a_spy,
01470                    int *d_spyp, int ototal)
01471 {
01472     struct nstr_item ni;
01473     struct lndstr land;
01474     struct sctstr sect, dsect;
01475     struct ulist *llp;
01476     int dtotal;
01477     double new_land = 0;
01478     double mobcost;
01479     double pathcost;
01480     int dist;
01481     int radius;
01482     int origx, origy;
01483     double eff = att_combat_eff(def);
01484     char buf[1024];
01485 
01486     if (list)
01487         dtotal = get_dtotal(def, list, 1.0, 1);
01488     else
01489         dtotal = 0;
01490     snxtitem_all(&ni, EF_LAND);
01491     while (nxtitem(&ni, &land) && dtotal + new_land * eff < 1.2 * ototal) {
01492         if (!land.lnd_own)
01493             continue;
01494         if (!land.lnd_rad_max)
01495             continue;
01496         if ((land.lnd_x == def->x) && (land.lnd_y == def->y))
01497             continue;
01498         if (land.lnd_own != def->own)
01499             continue;
01500         if (land.lnd_ship >= 0)
01501             continue;
01502         if (land.lnd_land >= 0)
01503             continue;
01504         if (defense_val(&land) < 1.0)
01505             continue;
01506         if (!lnd_can_attack(&land))
01507             continue;
01508 
01509         /* Only supplied units can react */
01510         if (!has_supply(&land))
01511             continue;
01512 
01513         dist = mapdist(land.lnd_x, land.lnd_y, def->x, def->y);
01514 
01515         getsect(land.lnd_x, land.lnd_y, &sect);
01516         /* Units on efficient headquarters can react 1 farther */
01517         if ((sect.sct_type == SCT_HEADQ) && (sect.sct_effic >= 60))
01518             radius = land.lnd_rad_max + 1;
01519         else
01520             radius = land.lnd_rad_max;
01521 
01522         if (land.lnd_mission == MI_RESERVE)
01523             radius += 2;
01524 
01525         if (dist > radius)
01526             continue;
01527 
01528         getsect(def->x, def->y, &dsect);
01529         if (!BestLandPath(buf, &sect, &dsect, &pathcost,
01530                           lnd_mobtype(&land)))
01531             continue;
01532 
01533         mobcost = lnd_pathcost(&land, pathcost);
01534         if (land.lnd_mobil < mobcost)
01535             continue;
01536 
01537         new_land += defense_val(&land);
01538 
01539         if (!list)              /* we are in the "strength" command */
01540             continue;
01541 
01542         /* move to defending sector */
01543         land.lnd_mobil -= ldround(mobcost, 1);
01544         origx = land.lnd_x;
01545         origy = land.lnd_y;
01546         land.lnd_x = def->x;
01547         land.lnd_y = def->y;
01548         putland(land.lnd_uid, &land);
01549         wu(0, land.lnd_own, "%s reacts to %s.\n",
01550            prland(&land), xyas(land.lnd_x, land.lnd_y, land.lnd_own));
01551 
01552         llp = malloc(sizeof(struct ulist));
01553 
01554         memset(llp, 0, sizeof(struct ulist));
01555         llp->supplied = 1;
01556         llp->x = origx;
01557         llp->y = origy;
01558         llp->chrp = (struct empobj_chr *)&lchr[(int)land.lnd_type];
01559         llp->unit.land = land;
01560         emp_insque(&llp->queue, list);
01561         if (lnd_spyval(&land) > *d_spyp)
01562             *d_spyp = lnd_spyval(&land);
01563 
01564         intelligence_report(player->cnum, &land, a_spy,
01565                             "Scouts sight reacting enemy unit:");
01566     }
01567     return new_land;
01568 }
01569 
01570 /* Pop off shells and fly bombing missions to get your attack multiplier up */
01571 
01572 static double
01573 get_osupport(char *outs, struct combat *def, int fort_sup, int ship_sup,
01574              int land_sup, int plane_sup)
01575 {
01576     double osupport = 1.0;
01577     int dam;
01578     double af, as, au, ap;
01579 
01580     af = as = au = ap = 0.0;
01581     if (fort_sup) {
01582         dam = dd(def->own, player->cnum, def->x, def->y, 0, 0);
01583         af = dam / 100.0;
01584         osupport += af;
01585     }
01586     if (ship_sup) {
01587         dam = sd(def->own, player->cnum, def->x, def->y, 0, 0, 0);
01588 
01589         as = dam / 100.0;
01590         osupport += as;
01591     }
01592 
01593     if (land_sup) {
01594         dam = lnd_support(def->own, player->cnum, def->x, def->y, 0);
01595         au = dam / 100.0;
01596         osupport += au;
01597     }
01598 
01599     if (plane_sup) {
01600         dam = off_support(def->x, def->y, def->own, player->cnum);
01601         ap = dam / 100.0;
01602         osupport += ap;
01603     }
01604     sprintf(outs, "attacker\t%1.2f\t%1.2f\t%1.2f\t%1.2f\n", af, as, au,
01605             ap);
01606     return osupport;
01607 }
01608 
01609 /* Pop off shells and fly bombing missions to get your defense multiplier up */
01610 
01611 static double
01612 get_dsupport(char *outs, struct emp_qelem *list, struct combat *def,
01613              int ototal, int dtotal)
01614 {
01615     double dsupport = 1.0;
01616     int dam;
01617     double df, ds, du, dp;
01618     int good = 0;
01619 
01620     df = ds = du = dp = 0.0;
01621     if (dtotal < 0.1 * ototal) {
01622         good = -1;
01623     } else if (dtotal >= 1.2 * ototal) {
01624         good = 1;
01625     } else {
01626         dam = dd(player->cnum, def->own, def->x, def->y, 0, 1);
01627         df = dam / 100.0;
01628         dsupport += df;
01629 
01630         dtotal = get_dtotal(def, list, dsupport, 0);
01631         if (dtotal < 1.2 * ototal) {
01632             dam = sd(player->cnum, def->own, def->x, def->y, 0, 1, 0);
01633             ds = dam / 100.0;
01634             dsupport += ds;
01635             dtotal = get_dtotal(def, list, dsupport, 0);
01636         }
01637         if (dtotal < 1.2 * ototal) {
01638             dam = lnd_support(player->cnum, def->own, def->x, def->y, 1);
01639             du = dam / 100.0;
01640             dsupport += du;
01641             dtotal = get_dtotal(def, list, dsupport, 1);
01642         }
01643         if (dtotal < 1.2 * ototal) {
01644             dam = def_support(def->x, def->y, player->cnum, def->own);
01645             dp = dam / 100.0;
01646             dsupport += dp;
01647         }
01648     }
01649     if (good)
01650         *outs = '\0';
01651     else
01652         sprintf(outs, "defender\t%1.2f\t%1.2f\t%1.2f\t%1.2f\n\n", df, ds,
01653                 du, dp);
01654     if (def->own) {
01655         if (good < 0)
01656             wu(0, def->own,
01657                "\nOdds are bad for us...support cancelled.\n\n");
01658         else if (good > 0)
01659             wu(0, def->own,
01660                "\nOdds are good for us...support cancelled.\n\n");
01661     }
01662     return dsupport;
01663 }
01664 
01665 /*
01666  * Land mines add to the defense multiplier.  If the attacker has engineers
01667  * then this multiplier is cut in half.
01668  */
01669 
01670 static double
01671 get_mine_dsupport(struct combat *def, int a_engineer)
01672 {
01673     int mines;
01674     struct sctstr sect;
01675 
01676     getsect(def->x, def->y, &sect);
01677 
01678     if (sect.sct_oldown != player->cnum) {
01679         mines = MIN(sect.sct_mines, 20);
01680         if (a_engineer)
01681             mines = ldround(mines / 2.0, 1);
01682         if (mines > 0) {
01683             if (def->own)
01684                 wu(0, def->own, "Defending mines add %1.2f\n",
01685                    mines * 0.02);
01686             pr("Defending mines add %1.2f\n", mines * 0.02);
01687             return mines * 0.02;
01688         }
01689     }
01690     return 0.0;
01691 }
01692 
01693 /* Get the offensive and defensive support */
01694 int
01695 att_get_support(int combat_mode, int ofort, int oship, int oland,
01696                 int oplane, struct emp_qelem *olist, struct combat *off,
01697                 struct emp_qelem *dlist, struct combat *def,
01698                 double *osupportp, double *dsupportp, int a_engineer)
01699 {
01700     int ototal, dtotal;
01701     char osupports[512];
01702     char dsupports[512];
01703 
01704     if (combat_mode == A_PARA)
01705         *osupports = '\0';
01706     else
01707         *osupportp = get_osupport(osupports, def,
01708                                   ofort, oship, oland, oplane);
01709 
01710     /*
01711      * I need to put a 1 at the end of the next four total_stren calls
01712      * because units & mil may have been damaged by collateral damage or
01713      * nuclear warheads from the offensive & defensive support.
01714      */
01715 
01716     ototal = get_ototal(combat_mode, off, olist, *osupportp, 1);
01717     if (att_empty_attack(combat_mode, ototal, def))
01718         return abort_attack();
01719     dtotal = get_dtotal(def, dlist, *dsupportp, 1);
01720 
01721     /*
01722      * Calculate defensive support.  If odds are too good or too bad
01723      * then don't call in support.
01724      */
01725 
01726     *dsupportp = get_dsupport(dsupports, dlist, def, ototal, dtotal);
01727     ototal = get_ototal(combat_mode, off, olist, *osupportp, 1);
01728     if (att_empty_attack(combat_mode, ototal, def))
01729         return abort_attack();
01730 
01731     if ((*osupports || *dsupports) &&
01732         (*osupportp != 1.0 || *dsupportp != 1.0)) {
01733         pr("\n\t\tsupport values\n");
01734         pr("\t\tforts\tships\tunits\tplanes\n");
01735         if (*osupportp != 1.0)
01736             pr("%s", osupports);
01737         if (*dsupportp != 1.0)
01738             pr("%s", dsupports);
01739         if (def->own) {
01740             wu(0, def->own, "\n\t\tsupport values\n");
01741             wu(0, def->own, "\t\tforts\tships\tunits\tplanes\n");
01742             if (*osupportp != 1.0)
01743                 wu(0, def->own, "%s", osupports);
01744             if (*dsupportp != 1.0)
01745                 wu(0, def->own, "%s", dsupports);
01746         }
01747     }
01748 
01749     dtotal = get_dtotal(def, dlist, *dsupportp, 1);
01750     if (dtotal && def->type == EF_SECTOR)
01751         *dsupportp += get_mine_dsupport(def, a_engineer);
01752     return 0;
01753 }
01754 
01755 /* How many two-legged bipeds are in this combat force? */
01756 
01757 static int
01758 count_bodies(struct combat *off, struct emp_qelem *list)
01759 {
01760     int n;
01761     int bodies = 0;
01762     struct emp_qelem *qp;
01763     struct ulist *llp;
01764 
01765     for (n = 0; n <= off->last; ++n)
01766         bodies += off[n].troops;
01767     for (qp = list->q_forw; qp != list; qp = qp->q_forw) {
01768         llp = (struct ulist *)qp;
01769         bodies += llp->unit.land.lnd_item[I_MILIT];
01770     }
01771     return bodies;
01772 }
01773 
01774 /* This is where the fighting actually occurs. */
01775 
01776 int
01777 att_fight(int combat_mode, struct combat *off, struct emp_qelem *olist,
01778           double osupport, struct combat *def, struct emp_qelem *dlist,
01779           double dsupport)
01780 {
01781     int success = 0;
01782     int a_cas = 0;              /* Casualty counts */
01783     int d_cas = 0;
01784     int ototal;                 /* total attacking strength */
01785     int dtotal;                 /* total defending strength */
01786     int a_bodies;               /* total attacking mil (incl. mil in units) */
01787     int d_bodies;               /* total defending mil (incl. mil in units) */
01788     int d_mil;
01789     int a_troops[6];
01790     int n;
01791     int news_item;
01792     int recalctime;
01793     double odds;
01794     int newmob;
01795     char *action;
01796 
01797     ototal = get_ototal(combat_mode, off, olist, osupport,
01798                         combat_mode != A_PARA);
01799     dtotal = get_dtotal(def, dlist, dsupport, 0);
01800     if (!dtotal)
01801         success = 1;
01802 
01803     a_bodies = count_bodies(off, olist);
01804     d_bodies = count_bodies(def, dlist);
01805     d_mil = def->troops;
01806     for (n = 0; n <= off->last; ++n)
01807         if (off[n].type == EF_BAD)
01808             a_troops[n] = 0;
01809         else
01810             a_troops[n] = off[n].troops;
01811 
01812     /* This switch is required to get the spacing right */
01813     switch (combat_mode) {
01814     case A_ATTACK:
01815         pr("               Final attack strength: %8d\n", ototal);
01816         break;
01817     case A_ASSAULT:
01818         pr("              Final assault strength: %8d\n", ototal);
01819         break;
01820     case A_PARA:
01821         if (def->sct_type == SCT_MOUNT ||
01822             def->sct_type == SCT_WATER ||
01823             def->sct_type == SCT_CAPIT ||
01824             def->sct_type == SCT_FORTR || def->sct_type == SCT_WASTE) {
01825             pr("You can't air-assault a %s sector!\n",
01826                def->sct_dcp->d_name);
01827             a_cas = a_bodies;
01828             off[0].troops = 0;
01829             ototal = get_ototal(A_PARA, off, olist, osupport, 0);
01830         }
01831         pr("          Final air-assault strength: %8d\n", ototal);
01832         break;
01833     case A_BOARD:
01834     case A_LBOARD:
01835         pr("                Final board strength: %8d\n", ototal);
01836     }
01837 
01838 
01839     pr("              Final defense strength: %8d\n", dtotal);
01840     odds = att_calcodds(ototal, dtotal);
01841     pr("                          Final odds: %8d%%\n", (int)(odds * 100));
01842 
01843     /* spread the plague */
01844     if (combat_mode != A_PARA) {
01845         if (!def->plague)
01846             for (n = 0; n <= off->last; ++n)
01847                 if (off[n].type != EF_BAD)
01848                     def->plague |= off[n].plague;
01849         for (n = 0; n <= off->last; ++n)
01850             if (off[n].type != EF_BAD)
01851                 off[n].plague |= def->plague;
01852     }
01853     att_infect_units(olist, off->plague);
01854     att_infect_units(dlist, def->plague);
01855 
01856     /*
01857      * Fighting is slightly random.  There is always that last little 
01858      * effort you see people put in.  Or the stray bullet that takes out
01859      * an officer and the rest go into chaos.  Things like that.
01860      * Thus, we have added a very slight random factor that will sometimes
01861      * allow the little guy to win. We modify the odds a little
01862      * (either +- 5%) to account for this randomness.  We also only
01863      * recalculate the odds every 8-50 casualties, not every cacsualty,
01864      * since a single dead guy normally wouldn't cause a commander to
01865      * rethink his strategies, but 50 dead guys might.
01866      */
01867     odds += (random() % 11 - 5) / 100.0;
01868     if (odds < 0.0)
01869         odds = 0.1;
01870     if (odds > 1.0)
01871         odds = 1.0;
01872     recalctime = 8 + (random() % 43);
01873     while (!success && ototal) {
01874         if (chance(odds)) {
01875             pr("!");
01876             d_cas += take_casualty(A_DEFEND, def, dlist);
01877             dtotal = get_dtotal(def, dlist, dsupport, 0);
01878             if (!dtotal)
01879                 ++success;
01880         } else {
01881             pr("@");
01882             a_cas += take_casualty(combat_mode, off, olist);
01883             ototal = get_ototal(combat_mode, off, olist, osupport, 0);
01884         }
01885         if (((a_cas + d_cas) % 70) == 69)
01886             pr("\n");
01887         if (recalctime-- <= 0) {
01888             recalctime = 8 + (random() % 43);
01889             odds = att_calcodds(ototal, dtotal);
01890             odds += (random() % 11 - 5) / 100.0;
01891             if (odds < 0.0)
01892                 odds = 0.1;
01893             if (odds > 1.0)
01894                 odds = 1.0;
01895         }
01896     }
01897     pr("\n");
01898     /* update defense mobility & mil */
01899     if (success)
01900         def->mil = 0;
01901     else {
01902         if (def->type == EF_SECTOR && d_mil && d_cas) {
01903             if (def->mob < 0)
01904                 def->mobcost = 0;
01905             else {
01906                 newmob = damage(def->mob, 100 * d_cas / d_mil);
01907                 def->mobcost = MIN(20, def->mob - newmob);
01908             }
01909         }
01910         def->mil = def->troops;
01911     }
01912 
01913     /* update attack mobility & mil */
01914     for (n = 0; n <= off->last; ++n)
01915         if (off[n].type != EF_BAD && off[n].troops < a_troops[n]) {
01916             if (off[n].type == EF_SECTOR && off[n].mil) {
01917                 if (!CANT_HAPPEN(off[n].mob < 0)) {
01918                     newmob = damage(off[n].mob,
01919                                     100 * (a_troops[n] - off[n].troops)
01920                                     / off[n].mil);
01921                     off[n].mobcost += MIN(20, off[n].mob - newmob);
01922                 }
01923             }
01924             off[n].mil -= a_troops[n] - off[n].troops;
01925         }
01926 
01927     /* update land unit mobility */
01928     if (d_bodies && d_cas)
01929         lnd_takemob(dlist, (double)d_cas / d_bodies);
01930     if (a_bodies && a_cas)
01931         lnd_takemob(olist, (double)a_cas / a_bodies);
01932 
01933     /* damage attacked sector */
01934     def->eff = effdamage(def->eff, (d_cas + a_cas) / 10);
01935 
01936     pr("- Casualties -\n     Yours: %d\n", a_cas);
01937     pr("    Theirs: %d\n", d_cas);
01938     pr("Papershuffling ... %.1f B.T.U\n", (d_cas + a_cas) * 0.15);
01939     player->btused += (int)((d_cas + a_cas) * 0.015 + 0.5);
01940 
01941     if (success) {
01942         switch (combat_mode) {
01943         case A_ATTACK:
01944             news_item = def->own ? N_WON_SECT : N_TOOK_UNOCC;
01945             pr("We have captured %s, sir!\n", prcom(0, def));
01946             action = "taking";
01947             break;
01948         case A_ASSAULT:
01949             news_item = def->own ? N_AWON_SECT : N_START_COL;
01950             pr("We have secured a beachhead at %s, sir!\n", prcom(0, def));
01951             action = "assaulting and taking";
01952             break;
01953         case A_PARA:
01954             news_item = def->own ? N_PWON_SECT : N_PARA_UNOCC;
01955             pr("We have captured %s, sir!\n", prcom(0, def));
01956             action = "air-assaulting and taking";
01957             break;
01958         case A_BOARD:
01959             news_item = N_BOARD_SHIP;
01960             pr("We have boarded %s, sir!\n", prcom(0, def));
01961             action = "boarding";
01962             break;
01963         case A_LBOARD:
01964             news_item = N_BOARD_LAND;
01965             pr("We have boarded %s, sir!\n", prcom(0, def));
01966             action = "boarding";
01967             break;
01968         default:
01969             CANT_REACH();
01970             news_item = 0;
01971             action = "defeating";
01972         }
01973     } else {
01974         switch (combat_mode) {
01975         case A_ATTACK:
01976             news_item = N_SCT_LOSE;
01977             pr("You have been defeated!\n");
01978             action = "attacking";
01979             break;
01980         case A_ASSAULT:
01981             news_item = N_ALOSE_SCT;
01982             pr("You have been defeated!\n");
01983             kill_land(olist);
01984             action = "trying to assault";
01985             break;
01986         case A_PARA:
01987             news_item = N_PLOSE_SCT;
01988             pr("All of your troops were destroyed\n");
01989             action = "trying to air-assault";
01990             break;
01991         case A_BOARD:
01992             news_item = N_SHP_LOSE;
01993             pr("You have been repelled\n");
01994             kill_land(olist);
01995             action = "trying to board";
01996             break;
01997         case A_LBOARD:
01998             news_item = N_LND_LOSE;
01999             pr("You have been repelled\n");
02000             kill_land(olist);
02001             action = "trying to board";
02002             break;
02003         default:
02004             CANT_REACH();
02005             news_item = 0;
02006             action = "fighting";
02007         }
02008     }
02009     nreport(player->cnum, news_item, def->own, 1);
02010     if (def->own) {
02011         wu(0, def->own,
02012            "%s (#%d) lost %d troops %s %s\nWe lost %d troops defending\n",
02013            cname(player->cnum), player->cnum, a_cas,
02014            action, pr_com(0, def, def->own), d_cas);
02015     }
02016 
02017     send_reacting_units_home(dlist);
02018 
02019     /* putland the defending land */
02020     unit_put(dlist, 0);
02021 
02022     /* putland the attacking land */
02023     put_land(olist);
02024 
02025     /* put the victim sector/ship/land */
02026     if (!success || !take_def(combat_mode, olist, off, def))
02027         put_combat(def);
02028 
02029     /* put the attacking sectors/ship */
02030     for (n = 0; n <= off->last; ++n)
02031         if (off[n].type != EF_BAD)
02032             put_combat(&off[n]);
02033 
02034     if (!success)
02035         return 0;
02036 
02037     switch (combat_mode) {
02038     case A_ATTACK:
02039         ask_move_in(off, olist, def);
02040 
02041         /* put sectors again to get abandon warnings */
02042         for (n = 0; n <= off->last; ++n)
02043             if (off[n].type != EF_BAD)
02044                 put_combat(&off[n]);
02045         break;
02046     default:
02047         att_move_in_off(combat_mode, off, olist, def);
02048     }
02049     if (def->mil > 0)
02050         pr("%d of your troops now occupy %s\n", def->mil, prcom(0, def));
02051     return 1;
02052 }
02053 
02054 /* What percentage of the combat forces going head-to-head are we? */
02055 
02056 static double
02057 att_calcodds(int ototal, int dtotal)
02058 {
02059     double odds;
02060 
02061     /* calculate odds */
02062     if (ototal <= 0)
02063         odds = 0.0;
02064     else if (dtotal <= 0)
02065         odds = 1.0;
02066     else
02067         odds = (double)ototal / (dtotal + ototal);
02068 
02069     return odds;
02070 }
02071 
02072 /* Here's where the dead soldiers get dragged off the battlefield */
02073 
02074 static int
02075 take_casualty(int combat_mode, struct combat *off, struct emp_qelem *olist)
02076 {
02077     int to_take = CASUALTY_LUMP;
02078     int biggest_troops = 0, index = -1;
02079     int n, tot_troops = 0, biggest_mil, cas;
02080     struct emp_qelem *qp, *biggest;
02081     struct ulist *llp;
02082 
02083     for (n = 0; n <= off->last; ++n) {
02084         if (off[n].type != EF_BAD) {
02085             tot_troops += off[n].troops;
02086             if (off[n].troops > biggest_troops) {
02087                 biggest_troops = off[n].troops;
02088                 index = n;
02089             }
02090         }
02091     }
02092 
02093     if (tot_troops)
02094         to_take -= tot_troops;
02095 
02096     if (to_take >= 0) {
02097         for (n = 0; n <= off->last; ++n)
02098             if (off[n].type != EF_BAD)
02099                 off[n].troops = 0;
02100     } else {
02101         /*
02102          * They can all come off mil.  We rotate the casualties,
02103          * starting with the sector containing the most mil.
02104          */
02105         to_take = CASUALTY_LUMP;
02106         if (index < 0) {
02107             pr("ERROR: Tell the deity that you got the 'green librarian' error\n");
02108             index = 0;
02109         }
02110         while (to_take > 0) {
02111             for (n = index; n <= off->last && to_take; ++n) {
02112                 if (off[n].type != EF_BAD && off[n].troops > 0) {
02113                     --to_take;
02114                     --off[n].troops;
02115                 }
02116             }
02117             for (n = 0; n < index && to_take; ++n) {
02118                 if (off[n].type != EF_BAD && off[n].troops > 0) {
02119                     --to_take;
02120                     --off[n].troops;
02121                 }
02122             }
02123         }
02124         return CASUALTY_LUMP;
02125     }
02126 
02127     if (QEMPTY(olist))
02128         return CASUALTY_LUMP - to_take;
02129 
02130     /*
02131      *  Need to take some casualties from attacking units
02132      *  Procedure: find the biggest unit remaining (in
02133      *  terms of mil) and give it the casualties.
02134      */
02135     biggest = NULL;
02136     biggest_mil = -1;
02137     for (qp = olist->q_forw; qp != olist; qp = qp->q_forw) {
02138         llp = (struct ulist *)qp;
02139 
02140         if (llp->unit.land.lnd_item[I_MILIT] > biggest_mil) {
02141             biggest_mil = llp->unit.land.lnd_item[I_MILIT];
02142             biggest = qp;
02143         }
02144     }
02145     if (biggest == NULL)
02146         return CASUALTY_LUMP - to_take;
02147 
02148     llp = (struct ulist *)biggest;
02149     cas = lnd_take_casualty(combat_mode, llp, to_take);
02150     return CASUALTY_LUMP - (to_take - cas);
02151 }
02152 
02153 /* Send reacting defense units back to where they came from (at no mob cost) */
02154 
02155 static void
02156 send_reacting_units_home(struct emp_qelem *list)
02157 {
02158     struct emp_qelem *qp, *next;
02159     struct ulist *llp;
02160     char buf[1024];
02161 
02162     for (qp = list->q_forw; qp != list; qp = next) {
02163         next = qp->q_forw;
02164         llp = (struct ulist *)qp;
02165         if ((llp->unit.land.lnd_x != llp->x) ||
02166             (llp->unit.land.lnd_y != llp->y)) {
02167             sprintf(buf, "returns to %s",
02168                     xyas(llp->x, llp->y, llp->unit.land.lnd_own));
02169             llp->unit.land.lnd_x = llp->x;
02170             llp->unit.land.lnd_y = llp->y;
02171             lnd_delete(llp, buf);
02172         }
02173     }
02174 }
02175 
02176 /* Check for 0 offense strength.  This call will always preceed an abort */
02177 
02178 int
02179 att_empty_attack(int combat_mode, int ototal, struct combat *def)
02180 {
02181     if (ototal <= 0) {
02182         if (def->own && player->cnum != def->own) {
02183             wu(0, def->own,
02184                "%s (#%d) considered %sing you @%s\n",
02185                cname(player->cnum), player->cnum,
02186                att_mode[combat_mode], xyas(def->x, def->y, def->own));
02187         }
02188         pr("No troops for %s...\n", att_mode[combat_mode]);
02189         return 1;
02190     }
02191     return 0;
02192 }
02193 
02194 /*
02195  * Take the defending sector or ship from the defender and give it to the
02196  * attacker.
02197  */
02198 
02199 static int
02200 take_def(int combat_mode, struct emp_qelem *list, struct combat *off,
02201          struct combat *def)
02202 {
02203     int n;
02204     int occuppied = 0;
02205     struct ulist *llp, *delete_me = 0;
02206     char buf[1024];
02207     struct sctstr sect;
02208     struct shpstr ship;
02209     struct lndstr land;
02210 
02211     for (n = 0; n <= off->last && !occuppied; ++n) {
02212         if (off[n].type != EF_BAD &&
02213             off[n].troops > 0 &&
02214             (off[n].type != EF_SECTOR || off[n].mob)) {
02215             ++occuppied;
02216             if (def->type == EF_LAND) {
02217                 if (def->lnd_lcp->l_flags & L_SPY) {
02218                     continue;
02219                 }
02220             }
02221             --(off[n].troops);
02222             --(off[n].mil);
02223             ++def->mil;
02224             pr("1 mil from %s moves %s\n",
02225                prcom(0, off + n), prcom(2, def));
02226         }
02227     }
02228     if (!occuppied) {
02229         if (QEMPTY(list)) {
02230             pr("%s left unoccupied\n", prcom(0, def));
02231             if (def->own)
02232                 wu(0, def->own,
02233                    "No enemy troops moved %s so you still own it!\n",
02234                    pr_com(2, def, def->own));
02235             return 0;
02236         } else {
02237             llp = (struct ulist *)list->q_forw;
02238             llp->unit.land.lnd_x = def->x;
02239             llp->unit.land.lnd_y = def->y;
02240             take_move_in_mob(combat_mode, llp, off, def);
02241             if (def->type == EF_SHIP) {
02242                 llp->unit.land.lnd_ship = def->shp_uid;
02243                 sprintf(buf, "boards %s", prcom(0, def));
02244                 delete_me = llp;
02245             } else {
02246                 llp->unit.land.lnd_ship = -1;
02247                 sprintf(buf, "moves in to occupy %s",
02248                         xyas(def->x, def->y, player->cnum));
02249                 lnd_delete(llp, buf);
02250             }
02251         }
02252     }
02253     put_combat(def);
02254     if (def->type == EF_SECTOR) {
02255         getsect(def->x, def->y, &sect);
02256         takeover(&sect, player->cnum);
02257         if (sect.sct_type == SCT_CAPIT || sect.sct_type == SCT_MOUNT)
02258             caploss(&sect, def->own,
02259                     "* We have captured %s's capital, sir! *\n");
02260         putsect(&sect);
02261     } else if (def->type == EF_SHIP) {
02262         getship(def->shp_uid, &ship);
02263         takeover_ship(&ship, player->cnum, 1);
02264         putship(ship.shp_uid, &ship);
02265     } else if (def->type == EF_LAND) {
02266         getland(def->lnd_uid, &land);
02267         takeover_land(&land, player->cnum, 1);
02268         putland(land.lnd_uid, &land);
02269     }
02270     if (delete_me)
02271         lnd_delete(delete_me, buf);
02272     att_get_combat(def, 0);
02273     return 1;
02274 }
02275 
02276 /*
02277  * Ask the attacker which mil & land units they'd like to move into the
02278  * conquered sector.
02279  */
02280 
02281 static void
02282 ask_move_in(struct combat *off, struct emp_qelem *olist,
02283             struct combat *def)
02284 {
02285     int n;
02286     struct emp_qelem *qp, *next;
02287     struct ulist *llp;
02288     char buf[512];
02289     char prompt[512];
02290     char land_answer[256];
02291     char *answerp;
02292 
02293     for (n = 0; n <= off->last; ++n)
02294         if (off[n].type != EF_BAD && off[n].troops > 0)
02295             if (off[n].mob) {
02296                 ask_move_in_off(&off[n], def);
02297                 if (player->aborted)
02298                     break;
02299             }
02300 
02301     if (QEMPTY(olist))
02302         return;
02303     memset(land_answer, 0, sizeof(land_answer));
02304     for (qp = olist->q_forw; qp != olist; qp = next) {
02305         next = qp->q_forw;
02306         llp = (struct ulist *)qp;
02307         answerp = &land_answer[(int)llp->unit.land.lnd_army];
02308         if (player->aborted || att_get_combat(def, 0) < 0)
02309             *answerp = 'N';
02310         if (*answerp == 'Y')
02311             continue;
02312         if (*answerp != 'N') {
02313             if (!get_land(A_ATTACK, def, llp->unit.land.lnd_uid, llp, 0))
02314                 continue;
02315             sprintf(prompt, "Move in with %s (%c %d%%) [ynYNq?] ",
02316                     prland(&llp->unit.land),
02317                     llp->unit.land.lnd_army ? llp->unit.land.lnd_army : '~',
02318                     llp->unit.land.lnd_effic);
02319             *answerp = att_prompt(prompt, llp->unit.land.lnd_army);
02320             if (player->aborted || att_get_combat(def, 0) < 0)
02321                 *answerp = 'N';
02322             if (!get_land(A_ATTACK, def, llp->unit.land.lnd_uid, llp, 0))
02323                 continue;
02324         }
02325         if (*answerp == 'y' || *answerp == 'Y')
02326             continue;
02327         sprintf(buf, "stays in %s",
02328                 xyas(llp->unit.land.lnd_x, llp->unit.land.lnd_y,
02329                      player->cnum));
02330         lnd_delete(llp, buf);
02331     }
02332     if (QEMPTY(olist))
02333         return;
02334     if (att_get_combat(def, 0) < 0) {
02335         for (qp = olist->q_forw; qp != olist; qp = next) {
02336             next = qp->q_forw;
02337             llp = (struct ulist *)qp;
02338             if (!get_land(A_ATTACK, def, llp->unit.land.lnd_uid, llp, 0))
02339                 continue;
02340             sprintf(buf, "stays in %s",
02341                     xyas(llp->unit.land.lnd_x, llp->unit.land.lnd_y,
02342                          player->cnum));
02343             lnd_delete(llp, buf);
02344         }
02345         return;
02346     }
02347     if (opt_INTERDICT_ATT)
02348         lnd_interdict(olist, def->x, def->y, player->cnum);
02349     move_in_land(A_ATTACK, off, olist, def);
02350 }
02351 
02352 /* Move offensive land units to the conquered sector or ship */
02353 
02354 static void
02355 move_in_land(int combat_mode, struct combat *off, struct emp_qelem *olist,
02356              struct combat *def)
02357 {
02358     struct emp_qelem *qp, *next;
02359     struct ulist *llp;
02360     char buf[512];
02361 
02362     if (QEMPTY(olist))
02363         return;
02364     for (qp = olist->q_forw; qp != olist; qp = next) {
02365         next = qp->q_forw;
02366         llp = (struct ulist *)qp;
02367         if (!get_land(combat_mode, def, llp->unit.land.lnd_uid, llp, 0))
02368             continue;
02369         take_move_in_mob(combat_mode, llp, off, def);
02370         llp->unit.land.lnd_x = def->x;
02371         llp->unit.land.lnd_y = def->y;
02372         if (def->type == EF_SHIP)
02373             llp->unit.land.lnd_ship = def->shp_uid;
02374         else
02375             llp->unit.land.lnd_ship = -1;
02376     }
02377     if (QEMPTY(olist))
02378         return;
02379     if (def->type == EF_SECTOR) {
02380         if (opt_INTERDICT_ATT) {
02381             lnd_sweep(olist, 0, 0, def->own);
02382             lnd_check_mines(olist);
02383         }
02384         sprintf(buf, "now occupies %s", prcom(0, def));
02385     } else {
02386         sprintf(buf, "boards %s", prcom(0, def));
02387     }
02388     if (QEMPTY(olist))
02389         return;
02390     for (qp = olist->q_forw; qp != olist; qp = next) {
02391         next = qp->q_forw;
02392         llp = (struct ulist *)qp;
02393         lnd_print(llp, buf);
02394     }
02395     if (QEMPTY(olist))
02396         return;
02397     unit_put(olist, 0);
02398 }
02399 
02400 /*
02401  * Move assaulting, paradropping, or boarding mil & units into def
02402  * If the mil are coming from a ship, then pack a lunch.
02403  */
02404 
02405 void
02406 att_move_in_off(int combat_mode, struct combat *off,
02407                 struct emp_qelem *olist, struct combat *def)
02408 {
02409     struct sctstr sect;
02410     struct shpstr ship;
02411     int troops, n;
02412     int lunchbox = 0;
02413 
02414     move_in_land(combat_mode, off, olist, def);
02415 
02416     for (n = 0; n <= off->last; ++n) {
02417         if (off[n].type == EF_BAD || !off[n].troops)
02418             continue;
02419         troops = off[n].troops;
02420         off[n].troops = 0;
02421         off[n].mil -= troops;
02422         def->mil += troops;
02423         put_combat(off + n);
02424         if (combat_mode == A_ASSAULT) {
02425             if (CANT_HAPPEN(off[n].type != EF_SHIP))
02426                 continue;
02427             getship(off[n].shp_uid, &ship);
02428             lunchbox += (int)((troops + 1) * ship.shp_item[I_FOOD]
02429                               / (ship.shp_item[I_MILIT] + troops
02430                                  + ship.shp_item[I_CIVIL] + 0.5));
02431 
02432             ship.shp_item[I_FOOD] -= lunchbox;
02433             putship(ship.shp_uid, &ship);
02434         }
02435     }
02436 
02437     put_combat(def);
02438 
02439     if (combat_mode == A_ASSAULT) {
02440         if (CANT_HAPPEN(def->type != EF_SECTOR))
02441             return;
02442         getsect(def->x, def->y, &sect);
02443         if (lunchbox > ITEM_MAX - sect.sct_item[I_FOOD])
02444             lunchbox = ITEM_MAX - sect.sct_item[I_FOOD];
02445         sect.sct_item[I_FOOD] += lunchbox;
02446         putsect(&sect);
02447     }
02448 }
02449 
02450 
02451 /* Ask how many mil to move in from each sector */
02452 
02453 static void
02454 ask_move_in_off(struct combat *off, struct combat *def)
02455 {
02456     int mob_support;
02457     int num_mil, dam = 0, left;
02458     double d, weight;
02459     char prompt[512];
02460     char buf[1024];
02461     char *p;
02462 
02463     if (att_get_combat(off, 0) <= 0)
02464         return;
02465     if (att_get_combat(def, 0) < 0)
02466         return;
02467     if (off->own != player->cnum)
02468         return;
02469     d = att_mobcost(off->own, def, MOB_MOVE);
02470     if ((mob_support = MIN(off->troops, (int)(off->mob / d))) <= 0)
02471         return;
02472     sprintf(prompt, "How many mil to move in from %s (%d max)? ",
02473             xyas(off->x, off->y, player->cnum), mob_support);
02474     if (!(p = getstring(prompt, buf)) || !*p || (num_mil = atoi(p)) <= 0)
02475         return;
02476 /* Make sure we don't move in more than we can support mobility-wise */
02477     if (num_mil > mob_support)
02478         num_mil = mob_support;
02479     if (att_get_combat(off, 0) <= 0)
02480         return;
02481     if (att_get_combat(def, 0) < 0)
02482         return;
02483     if ((num_mil = MIN(off->troops, num_mil)) <= 0) {
02484         pr("No mil moved in from %s\n",
02485            xyas(off->x, off->y, player->cnum));
02486         return;
02487     }
02488     mob_support = MAX(1, (int)(num_mil * d));
02489     off->mob -= MIN(off->mob, mob_support);
02490     off->mil -= num_mil;
02491     off->troops -= num_mil;
02492     put_combat(off);
02493     left = num_mil;
02494     weight = (double)num_mil * ichr[I_MILIT].i_lbs;
02495     if (opt_INTERDICT_ATT && chance(weight / 200.0)) {
02496         if (chance(weight / 100.0))
02497             dam +=
02498                 ground_interdict(def->x, def->y, player->cnum, "military");
02499         dam += check_lmines(def->x, def->y, weight);
02500     }
02501 
02502     if (dam) {
02503         left = commdamage(num_mil, dam, I_MILIT);
02504         if (left < num_mil) {
02505             if (left) {
02506                 pr("%d of the mil you were moving were destroyed!\nOnly %d mil made it to %s\n", num_mil - left, left, xyas(def->x, def->y, player->cnum));
02507             } else {
02508                 pr("All of the mil you were moving were destroyed!\n");
02509             }
02510         }
02511         /* maybe got nuked */
02512         if (att_get_combat(def, 0) < 0)
02513             return;
02514     }
02515     def->mil += left;
02516     put_combat(def);
02517 }
02518 
02519 
02520 /* Charge land units for moving into a sector or onto a ship */
02521 
02522 static void
02523 take_move_in_mob(int combat_mode, struct ulist *llp, struct combat *off,
02524                  struct combat *def)
02525 {
02526     int mobcost;
02527     int new;
02528 
02529     switch (combat_mode) {
02530     case A_ATTACK:
02531         mobcost = lnd_pathcost(&llp->unit.land,
02532                                att_mobcost(off->own, def,
02533                                            lnd_mobtype(&llp->unit.land)));
02534         new = llp->unit.land.lnd_mobil - mobcost;
02535         if (new < -127)
02536             new = -127;
02537         llp->unit.land.lnd_mobil = new;
02538         break;
02539     case A_ASSAULT:
02540         if (off->shp_mcp->m_flags & M_LAND) {
02541             if (((struct lchrstr *)llp->chrp)->l_flags & L_MARINE)
02542                 llp->unit.land.lnd_mobil -=
02543                     (float)etu_per_update * land_mob_scale * 0.5;
02544             else
02545                 llp->unit.land.lnd_mobil -= (float)etu_per_update *
02546                     land_mob_scale;
02547         } else {
02548             if (((struct lchrstr *)llp->chrp)->l_flags & L_MARINE)
02549                 llp->unit.land.lnd_mobil = 0;
02550             else
02551                 llp->unit.land.lnd_mobil = -(float)etu_per_update *
02552                     land_mob_scale;
02553         }
02554         break;
02555     case A_BOARD:
02556         /* I arbitrarily chose the numbers 10 and 40 below -KHS */
02557         if (((struct lchrstr *)llp->chrp)->l_flags & L_MARINE)
02558             llp->unit.land.lnd_mobil -= 10;
02559         else
02560             llp->unit.land.lnd_mobil -= 40;
02561         break;
02562     }
02563     llp->unit.land.lnd_harden = 0;
02564 }
02565 
02566 static void
02567 free_list(struct emp_qelem *list)
02568 {
02569     struct emp_qelem *qp, *next;
02570 
02571     if (!list || QEMPTY(list))
02572         return;
02573 
02574     qp = list->q_forw;
02575     while (qp != list) {
02576         next = qp->q_forw;
02577         emp_remque(qp);
02578         free(qp);
02579         qp = next;
02580     }
02581 }
02582 
02583 int
02584 att_free_lists(struct emp_qelem *olist, struct emp_qelem *dlist)
02585 {
02586     free_list(olist);
02587     free_list(dlist);
02588     return RET_OK;
02589 }
02590 
02591 /*
02592  * sector_strength - Everyone starts at 1.  You can get up to a max
02593  *                   of d_dstr, depending on how much you build up the
02594  *                   defenses of the sector. 
02595  */
02596 
02597 double
02598 sector_strength(struct sctstr *sp)
02599 {
02600     double def = SCT_DEFENSE(sp) / 100.0;
02601     double base = sp->sct_type == SCT_MOUNT ? 2.0 : 1.0;
02602     double d = base + (dchr[sp->sct_type].d_dstr - base) * def;
02603 
02604     if (d > dchr[sp->sct_type].d_dstr)
02605         d = dchr[sp->sct_type].d_dstr;
02606     if (d < base)
02607         d = base;
02608     return d;
02609 }

Generated on Fri Mar 28 11:01:15 2008 for empserver by  doxygen 1.5.2