Before 4.0.6, depth charges required no guns, one military, did damage like shell fire from two guns, and used two shells. Missions were not quite consistent with that (bug). 4.0.6 changed depth charges to work exactly like shell fire (but without updating documentation accordingly): require guns and gun crew, non-zero firing range, scale damage and ammunition use with guns. Go back to the old model, but with damage like three guns, to avoid changing the stock game's dd now (three gun damage for two shells). Stock game's af changes from two gun damage for one shell, and nas from four gun damage for two shells. Factor out common depth-charging code into shp_dchrg().
449 lines
12 KiB
C
449 lines
12 KiB
C
/*
|
|
* Empire - A multi-player, client/server Internet based war game.
|
|
* Copyright (C) 1986-2008, Dave Pare, Jeff Bailey, Thomas Ruschak,
|
|
* Ken Stevens, Steve McClure
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* ---
|
|
*
|
|
* See files README, COPYING and CREDITS in the root of the source
|
|
* tree for related information and legal notices. It is expected
|
|
* that future projects/authors will amend these files as needed.
|
|
*
|
|
* ---
|
|
*
|
|
* torp.c: Fire torpedoes at enemy ships
|
|
*
|
|
* Known contributors to this file:
|
|
* Dave Pare
|
|
* Thomas Ruschak, 1992
|
|
* Ken Stevens, 1995
|
|
* Steve McClure, 2000
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "commands.h"
|
|
#include "damage.h"
|
|
#include "news.h"
|
|
#include "optlist.h"
|
|
#include "retreat.h"
|
|
#include "ship.h"
|
|
|
|
static void anti_torp(int f, int ntorping, int vshipown);
|
|
static int candchrg(struct shpstr *, struct shpstr *);
|
|
static int canshoot(struct shpstr *, struct shpstr *);
|
|
static int cantorp(struct shpstr *, struct shpstr *);
|
|
static void fire_dchrg(struct shpstr *, struct shpstr *, int);
|
|
static int fire_torp(struct shpstr *, struct shpstr *, int, int);
|
|
|
|
int
|
|
torp(void)
|
|
{
|
|
natid vshipown;
|
|
int range;
|
|
int dam;
|
|
int shells;
|
|
int subno;
|
|
int victno;
|
|
int erange;
|
|
double hitchance;
|
|
struct shpstr vship;
|
|
struct shpstr sub;
|
|
char *ptr;
|
|
struct nstr_item nbst;
|
|
char buf[1024];
|
|
char *sav;
|
|
int ntorping = 0;
|
|
char prompt[128];
|
|
|
|
if (!(sav = getstarg(player->argp[1], "From ship(s)? ", buf)))
|
|
return RET_SYN;
|
|
if (!snxtitem(&nbst, EF_SHIP, sav))
|
|
return RET_SYN;
|
|
while (nxtitem(&nbst, &sub)) {
|
|
if (sub.shp_own != player->cnum)
|
|
continue;
|
|
if ((mchr[(int)sub.shp_type].m_flags & M_TORP) == 0)
|
|
continue;
|
|
shells = sub.shp_item[I_SHELL];
|
|
if (shells < SHP_TORP_SHELLS)
|
|
shells += supply_commod(sub.shp_own, sub.shp_x, sub.shp_y,
|
|
I_SHELL, SHP_TORP_SHELLS - shells);
|
|
if (sub.shp_item[I_GUN] == 0 || shells < SHP_TORP_SHELLS)
|
|
continue;
|
|
if (sub.shp_item[I_MILIT] < 1)
|
|
continue;
|
|
if (sub.shp_effic < 60)
|
|
continue;
|
|
if (sub.shp_mobil <= 0)
|
|
continue;
|
|
ntorping++;
|
|
}
|
|
pr("%d ships are eligible to torp\n", ntorping);
|
|
snxtitem(&nbst, EF_SHIP, sav);
|
|
while (nxtitem(&nbst, &sub)) {
|
|
if (!sub.shp_own)
|
|
continue;
|
|
if (sub.shp_own != player->cnum) {
|
|
continue;
|
|
}
|
|
if ((mchr[(int)sub.shp_type].m_flags & M_TORP) == 0) {
|
|
pr("Ship # %d: A %s can't fire torpedoes!\n",
|
|
sub.shp_uid, mchr[(int)sub.shp_type].m_name);
|
|
continue;
|
|
}
|
|
shells = sub.shp_item[I_SHELL];
|
|
if (shells < SHP_TORP_SHELLS)
|
|
shells += supply_commod(sub.shp_own, sub.shp_x, sub.shp_y,
|
|
I_SHELL, SHP_TORP_SHELLS - shells);
|
|
if (sub.shp_item[I_GUN] == 0 || shells < SHP_TORP_SHELLS) {
|
|
pr("Ship #%d has insufficient armament\n", sub.shp_uid);
|
|
continue;
|
|
}
|
|
if (sub.shp_item[I_MILIT] < 1) {
|
|
pr("Ship #%d has insufficient crew\n", sub.shp_uid);
|
|
continue;
|
|
}
|
|
if (sub.shp_effic < 60) {
|
|
pr("Ship #%d torpedo tubes inoperative.\n", sub.shp_uid);
|
|
continue;
|
|
}
|
|
if (sub.shp_mobil <= 0) {
|
|
pr("Ship #%d has insufficient mobility\n", sub.shp_uid);
|
|
continue;
|
|
}
|
|
subno = sub.shp_uid;
|
|
sprintf(prompt, "Ship %d, target? ", sub.shp_uid);
|
|
if ((ptr = getstarg(player->argp[2], prompt, buf)) == 0)
|
|
return RET_SYN;
|
|
if (!check_ship_ok(&sub))
|
|
return RET_FAIL;
|
|
if ((victno = atoi(ptr)) < 0)
|
|
return RET_SYN;
|
|
if (!getship(victno, &vship))
|
|
return RET_FAIL;
|
|
if (!vship.shp_own)
|
|
return RET_FAIL;
|
|
vshipown = vship.shp_own;
|
|
if (victno == subno) {
|
|
pr("Shooting yourself, eh? How strange...\n");
|
|
continue;
|
|
}
|
|
if (mchr[(int)vship.shp_type].m_flags & M_SUB) {
|
|
if (!(mchr[(int)sub.shp_type].m_flags & M_SUBT)) {
|
|
pr("You can't torpedo a submarine!\n");
|
|
continue;
|
|
}
|
|
}
|
|
if ((mchr[(int)sub.shp_type].m_flags & M_SUB) == 0)
|
|
anti_torp(sub.shp_uid, ntorping, vshipown);
|
|
getship(sub.shp_uid, &sub);
|
|
if (sub.shp_own == 0) {
|
|
continue;
|
|
}
|
|
erange = roundrange(torprange(&sub));
|
|
pr("Effective torpedo range is %d.0\n", erange);
|
|
shells -= SHP_TORP_SHELLS;
|
|
sub.shp_item[I_SHELL] = shells;
|
|
putship(sub.shp_uid, &sub);
|
|
/* Mob cost for a torp is equal to the cost of 1/2 sector of movement */
|
|
sub.shp_mobil -= shp_mobcost(&sub) / 2.0;
|
|
pr("Whooosh... ");
|
|
getship(victno, &vship);
|
|
vshipown = vship.shp_own;
|
|
range = mapdist(sub.shp_x, sub.shp_y, vship.shp_x, vship.shp_y);
|
|
hitchance = DTORP_HITCHANCE(range, sub.shp_visib);
|
|
if (range <= erange) {
|
|
pr("Hitchance = %d%%\n", (int)(hitchance * 100));
|
|
}
|
|
/* Now, can the torpedo even get there? */
|
|
if (!line_of_sight(NULL, sub.shp_x, sub.shp_y,
|
|
vship.shp_x, vship.shp_y)) {
|
|
pr("BOOM!... Torpedo slams into land before reaching target.\n");
|
|
/* We only tell the victim if we were within range. */
|
|
if (range <= erange) {
|
|
if (vshipown != 0)
|
|
wu(0, vshipown, "Torpedo sighted @ %s by %s\n",
|
|
xyas(sub.shp_x, sub.shp_y, vshipown),
|
|
prship(&vship));
|
|
}
|
|
} else if (range > erange) {
|
|
pr("Out of range\n");
|
|
} else if (hitchance >= 1.0 || chance(hitchance)) {
|
|
pr("BOOM!...\n");
|
|
dam = TORP_DAMAGE();
|
|
if (vshipown != 0)
|
|
wu(0, vshipown, "%s in %s torpedoed %s for %d damage.\n",
|
|
prsub(&sub), xyas(sub.shp_x, sub.shp_y, vshipown),
|
|
prship(&vship), dam);
|
|
if (vship.shp_rflags & RET_TORPED) {
|
|
retreat_ship(&vship, 't');
|
|
shipdamage(&vship, dam);
|
|
} else
|
|
shipdamage(&vship, dam);
|
|
pr("Torpedo hit %s for %d damage.\n", prship(&vship), dam);
|
|
|
|
if (vship.shp_effic < SHIP_MINEFF)
|
|
pr("%s sunk!\n", prship(&vship));
|
|
putship(vship.shp_uid, &vship);
|
|
if (mchr[(int)sub.shp_type].m_flags & M_SUB)
|
|
nreport(vshipown, N_TORP_SHIP, 0, 1);
|
|
else
|
|
nreport(vshipown, N_SHIP_TORP, player->cnum, 1);
|
|
} else {
|
|
pr("Missed\n");
|
|
if (vshipown != 0)
|
|
wu(0, vshipown, "Torpedo sighted @ %s by %s\n",
|
|
xyas(sub.shp_x, sub.shp_y, vshipown), prship(&vship));
|
|
}
|
|
sub.shp_mission = 0;
|
|
putship(sub.shp_uid, &sub);
|
|
if (mchr[(int)sub.shp_type].m_flags & M_SUB)
|
|
anti_torp(sub.shp_uid, ntorping, vshipown);
|
|
}
|
|
return RET_OK;
|
|
}
|
|
|
|
static void
|
|
anti_torp(int f, int ntorping, int vshipown)
|
|
{
|
|
int range, erange;
|
|
struct shpstr sub;
|
|
struct shpstr dd;
|
|
int x;
|
|
|
|
getship(f, &sub);
|
|
|
|
if (sub.shp_own == vshipown)
|
|
return;
|
|
|
|
if ((mchr[(int)sub.shp_type].m_flags & M_SUB) == 0)
|
|
pr("Starting our attack run...\n");
|
|
|
|
x = 0;
|
|
while (getship(x++, &dd) && sub.shp_effic >= SHIP_MINEFF) {
|
|
if (dd.shp_own == 0)
|
|
continue;
|
|
if (dd.shp_own != vshipown)
|
|
continue;
|
|
if (!canshoot(&dd, &sub))
|
|
continue;
|
|
|
|
erange = roundrange(effrange(dd.shp_frnge, dd.shp_tech));
|
|
range = mapdist(sub.shp_x, sub.shp_y, dd.shp_x, dd.shp_y);
|
|
if (range > erange)
|
|
continue;
|
|
|
|
if (!line_of_sight(NULL, sub.shp_x, sub.shp_y, dd.shp_x, dd.shp_y))
|
|
continue;
|
|
|
|
if (cantorp(&dd, &sub)) {
|
|
/* Try torping.. if we can, maybe we can fire */
|
|
if (!fire_torp(&dd, &sub, range, ntorping))
|
|
if (candchrg(&dd, &sub))
|
|
fire_dchrg(&dd, &sub, ntorping);
|
|
} else
|
|
fire_dchrg(&dd, &sub, ntorping);
|
|
}
|
|
}
|
|
|
|
/* Can ship A shoot at ship B? */
|
|
static int
|
|
canshoot(struct shpstr *a, struct shpstr *b)
|
|
{
|
|
/* Anyone can shoot a normal ship */
|
|
if ((mchr[(int)b->shp_type].m_flags & M_SUB) == 0)
|
|
return 1;
|
|
|
|
/* You can depth-charge a sub */
|
|
if (mchr[(int)a->shp_type].m_flags & M_DCH)
|
|
return 1;
|
|
|
|
/* If you have SUBT flag, you can torp a sub */
|
|
if (mchr[(int)a->shp_type].m_flags & M_SUBT)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Can ship A torp ship B? */
|
|
static int
|
|
cantorp(struct shpstr *a, struct shpstr *b)
|
|
{
|
|
if ((mchr[(int)a->shp_type].m_flags & M_TORP) == 0)
|
|
return 0;
|
|
|
|
/* Anyone with TORP flag can torp a normal ship */
|
|
if ((mchr[(int)b->shp_type].m_flags & M_SUB) == 0)
|
|
return 1;
|
|
|
|
/* Ship b is a sub, so we need to have the SUBT flag */
|
|
if (mchr[(int)a->shp_type].m_flags & M_SUBT)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Can ship A depth-charge (or fire guns at) ship B? */
|
|
static int
|
|
candchrg(struct shpstr *a, struct shpstr *b)
|
|
{
|
|
if ((mchr[(int)b->shp_type].m_flags & M_SUB) == 0) {
|
|
if ((mchr[(int)a->shp_type].m_flags & M_SUB) == 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if ((mchr[(int)a->shp_type].m_flags & M_DCH) == 0)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
fire_dchrg(struct shpstr *sp, struct shpstr *targ, int ntargets)
|
|
{
|
|
int dam;
|
|
int shells;
|
|
int gun;
|
|
double guneff;
|
|
|
|
if ((mchr[(int)targ->shp_type].m_flags & M_SUB) == 0) {
|
|
shells = sp->shp_item[I_SHELL];
|
|
gun = sp->shp_item[I_GUN];
|
|
gun = MIN(gun, sp->shp_glim);
|
|
gun = MIN(gun, sp->shp_item[I_MILIT] / 2);
|
|
|
|
shells += supply_commod(sp->shp_own, sp->shp_x, sp->shp_y,
|
|
I_SHELL, (gun + 1) / 2 - shells);
|
|
|
|
gun = MIN(gun, shells * 2);
|
|
if (gun == 0)
|
|
return;
|
|
|
|
/* ok, all set.. now, we shoot */
|
|
shells -= ldround(gun / 2.0, 1);
|
|
sp->shp_item[I_SHELL] = shells;
|
|
putship(sp->shp_uid, sp);
|
|
|
|
guneff = seagun(sp->shp_effic, gun);
|
|
dam = (int)guneff;
|
|
if (ntargets > 2)
|
|
dam /= ntargets / 2;
|
|
|
|
pr_beep();
|
|
pr("Kaboom!!! Incoming shells!\n");
|
|
if (sp->shp_own != 0)
|
|
wu(0, sp->shp_own,
|
|
"%s fired at %s\n", prship(sp), prship(targ));
|
|
pr_beep();
|
|
pr("BLAM! %d damage!\n", dam);
|
|
shipdamage(targ, dam);
|
|
putship(targ->shp_uid, targ);
|
|
} else {
|
|
dam = shp_dchrg(sp);
|
|
putship(sp->shp_uid, sp);
|
|
if (dam < 0)
|
|
return;
|
|
if (ntargets > 2)
|
|
dam /= ntargets / 2;
|
|
|
|
pr("\nCAPTAIN! !!Depth charges!!...\n");
|
|
if (sp->shp_own != 0)
|
|
wu(0, sp->shp_own,
|
|
"%s depth charged %s\n", prship(sp), prsub(targ));
|
|
pr("click...WHAM! %d damage!\n", dam);
|
|
shipdamage(targ, dam);
|
|
putship(targ->shp_uid, targ);
|
|
}
|
|
}
|
|
|
|
static int
|
|
fire_torp(struct shpstr *sp, struct shpstr *targ, int range, int ntargets)
|
|
{
|
|
int dam;
|
|
int shells;
|
|
double hitchance;
|
|
|
|
shells = sp->shp_item[I_SHELL];
|
|
|
|
if (shells < SHP_TORP_SHELLS)
|
|
shells += supply_commod(sp->shp_own, sp->shp_x, sp->shp_y,
|
|
I_SHELL, SHP_TORP_SHELLS - shells);
|
|
|
|
if (sp->shp_item[I_GUN] == 0 || shells < SHP_TORP_SHELLS)
|
|
return 0;
|
|
|
|
if (sp->shp_item[I_MILIT] < 1)
|
|
return 0;
|
|
|
|
if (sp->shp_effic < 60)
|
|
return 0;
|
|
|
|
if (sp->shp_mobil <= 0)
|
|
return 0;
|
|
|
|
/* All set.. fire! */
|
|
shells -= SHP_TORP_SHELLS;
|
|
sp->shp_item[I_SHELL] = shells;
|
|
putship(sp->shp_uid, sp);
|
|
|
|
/* Mob cost for a torp is equal to the cost of 1/2 sector of movement */
|
|
sp->shp_mobil -= shp_mobcost(sp) / 2.0;
|
|
|
|
hitchance = DTORP_HITCHANCE(range, sp->shp_visib);
|
|
|
|
pr("Captain! Torpedoes sighted!\n");
|
|
|
|
if (chance(hitchance)) {
|
|
pr("BOOM!...\n");
|
|
if (sp->shp_own != 0)
|
|
wu(0, sp->shp_own, "%s @ %s torpedoed %s\n",
|
|
prship(sp),
|
|
xyas(sp->shp_x, sp->shp_y, sp->shp_own), prsub(targ));
|
|
dam = TORP_DAMAGE();
|
|
if (ntargets > 2)
|
|
dam /= ntargets / 2;
|
|
|
|
shipdamage(targ, dam);
|
|
putship(targ->shp_uid, targ);
|
|
|
|
if (mchr[(int)sp->shp_type].m_flags & M_SUB)
|
|
nreport(targ->shp_own, N_TORP_SHIP, 0, 1);
|
|
else
|
|
nreport(targ->shp_own, N_SHIP_TORP, player->cnum, 1);
|
|
} else {
|
|
pr("Missed!\n");
|
|
if (sp->shp_own != 0)
|
|
wu(0, sp->shp_own,
|
|
"%s missed %s with a torp at %s\n",
|
|
prship(sp), prsub(targ),
|
|
xyas(sp->shp_x, sp->shp_y, sp->shp_own));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
char *
|
|
prsub(struct shpstr *sp)
|
|
{
|
|
if (mchr[(int)sp->shp_type].m_flags & M_SUB)
|
|
return "sub";
|
|
else
|
|
return prship(sp);
|
|
}
|