btmux-0.6-rc3/doc/
btmux-0.6-rc3/event/
btmux-0.6-rc3/game/
btmux-0.6-rc3/game/maps/
btmux-0.6-rc3/game/mechs/
btmux-0.6-rc3/include/
btmux-0.6-rc3/misc/
btmux-0.6-rc3/python/
btmux-0.6-rc3/src/hcode/btech/
btmux-0.6-rc3/tree/
/*
 * $Id: bsuit.c,v 1.4 2005/08/10 14:09:34 av1-op Exp $
 *
 * Author: Markus Stenberg <fingon@iki.fi>
 *
 *  Copyright (c) 1997 Markus Stenberg
 *  Copyright (c) 1999-2000 Marco Peter Hoogeveen
 *  Copyright (c) 1998-2002 Thomas Wouters
 *  Copyright (c) 2000-2002 Cord Awtry
 *  Copyright (c) 1999-2005 Kevin Stevens
 *       All rights reserved
 *
 * Created: Thu Feb 13 21:19:23 1997 fingon
 * Last modified: Mon Jul 20 00:35:23 1998 fingon
 *
 */

#include <math.h>

#include "mech.h"
#include "mech.events.h"
#include "p.mech.utils.h"
#include "p.mech.combat.h"
#include "p.mech.damage.h"
#include "p.mech.los.h"
#include "p.mech.move.h"
#include "p.crit.h"
#include "p.mech.bth.h"
#include "p.mech.update.h"

/*! \todo {The Bsuit code needs an overhaul} */

/* 2 battlesuit-specific attacks:
   - attackleg
   - swarm
 */

#define MyHiddenTurns(mech) ((MechType(mech) == CLASS_MW ? 1 : MechType(mech) == CLASS_BSUIT ? 3 : MechType(mech) == CLASS_VTOL ? 4 : 5) * ((MechSpecials2(mech) & CAMO_TECH) ? 1 : 2))

/* Stops everyone who's swarming this poor guy */

#define RECYCLE_SWARM (PHYSICAL_RECYCLE_TIME / 3)
#define RECYCLE_ATTACKLEG (PHYSICAL_RECYCLE_TIME / 2)
#define RECYCLE_INT_STOPSWARM (PHYSICAL_RECYCLE_TIME / 3)
#define RECYCLE_UNINT_STOPSWARM (PHYSICAL_RECYCLE_TIME / 2)
#define RECYCLE_FALL_STOPSWARM ((PHYSICAL_RECYCLE_TIME / 4) * 3)

char *GetBSuitName(MECH * mech)
{
    return (MechSpecials(mech) & CLAN_TECH) ? "Point" : "Squad";
}

char *GetLCaseBSuitName(MECH * mech)
{
    return (MechSpecials(mech) & CLAN_TECH) ? "point" : "squad";
}

void StartBSuitRecycle(MECH * mech, int time)
{
    int i;

    for (i = 0; i < NUM_BSUIT_MEMBERS; i++)
	if (GetSectInt(mech, i))
	    SetRecycleLimb(mech, i, time);
}

void StopSwarming(MECH * mech, int intentional)
{
    MECH *target = getMech(MechSwarmTarget(mech));

    if (!target || MechSwarmTarget(mech) <= 0)
	return;

    MechSwarmTarget(mech) = -1;

    if (intentional > 0) {
	mech_notify(mech, MECHALL,
	    "You let your hold loosen and you drop from the 'mech!");
	mech_notify(target, MECHALL, tprintf("%s lets go of you!",
		GetMechToMechID(target, mech)));
	MechLOSBroadcasti(mech, target, "lets go of %s!");

	StartBSuitRecycle(mech, RECYCLE_INT_STOPSWARM);
    } else {
	if (MadePilotSkillRoll(mech, 4)) {
	    mech_notify(mech, MECHALL,
		"The hold loosens and you drop from the 'mech!");
	    MechLOSBroadcasti(mech, target, "jumps off of %s!");
	    mech_notify(target, MECHALL, tprintf("%s jumps off!",
		    GetMechToMechID(target, mech)));

	    StartBSuitRecycle(mech, RECYCLE_UNINT_STOPSWARM);
	} else {
	    mech_notify(mech, MECHALL,
		"You're suprised by the sudden action and find yourself rapidly approaching the ground!");
	    MechLOSBroadcasti(mech, target, "falls off %s!");
	    mech_notify(target, MECHALL, tprintf("%s falls off!",
		    GetMechToMechID(target, mech)));

	    DamageMech(mech, mech, 1, -1, Number(0, NUM_BSUIT_MEMBERS - 1),
		0, 0, 11, 0, -1, 0, -1, 0, 1);

	    StartBSuitRecycle(mech, RECYCLE_FALL_STOPSWARM);
	}
    }

    MechSpeed(mech) = 0;
    MaybeMove(mech);
    DropSetElevation(mech, 0);
    MechFloods(mech);
}

int IsMechSwarmed(MECH * mech)
{
    MAP *map = FindObjectsData(mech->mapindex);
    int count = 0, i, j;
    MECH *t;

    if (!map)
	return 0;

    for (i = 0; i < map->first_free; i++)
	if ((j = map->mechsOnMap[i]) > 0 && i != mech->mapnumber) {
	    if (!(t = FindObjectsData(j)))
		continue;

	    if (MechSwarmTarget(t) != mech->mynum)
		continue;

	    if (MechTeam(mech) == MechTeam(t))
		continue;

	    count++;
	    break;
	}
    return count > 0;
}

int IsMechMounted(MECH * mech)
{
    MAP *map = FindObjectsData(mech->mapindex);
    int count = 0, i, j;
    MECH *t;

    if (!map)
	return 0;

    for (i = 0; i < map->first_free; i++)
	if ((j = map->mechsOnMap[i]) > 0 && i != mech->mapnumber) {
	    if (!(t = FindObjectsData(j)))
		continue;

	    if (MechSwarmTarget(t) != mech->mynum)
		continue;

	    if (MechTeam(mech) != MechTeam(t))
		continue;

	    count++;
	    break;
	}
    return count > 0;
}

int CountSwarmers(MECH * mech)
{
    MAP *map = FindObjectsData(mech->mapindex);
    int count = 0, i, j;
    MECH *t;

    if (!map)
	return 0;
    for (i = 0; i < map->first_free; i++)
	if ((j = map->mechsOnMap[i]) > 0 && i != mech->mapnumber) {
	    if (!(t = FindObjectsData(j)))
		continue;
	    if (MechSwarmTarget(t) != mech->mynum)
		continue;
	    count++;
	}
    return count;
}

MECH *findSwarmers(MECH * mech)
{
    MAP *map = FindObjectsData(mech->mapindex);
    int i, j;
    MECH *t;

    if (!map)
	return 0;

    for (i = 0; i < map->first_free; i++)
	if ((j = map->mechsOnMap[i]) > 0 && i != mech->mapnumber) {
	    if (!(t = FindObjectsData(j)))
		continue;

	    if (MechSwarmTarget(t) == mech->mynum) {
		return t;
	    }
	}

    return NULL;
}

void StopBSuitSwarmers(MAP * map, MECH * mech, int intentional)
{
    int i, j;
    MECH *t;

    if (!map || !mech)
	return;
    for (i = 0; i < map->first_free; i++)
	if ((j = map->mechsOnMap[i]) > 0 && i != mech->mapnumber) {
	    if (!(t = FindObjectsData(j)))
		continue;
	    if (MechSwarmTarget(t) != mech->mynum)
		continue;
	    StopSwarming(t, intentional);
	}
}

void BSuitMirrorSwarmedTarget(MAP * map, MECH * mech)
{
    int i, j;
    MECH *t;

    for (i = 0; i < map->first_free; i++)
	if ((j = map->mechsOnMap[i]) > 0 && i != mech->mapnumber) {
	    if (!(t = FindObjectsData(j)))
		continue;
	    if (MechSwarmTarget(t) != mech->mynum)
		continue;
	    MirrorPosition(mech, t, 1);
	}
}

int doBSuitCommonChecks(MECH * mech, dbref player)
{
    int i;

    DOCHECK1(Jumping(mech), "Unavailable when jumping - sorry.");
    DOCHECK1(MechSwarmTarget(mech) > 0,
	"You are already busy with a special attack!");
#ifdef BT_MOVEMENT_MODES
    DOCHECK1(MoveModeLock(mech),
	"Unavailable when performing movement modes - deal.");
#endif
    for (i = 0; i < NUM_BSUIT_MEMBERS; i++) {
	DOCHECK1(!SectIsDestroyed(mech, i) &&
	    MechSections(mech)[i].recycle,
	    tprintf("Suit %d is still recovering from attack.", i + 1));
    }

    return 0;
}

int CountBSuitMembers(MECH * mech)
{
    int i, j = 0;

    for (i = 0; i < NUM_BSUIT_MEMBERS; i++)
	if (GetSectInt(mech, i))
	    j++;
    return j;
}

int FindBSuitTarget(dbref player, MECH * mech, MECH ** target,
    char *buffer)
{
    int argc;
    char *args[3];
    float range;
    char targetID[2];
    int targetnum;
    MECH *t = NULL;

    DOCHECK1((argc =
	    mech_parseattributes(buffer, args, 3)) > 1,
	"Invalid arguments!");
    switch (argc) {
    case 0:
	DOCHECK1(MechTarget(mech) <= 0,
	    "You do not have a default target set!");
	t = getMech(MechTarget(mech));
	if (!(t)) {
	    mech_notify(mech, MECHALL, "Invalid default target!");
	    MechTarget(mech) = -1;
	    return 1;
	}
	break;
    case 1:
	targetID[0] = args[0][0];
	targetID[1] = args[0][1];
	targetnum = FindTargetDBREFFromMapNumber(mech, targetID);
	DOCHECK1(targetnum <= 0, "Target is not in line of sight!");
	t = getMech(targetnum);
	DOCHECK1(!(t), "Invalid default target!");
	break;
    default:
	notify(player, "Invalid target!");
	return 1;
    }
    range = FaMechRange(mech, t);
    DOCHECK1(!InLineOfSight_NB(mech, t, MechX(t), MechY(t), range),
	     "Target is not in line of sight!");
    DOCHECK1(range >= 1.0, "Target out of range!");
    DOCHECK1(Jumping(t), "That target's unreachable right now!");
    DOCHECK1(MechType(t) != CLASS_MECH, "That target is of invalid type.");
    DOCHECK1(Destroyed(t), "A dead 'mech? C'mon :P");
    *target = t;
    return 0;
}

int doJettisonChecks(MECH * mech)
{
    int i, j;

    if (!(MechInfantrySpecials(mech) & MUST_JETTISON_TECH))
	return 0;

    for (i = 0; i < NUM_BSUIT_MEMBERS; i++) {
	for (j = 0; j < NUM_CRITICALS; j++) {
	    if ((GetPartFireMode(mech, i, j) & WILL_JETTISON_MODE) &&
		(!(GetPartFireMode(mech, i, j) & IS_JETTISONED_MODE))) {
		mech_notify(mech, MECHALL,
		    tprintf
		    ("Suit %d can not perform this feat before it jettisons its backpack!",
			i + 1));

		return 1;
	    }
	}
    }

    return 0;
}

void bsuit_swarm(dbref player, void *data, char *buffer)
{
    MECH *mech = data;
    MECH *target;
    int baseToHit = 4;
    int tIsMount = 0;

    cch(MECH_USUALO);
    skipws(buffer);

    /* Stop swarming... */
    if (!strcmp(buffer, "-")) {
	if (MechSwarmTarget(mech) > 0) {
	    StopSwarming(mech, 1);
	    return;
	}
    }

    if (doBSuitCommonChecks(mech, player))
	return;

    if (FindBSuitTarget(player, mech, &target, buffer))
	return;

    /* See if we're 'swarming' or 'mounting' */
    if (MechTeam(target) == MechTeam(mech)) {
	/* Make sure this type of bsuit has the ability to mount */
	if (!(MechInfantrySpecials(mech) & INF_SWARM_TECH)) {
	    mech_notify(mech, MECHALL,
		"These battlesuits are not capable of mounting mechs!");
	    return;
	}

	tIsMount = 1;
    } else {
	if (doJettisonChecks(mech))
	    return;

	/* Make sure this type of bsuit has the ability to swarm */
	if (!(MechInfantrySpecials(mech) & INF_SWARM_TECH)) {
	    mech_notify(mech, MECHALL,
		"These battlesuits are not capable of performing swarm attacks!");
	    return;
	}
    }

    /* Make sure there are no suits already on us */
    if (CountSwarmers(mech) > 0) {
	mech_notify(mech, MECHALL,
	    "That target already have battlesuits crawling all over it! There's no room for you!");

	return;
    }

    /* get our BTH... we make it easier for mounting */
    switch (CountBSuitMembers(mech)) {
    case 1:
    case 2:
    case 3:
	baseToHit = 5;
	break;

    default:
	baseToHit = 2;
	break;
    }

    if (tIsMount)
	baseToHit -= 4;

    if (MechCritStatus(mech) & HIDDEN) {
	if (Immobile(target))
	    baseToHit -= 4;

	if (Fallen(target))
	    baseToHit -= 4;
    } else {
	baseToHit += TargetMovementMods(mech, target, 0.0);

	if (Fallen(target))
	    baseToHit -= 2;
    }

    /* Well, we're here. Let's see if it works. */
    if (MadePilotSkillRoll(mech, baseToHit)) {
	mech_notify(target, MECHALL, tprintf("%s %s you!",
		GetMechToMechID(target, mech),
		(tIsMount ? "mounts" : "swarms")));
	mech_notify(mech, MECHALL, tprintf("You %s %s!",
		(tIsMount ? "mount" : "swarm"), GetMechToMechID(mech,
		    target)));

	MechSwarmTarget(mech) = target->mynum;

	if (tIsMount) {
	    MechLOSBroadcasti(mech, target, "mounts %s!");
	} else {
	    MechLOSBroadcasti(mech, target, "swarms %s!");
	}

	MechSpeed(mech) = 0.0;
	MechDesiredSpeed(mech) = 0.0;
	SetFacing(mech, 270);
	MechDesiredFacing(mech) = 270;
	MirrorPosition(target, mech, 1);
	StopLock(mech);
    } else {
	mech_notify(target, MECHALL, tprintf("%s attempts to %s you!",
		GetMechToMechID(target, mech),
		(tIsMount ? "mount" : "swarm")));
	mech_notify(mech, MECHALL,
	    tprintf
	    ("Nice try, but you don't succeed in your attempt at %s %s!",
		(tIsMount ? "mounting" : "swarming"), GetMechToMechID(mech,
		    target)));
    }

    StartBSuitRecycle(mech, RECYCLE_SWARM);
}

void bsuit_attackleg(dbref player, void *data, char *buffer)
{
    MECH *mech = data;
    MECH *target;
    int baseToHit = 0;
    int wLegTemp = -1;
    int wLegID = -1;
    int wCritRoll = 0;
    char strAttackLoc[50];

    cch(MECH_USUALO);

    if (!(MechInfantrySpecials(mech) & INF_ANTILEG_TECH)) {
	mech_notify(mech, MECHALL,
	    "These battlesuits are not capable of performing leg attacks!");
	return;
    }

    if (doBSuitCommonChecks(mech, player))
	return;

    if (doJettisonChecks(mech))
	return;

    if (FindBSuitTarget(player, mech, &target, buffer))
	return;

    DOCHECK(IsMechLegLess(mech), "That mech has no legs to grab!");
    DOCHECK((MechTeam(mech) == MechTeam(target)),
	"You can't attack the leg of a friendly mech!");

    switch (CountBSuitMembers(mech)) {
    case 1:
	baseToHit = 7;
	break;

    case 2:
	baseToHit = 5;
	break;

    case 3:
	baseToHit = 2;
	break;

    default:
	baseToHit = -1;
	break;
    }

    if (MechCritStatus(mech) & HIDDEN) {
	if (Immobile(target))
	    baseToHit -= 4;

	if (Fallen(target))
	    baseToHit -= 2;
    } else {
	baseToHit += TargetMovementMods(mech, target, 0.0);
    }

    if (MechIsQuad(target)) {
	do {
	    switch (Number(0, 3)) {
	    case 0:
		wLegTemp = RLEG;
		break;

	    case 1:
		wLegTemp = LLEG;
		break;

	    case 2:
		wLegTemp = RARM;
		break;

	    case 3:
		wLegTemp = LARM;
		break;
	    }

	    if (GetSectInt(target, wLegTemp))
		wLegID = wLegTemp;

	} while (wLegID == -1);
    } else {
	wLegTemp = (Number(0, 1)) ? RLEG : LLEG;

	if (GetSectInt(target, wLegTemp) == 0) {
	    wLegID = (wLegTemp == RLEG) ? LLEG : RLEG;
	} else {
	    wLegID = wLegTemp;
	}
    }

    ArmorStringFromIndex(wLegID, strAttackLoc, MechType(target),
	MechMove(target));

    mech_notify(mech, MECHALL,
	tprintf("You go for %s's %s, placing explosives in the joints!",
	    GetMechToMechID(mech, target), strAttackLoc));

    if (MadePilotSkillRoll(mech, baseToHit)) {
	mech_notify(target, MECHALL,
	    tprintf
	    ("%s swarms your %s putting small packets of explosives all over it!",
		GetMechToMechID(target, mech), strAttackLoc));

	MechLOSBroadcasti(mech, target, "attacks %s's legs!");

	/* find out if we do a crit or damage */
	wCritRoll = Roll();

	if (wCritRoll >= 8) {
	    mech_notify(target, MECHALL,
		tprintf
		("The explosives manage to rip into the internals of your %s!",
		    strAttackLoc));

	    switch (wCritRoll) {
	    case 8:
	    case 9:
		HandleCritical(target, mech, 1, wLegID, 1);
		break;
	    case 10:
	    case 11:
		HandleCritical(target, mech, 1, wLegID, 2);
		break;
	    case 12:
		switch (wLegID) {
		case RARM:
		case LARM:
		case RLEG:
		case LLEG:
		    /* Limb blown off */
		    mech_notify(target, MECHALL, "%ch%cyCRITICAL HIT!!%c");

		    MechLOSBroadcast(target,
			tprintf
			("'s %s is blown off in a shower of sparks and smoke!",
			    strAttackLoc));
		    DestroySection(target, mech, 1, wLegID);
		default:
		    HandleCritical(target, mech, 1, wLegID, 3);
		}
		break;
	    default:
		break;
	    }
	} else {
	    mech_notify(target, MECHALL,
		tprintf
		("The explosives explode on the surface of your %s!",
		    strAttackLoc));
	    DamageMech(target, mech, 1, MechPilot(mech), wLegID, 0, 1, 4,
		0, -1, 0, -1, 0, 1);
	}
    } else {
	mech_notify(target, MECHALL,
	    tprintf
	    ("%s attempts to attacks your legs, but misses miserably.",
		GetMechToMechID(target, mech)));

	mech_notify(mech, MECHALL,
	    tprintf
	    ("You realize that this is harder than it looks and fail in your attempt at hitting %s's legs!",
		GetMechToMechID(mech, target)));

	MechLOSBroadcasti(mech, target,
	    "attacks to climb %s's legs, but fails miserably!");
    }

    StartBSuitRecycle(mech, RECYCLE_ATTACKLEG);
}

static void mech_hide_event(MUXEVENT * e)
{
    MECH *mech = (MECH *) e->data;
    MECH *t;
    MAP *map = getMap(mech->mapindex);
    int fail = 0, i;
    int tic = (int) e->data2;

    if (!map)
        return;

    for (i = 0; i < map->first_free; i++) {
        if (map->mechsOnMap[i] <= 0)
            continue;
        if (!(t = getMech(map->mechsOnMap[i])))
            continue;
        if (MechCritStatus(t) & (CLAIRVOYANT|OBSERVATORIC|INVISIBLE))
            continue;
        if (MechTeam(t) == MechTeam(mech))
            continue;
        if (!Started(t))
            continue;
        if (Destroyed(t))
            continue;
        if (InLineOfSight(t, mech, MechX(mech), MechY(mech), 
                FaMechRange(t, mech)))
            fail = 1;
    }

    if (MechsElevation(mech))
        fail = 1;

    if (fail) {
        mech_notify(mech, MECHALL,
            "Your spidey sense tingles, telling you this isn't going to work......");
        return;
    } else if (tic < (MyHiddenTurns(mech) * HIDE_TICK)) {
        tic++;
        MECHEVENT(mech, EVENT_HIDE, mech_hide_event, 1, tic);
    } else if (!fail) {
        mech_notify(mech, MECHALL, "You are now hidden!");
        MechCritStatus(mech) |= HIDDEN;
    }
    return;
}

void bsuit_hide(dbref player, void *data, char *buffer)
{
    int i;
    MECH *mech = data;
    MECH *t;
    MAP *map = FindObjectsData(mech->mapindex);
    int terrain;

    cch(MECH_USUALO);
    DOCHECK(((HasCamo(mech)) || (Wizard(player))) ? 0 : MechType(mech) != CLASS_BSUIT && 
            MechType(mech) != CLASS_MW, "You aren't capable of such curious things.");

    if (!map) {
        mech_notify(mech, MECHALL, "You are not on a map!");
        return;
    }

    DOCHECK(Jumping(mech) || OODing(mech), "Hide where? Up here?");
    DOCHECK((fabs(MechSpeed(mech)) > MP1), "Come to a complete stop first.");
    DOCHECK(Hiding(mech), "You are looking for cover already!");
    DOCHECK(MechMove(mech) == MOVE_VTOL && !Landed(mech), "You must be landed!");

    terrain = GetRTerrain(map, MechX(mech), MechY(mech));

    if (IsForest(terrain)) {
        mech_notify(mech, MECHALL,
            "You start to hide amongst the trees...");
    } else if (IsMountains(terrain)) {
        mech_notify(mech, MECHALL,
            "You start to hide behind some rocky outcroppings...");
    } else if (IsRough(terrain)) {
        mech_notify(mech, MECHALL,
            "You find some boulders to try to hide behind...");
    } else if ((IsBuilding(terrain)) && (MechType(mech) == CLASS_BSUIT)) {
        mech_notify(mech, MECHALL,
            "You break into a building and look for a spot to hide...");
    } else {
        mech_notify(mech, MECHALL,
            "You begin to hide in this terrain...");
        mech_notify(mech, MECHALL,
            "... then realize that just isn't going to work!");
        return;
    }

    MECHEVENT(mech, EVENT_HIDE, mech_hide_event, 1, 0);
}

void JettisonPacks(dbref player, void *data, char *buffer)
{
    MECH *mech = data;
    int wcJettisoned = 0;
    int wcSuits = 0;
    int i, j;

    cch(MECH_USUALO);
    DOCHECK((!(MechInfantrySpecials(mech) & CAN_JETTISON_TECH)),
	"You have no backpack that is capable of being jettisoned!");

    for (i = 0; i < NUM_BSUIT_MEMBERS; i++) {
	for (j = 0; j < NUM_CRITICALS; j++) {
	    if ((GetPartFireMode(mech, i, j) & WILL_JETTISON_MODE) &&
		(!(GetPartFireMode(mech, i, j) & IS_JETTISONED_MODE))) {

		GetPartFireMode(mech, i, j) |= DESTROYED_MODE;
		GetPartFireMode(mech, i, j) |= IS_JETTISONED_MODE;
		GetPartFireMode(mech, i, j) &=
		    ~(BROKEN_MODE | DISABLED_MODE);

		wcJettisoned++;
	    }
	}
    }

    if (wcJettisoned > 0) {
	wcSuits = CountBSuitMembers(mech);

	if (wcSuits > 1) {
	    mech_notify(mech, MECHALL,
		"The explosive bolts that hold the backpacks on blow, allowing them to drop to the ground.");
	    MechLOSBroadcast(mech,
		"'s backpacks blow off in a shower of small explosions!");
	} else {
	    mech_notify(mech, MECHALL,
		"The explosive bolts that hold your backpack on blows, allowing it to drop to the ground.");
	    MechLOSBroadcast(mech,
		"'s backpack blows off in a puff of grey smoke!");
	}
    } else {
	mech_notify(mech, MECHALL,
	    "You realize you have nothing to jettison. Maybe you already did it?");
    }
}