mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-27 06:22:18 +00:00
6c6fdaf332
All callers left it at 0.
2669 lines
76 KiB
C++
2669 lines
76 KiB
C++
//-------------------------------------------------------------------------
|
|
/*
|
|
Copyright (C) 2010-2019 EDuke32 developers and contributors
|
|
Copyright (C) 2019 Nuke.YKT
|
|
|
|
This file is part of NBlood.
|
|
|
|
NBlood is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License version 2
|
|
as published by the Free Software Foundation.
|
|
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
//-------------------------------------------------------------------------
|
|
#include "ns.h" // Must come before everything else!
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "automap.h"
|
|
#include "build.h"
|
|
|
|
#include "blood.h"
|
|
#include "gstrings.h"
|
|
#include "gamestate.h"
|
|
#include "automap.h"
|
|
|
|
BEGIN_BLD_NS
|
|
|
|
PLAYER gPlayer[kMaxPlayers];
|
|
|
|
bool gBlueFlagDropped = false;
|
|
bool gRedFlagDropped = false;
|
|
|
|
// V = has effect in game, X = no effect in game
|
|
POWERUPINFO gPowerUpInfo[kMaxPowerUps] = {
|
|
{ -1, 1, 1, 1 }, // 00: V keys
|
|
{ -1, 1, 1, 1 }, // 01: V keys
|
|
{ -1, 1, 1, 1 }, // 02: V keys
|
|
{ -1, 1, 1, 1 }, // 03: V keys
|
|
{ -1, 1, 1, 1 }, // 04: V keys
|
|
{ -1, 1, 1, 1 }, // 05: V keys
|
|
{ -1, 1, 1, 1 }, // 06: V keys
|
|
{ -1, 0, 100, 100 }, // 07: V doctor's bag
|
|
{ -1, 0, 50, 100 }, // 08: V medicine pouch
|
|
{ -1, 0, 20, 100 }, // 09: V life essense
|
|
{ -1, 0, 100, 200 }, // 10: V life seed
|
|
{ -1, 0, 2, 200 }, // 11: V red potion
|
|
{ 783, 0, 3600, 432000 }, // 12: V feather fall
|
|
{ 896, 0, 3600, 432000 }, // 13: V cloak of invisibility
|
|
{ 825, 1, 3600, 432000 }, // 14: V death mask (invulnerability)
|
|
{ 827, 0, 3600, 432000 }, // 15: V jump boots
|
|
{ 828, 0, 3600, 432000 }, // 16: X raven flight
|
|
{ 829, 0, 3600, 1728000 }, // 17: V guns akimbo
|
|
{ 830, 0, 3600, 432000 }, // 18: V diving suit
|
|
{ 831, 0, 3600, 432000 }, // 19: V gas mask
|
|
{ -1, 0, 3600, 432000 }, // 20: X clone
|
|
{ 2566, 0, 3600, 432000 }, // 21: V crystal ball
|
|
{ 836, 0, 3600, 432000 }, // 22: X decoy
|
|
{ 853, 0, 3600, 432000 }, // 23: V doppleganger
|
|
{ 2428, 0, 3600, 432000 }, // 24: V reflective shots
|
|
{ 839, 0, 3600, 432000 }, // 25: V beast vision
|
|
{ 768, 0, 3600, 432000 }, // 26: X cloak of shadow (useless)
|
|
{ 840, 0, 3600, 432000 }, // 27: X rage shroom
|
|
{ 841, 0, 900, 432000 }, // 28: V delirium shroom
|
|
{ 842, 0, 3600, 432000 }, // 29: V grow shroom (gModernMap only)
|
|
{ 843, 0, 3600, 432000 }, // 30: V shrink shroom (gModernMap only)
|
|
{ -1, 0, 3600, 432000 }, // 31: X death mask (useless)
|
|
{ -1, 0, 3600, 432000 }, // 32: X wine goblet
|
|
{ -1, 0, 3600, 432000 }, // 33: X wine bottle
|
|
{ -1, 0, 3600, 432000 }, // 34: X skull grail
|
|
{ -1, 0, 3600, 432000 }, // 35: X silver grail
|
|
{ -1, 0, 3600, 432000 }, // 36: X tome
|
|
{ -1, 0, 3600, 432000 }, // 37: X black chest
|
|
{ -1, 0, 3600, 432000 }, // 38: X wooden chest
|
|
{ 837, 1, 3600, 432000 }, // 39: V asbestos armor
|
|
{ -1, 0, 1, 432000 }, // 40: V basic armor
|
|
{ -1, 0, 1, 432000 }, // 41: V body armor
|
|
{ -1, 0, 1, 432000 }, // 42: V fire armor
|
|
{ -1, 0, 1, 432000 }, // 43: V spirit armor
|
|
{ -1, 0, 1, 432000 }, // 44: V super armor
|
|
{ 0, 0, 0, 0 }, // 45: ? unknown
|
|
{ 0, 0, 0, 0 }, // 46: ? unknown
|
|
{ 0, 0, 0, 0 }, // 47: ? unknown
|
|
{ 0, 0, 0, 0 }, // 48: ? unknown
|
|
{ 0, 0, 0, 0 }, // 49: X dummy
|
|
{ 833, 1, 1, 1 } // 50: V kModernItemLevelMap (gModernMap only)
|
|
};
|
|
|
|
int Handicap[] = {
|
|
144, 208, 256, 304, 368
|
|
};
|
|
|
|
POSTURE gPostureDefaults[kModeMax][kPostureMax] = {
|
|
|
|
// normal human
|
|
{
|
|
{ FixedToFloat<14>(0x4000), FixedToFloat<14>(0x4000), FixedToFloat<14>(0x4000), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 22, 18, 36, 12, -FixedToFloat(0xbaaaa), -FixedToFloat(0x175555) },
|
|
{ FixedToFloat<14>(0x1200), FixedToFloat<14>(0x1200), FixedToFloat<14>(0x1200), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 20, 16, 44, -6, FixedToFloat(0x5b05), 0 },
|
|
{ FixedToFloat<14>(0x2000), FixedToFloat<14>(0x2000), FixedToFloat<14>(0x2000), 22, 28, 0.09375, 0.0625, 0.0625, 0.15625, 8, 6, 44, -6, 0, 0 },
|
|
},
|
|
|
|
// normal beast
|
|
{
|
|
{ FixedToFloat<14>(0x4000), FixedToFloat<14>(0x4000), FixedToFloat<14>(0x4000), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 22, 18, 36, 12, -FixedToFloat(0xbaaaa), -FixedToFloat(0x175555) },
|
|
{ FixedToFloat<14>(0x1200), FixedToFloat<14>(0x1200), FixedToFloat<14>(0x1200), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 20, 16, 44, -6, FixedToFloat(0x5b05), 0 },
|
|
{ FixedToFloat<14>(0x2000), FixedToFloat<14>(0x2000), FixedToFloat<14>(0x2000), 22, 28, 0.09375, 0.0625, 0.0625, 0.15625, 8, 6, 44, -6, 0, 0 },
|
|
},
|
|
|
|
// shrink human
|
|
{
|
|
{ FixedToFloat<14>(10384), FixedToFloat<14>(10384), FixedToFloat<14>(10384), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 22, 18, 36, 12, -FixedToFloat(564586), -FixedToFloat(1329173) },
|
|
{ FixedToFloat<14>(2108), FixedToFloat<14>(2108), FixedToFloat<14>(2108), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 20, 16, 44, -6, FixedToFloat(0x5b05), 0 },
|
|
{ FixedToFloat<14>(2192), FixedToFloat<14>(2192), FixedToFloat<14>(2192), 22, 28, 0.09375, 0.0625, 0.0625, 0.15625, 8, 6, 44, -6, 0, 0 },
|
|
},
|
|
|
|
// grown human
|
|
{
|
|
{ FixedToFloat<14>(19384), FixedToFloat<14>(19384), FixedToFloat<14>(19384), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 22, 18, 36, 12, -FixedToFloat(1014586), -FixedToFloat(1779173) },
|
|
{ FixedToFloat<14>(5608), FixedToFloat<14>(5608), FixedToFloat<14>(5608), 14, 17, 0.09375, 0.0625, 0.125, 0.3125, 20, 16, 44, -6, FixedToFloat(0x5b05), 0 },
|
|
{ FixedToFloat<14>(11192), FixedToFloat<14>(11192), FixedToFloat<14>(11192), 22, 28, 0.09375, 0.0625, 0.0625, 0.15625, 8, 6, 44, -6, 0, 0 },
|
|
},
|
|
};
|
|
|
|
AMMOINFO gAmmoInfo[] = {
|
|
{ 0, -1 },
|
|
{ 100, -1 },
|
|
{ 100, 4 },
|
|
{ 500, 5 },
|
|
{ 100, -1 },
|
|
{ 50, -1 },
|
|
{ 2880, -1 },
|
|
{ 250, -1 },
|
|
{ 100, -1 },
|
|
{ 100, -1 },
|
|
{ 50, -1 },
|
|
{ 50, -1 },
|
|
};
|
|
|
|
struct ARMORDATA {
|
|
int armor0;
|
|
int armor0max;
|
|
int armor1;
|
|
int armor1max;
|
|
int armor2;
|
|
int armor2max;
|
|
};
|
|
ARMORDATA armorData[5] = {
|
|
{ 0x320, 0x640, 0x320, 0x640, 0x320, 0x640 },
|
|
{ 0x640, 0x640, 0, 0x640, 0, 0x640 },
|
|
{ 0, 0x640, 0x640, 0x640, 0, 0x640 },
|
|
{ 0, 0x640, 0, 0x640, 0x640, 0x640 },
|
|
{ 0xc80, 0xc80, 0xc80, 0xc80, 0xc80, 0xc80 }
|
|
};
|
|
|
|
|
|
|
|
struct VICTORY {
|
|
const char* message;
|
|
int Kills;
|
|
};
|
|
|
|
VICTORY gVictory[] = {
|
|
{ "%s boned %s like a fish", 4100 },
|
|
{ "%s castrated %s", 4101 },
|
|
{ "%s creamed %s", 4102 },
|
|
{ "%s destroyed %s", 4103 },
|
|
{ "%s diced %s", 4104 },
|
|
{ "%s disemboweled %s", 4105 },
|
|
{ "%s flattened %s", 4106 },
|
|
{ "%s gave %s Anal Justice", 4107 },
|
|
{ "%s gave AnAl MaDnEsS to %s", 4108 },
|
|
{ "%s hurt %s real bad", 4109 },
|
|
{ "%s killed %s", 4110 },
|
|
{ "%s made mincemeat out of %s", 4111 },
|
|
{ "%s massacred %s", 4112 },
|
|
{ "%s mutilated %s", 4113 },
|
|
{ "%s reamed %s", 4114 },
|
|
{ "%s ripped %s a new orifice", 4115 },
|
|
{ "%s slaughtered %s", 4116 },
|
|
{ "%s sliced %s", 4117 },
|
|
{ "%s smashed %s", 4118 },
|
|
{ "%s sodomized %s", 4119 },
|
|
{ "%s splattered %s", 4120 },
|
|
{ "%s squashed %s", 4121 },
|
|
{ "%s throttled %s", 4122 },
|
|
{ "%s wasted %s", 4123 },
|
|
{ "%s body bagged %s", 4124 },
|
|
};
|
|
|
|
struct SUICIDE {
|
|
const char* message;
|
|
int Kills;
|
|
};
|
|
|
|
SUICIDE gSuicide[] = {
|
|
{ "%s is excrement", 4202 },
|
|
{ "%s is hamburger", 4203 },
|
|
{ "%s suffered scrotum separation", 4204 },
|
|
{ "%s volunteered for population control", 4206 },
|
|
{ "%s has suicided", 4207 },
|
|
};
|
|
|
|
struct DAMAGEINFO {
|
|
int armorType;
|
|
int Kills[3];
|
|
int at10[3];
|
|
};
|
|
|
|
DAMAGEINFO damageInfo[7] = {
|
|
{ -1, 731, 732, 733, 710, 710, 710 },
|
|
{ 1, 742, 743, 744, 711, 711, 711 },
|
|
{ 0, 731, 732, 733, 712, 712, 712 },
|
|
{ 1, 731, 732, 733, 713, 713, 713 },
|
|
{ -1, 724, 724, 724, 714, 714, 714 },
|
|
{ 2, 731, 732, 733, 715, 715, 715 },
|
|
{ 0, 0, 0, 0, 0, 0, 0 }
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
inline bool IsTargetTeammate(PLAYER* pSourcePlayer, DBloodActor* target)
|
|
{
|
|
if (pSourcePlayer == nullptr)
|
|
return false;
|
|
if (!target->IsPlayerActor())
|
|
return false;
|
|
if (gGameOptions.nGameType == 1 || gGameOptions.nGameType == 3)
|
|
{
|
|
PLAYER* pTargetPlayer = &gPlayer[target->spr.type - kDudePlayer1];
|
|
if (pSourcePlayer != pTargetPlayer)
|
|
{
|
|
if (gGameOptions.nGameType == 1)
|
|
return true;
|
|
if (gGameOptions.nGameType == 3 && (pSourcePlayer->teamId & 3) == (pTargetPlayer->teamId & 3))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int powerupCheck(PLAYER* pPlayer, int nPowerUp)
|
|
{
|
|
assert(pPlayer != NULL);
|
|
assert(nPowerUp >= 0 && nPowerUp < kMaxPowerUps);
|
|
int nPack = powerupToPackItem(nPowerUp);
|
|
if (nPack >= 0 && !packItemActive(pPlayer, nPack))
|
|
return 0;
|
|
return pPlayer->pwUpTime[nPowerUp];
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool powerupActivate(PLAYER* pPlayer, int nPowerUp)
|
|
{
|
|
if (powerupCheck(pPlayer, nPowerUp) > 0 && gPowerUpInfo[nPowerUp].pickupOnce)
|
|
return 0;
|
|
if (!pPlayer->pwUpTime[nPowerUp])
|
|
pPlayer->pwUpTime[nPowerUp] = gPowerUpInfo[nPowerUp].bonusTime;
|
|
int nPack = powerupToPackItem(nPowerUp);
|
|
if (nPack >= 0)
|
|
pPlayer->packSlots[nPack].isActive = 1;
|
|
|
|
switch (nPowerUp + kItemBase) {
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kItemModernMapLevel:
|
|
if (gModernMap) gFullMap = true;
|
|
break;
|
|
case kItemShroomShrink:
|
|
if (!gModernMap) break;
|
|
else if (isGrown(pPlayer->actor)) playerDeactivateShrooms(pPlayer);
|
|
else playerSizeShrink(pPlayer, 2);
|
|
break;
|
|
case kItemShroomGrow:
|
|
if (!gModernMap) break;
|
|
else if (isShrinked(pPlayer->actor)) playerDeactivateShrooms(pPlayer);
|
|
else {
|
|
playerSizeGrow(pPlayer, 2);
|
|
if (powerupCheck(&gPlayer[pPlayer->actor->spr.type - kDudePlayer1], kPwUpShadowCloak) > 0) {
|
|
powerupDeactivate(pPlayer, kPwUpShadowCloak);
|
|
pPlayer->pwUpTime[kPwUpShadowCloak] = 0;
|
|
}
|
|
|
|
if (ceilIsTooLow(pPlayer->actor))
|
|
actDamageSprite(pPlayer->actor, pPlayer->actor, kDamageExplode, 65535);
|
|
}
|
|
break;
|
|
#endif
|
|
case kItemFeatherFall:
|
|
case kItemJumpBoots:
|
|
pPlayer->damageControl[0]++;
|
|
break;
|
|
case kItemReflectShots: // reflective shots
|
|
if (pPlayer->nPlayer == myconnectindex && gGameOptions.nGameType == 0)
|
|
sfxSetReverb2(1);
|
|
break;
|
|
case kItemDeathMask:
|
|
for (int i = 0; i < 7; i++)
|
|
pPlayer->damageControl[i]++;
|
|
break;
|
|
case kItemDivingSuit: // diving suit
|
|
pPlayer->damageControl[4]++;
|
|
if (pPlayer->nPlayer == myconnectindex && gGameOptions.nGameType == 0)
|
|
sfxSetReverb(1);
|
|
break;
|
|
case kItemGasMask:
|
|
pPlayer->damageControl[4]++;
|
|
break;
|
|
case kItemArmorAsbest:
|
|
pPlayer->damageControl[1]++;
|
|
break;
|
|
case kItemTwoGuns:
|
|
pPlayer->newWeapon = pPlayer->curWeapon;
|
|
WeaponRaise(pPlayer);
|
|
break;
|
|
}
|
|
sfxPlay3DSound(pPlayer->actor, 776, -1, 0);
|
|
return 1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void powerupDeactivate(PLAYER* pPlayer, int nPowerUp)
|
|
{
|
|
int nPack = powerupToPackItem(nPowerUp);
|
|
if (nPack >= 0)
|
|
pPlayer->packSlots[nPack].isActive = 0;
|
|
|
|
switch (nPowerUp + kItemBase) {
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kItemShroomShrink:
|
|
if (gModernMap) {
|
|
playerSizeReset(pPlayer);
|
|
if (ceilIsTooLow(pPlayer->actor))
|
|
actDamageSprite(pPlayer->actor, pPlayer->actor, kDamageExplode, 65535);
|
|
}
|
|
break;
|
|
case kItemShroomGrow:
|
|
if (gModernMap) playerSizeReset(pPlayer);
|
|
break;
|
|
#endif
|
|
case kItemFeatherFall:
|
|
case kItemJumpBoots:
|
|
pPlayer->damageControl[0]--;
|
|
break;
|
|
case kItemDeathMask:
|
|
for (int i = 0; i < 7; i++)
|
|
pPlayer->damageControl[i]--;
|
|
break;
|
|
case kItemDivingSuit:
|
|
pPlayer->damageControl[4]--;
|
|
if (pPlayer && pPlayer->nPlayer == myconnectindex && VanillaMode() ? true : pPlayer->pwUpTime[24] == 0)
|
|
sfxSetReverb(0);
|
|
break;
|
|
case kItemReflectShots:
|
|
if (pPlayer && pPlayer->nPlayer == myconnectindex && VanillaMode() ? true : pPlayer->packSlots[1].isActive == 0)
|
|
sfxSetReverb(0);
|
|
break;
|
|
case kItemGasMask:
|
|
pPlayer->damageControl[4]--;
|
|
break;
|
|
case kItemArmorAsbest:
|
|
pPlayer->damageControl[1]--;
|
|
break;
|
|
case kItemTwoGuns:
|
|
pPlayer->newWeapon = pPlayer->curWeapon;
|
|
WeaponRaise(pPlayer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void powerupSetState(PLAYER* pPlayer, int nPowerUp, bool bState)
|
|
{
|
|
if (!bState)
|
|
powerupActivate(pPlayer, nPowerUp);
|
|
else
|
|
powerupDeactivate(pPlayer, nPowerUp);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void powerupProcess(PLAYER* pPlayer)
|
|
{
|
|
pPlayer->packItemTime = ClipLow(pPlayer->packItemTime - 4, 0);
|
|
for (int i = kMaxPowerUps - 1; i >= 0; i--)
|
|
{
|
|
int nPack = powerupToPackItem(i);
|
|
if (nPack >= 0)
|
|
{
|
|
if (pPlayer->packSlots[nPack].isActive)
|
|
{
|
|
pPlayer->pwUpTime[i] = ClipLow(pPlayer->pwUpTime[i] - 4, 0);
|
|
if (pPlayer->pwUpTime[i])
|
|
pPlayer->packSlots[nPack].curAmount = (100 * pPlayer->pwUpTime[i]) / gPowerUpInfo[i].bonusTime;
|
|
else
|
|
{
|
|
powerupDeactivate(pPlayer, i);
|
|
if (pPlayer->packItemId == nPack)
|
|
pPlayer->packItemId = 0;
|
|
}
|
|
}
|
|
}
|
|
else if (pPlayer->pwUpTime[i] > 0)
|
|
{
|
|
pPlayer->pwUpTime[i] = ClipLow(pPlayer->pwUpTime[i] - 4, 0);
|
|
if (!pPlayer->pwUpTime[i])
|
|
powerupDeactivate(pPlayer, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void powerupClear(PLAYER* pPlayer)
|
|
{
|
|
for (int i = kMaxPowerUps - 1; i >= 0; i--)
|
|
{
|
|
pPlayer->pwUpTime[i] = 0;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int packItemToPowerup(int nPack)
|
|
{
|
|
int nPowerUp = -1;
|
|
switch (nPack) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
nPowerUp = kPwUpDivingSuit;
|
|
break;
|
|
case 2:
|
|
nPowerUp = kPwUpCrystalBall;
|
|
break;
|
|
case 3:
|
|
nPowerUp = kPwUpBeastVision;
|
|
break;
|
|
case 4:
|
|
nPowerUp = kPwUpJumpBoots;
|
|
break;
|
|
default:
|
|
I_Error("Unhandled pack item %d", nPack);
|
|
break;
|
|
}
|
|
return nPowerUp;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int powerupToPackItem(int nPowerUp)
|
|
{
|
|
switch (nPowerUp) {
|
|
case kPwUpDivingSuit:
|
|
return 1;
|
|
case kPwUpCrystalBall:
|
|
return 2;
|
|
case kPwUpBeastVision:
|
|
return 3;
|
|
case kPwUpJumpBoots:
|
|
return 4;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool packAddItem(PLAYER* pPlayer, unsigned int nPack)
|
|
{
|
|
if (nPack <= 4)
|
|
{
|
|
if (pPlayer->packSlots[nPack].curAmount >= 100)
|
|
return 0;
|
|
pPlayer->packSlots[nPack].curAmount = 100;
|
|
int nPowerUp = packItemToPowerup(nPack);
|
|
if (nPowerUp >= 0)
|
|
pPlayer->pwUpTime[nPowerUp] = gPowerUpInfo[nPowerUp].bonusTime;
|
|
if (pPlayer->packItemId == -1)
|
|
pPlayer->packItemId = nPack;
|
|
if (!pPlayer->packSlots[pPlayer->packItemId].curAmount)
|
|
pPlayer->packItemId = nPack;
|
|
}
|
|
else
|
|
I_Error("Unhandled pack item %d", nPack);
|
|
return 1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int packCheckItem(PLAYER* pPlayer, int nPack)
|
|
{
|
|
return pPlayer->packSlots[nPack].curAmount;
|
|
}
|
|
|
|
bool packItemActive(PLAYER* pPlayer, int nPack)
|
|
{
|
|
return pPlayer->packSlots[nPack].isActive;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void packUseItem(PLAYER* pPlayer, int nPack)
|
|
{
|
|
bool v4 = 0;
|
|
int nPowerUp = -1;
|
|
if (pPlayer->packSlots[nPack].curAmount > 0)
|
|
{
|
|
pPlayer->packItemId = nPack;
|
|
|
|
switch (nPack)
|
|
{
|
|
case 0:
|
|
{
|
|
unsigned int health = pPlayer->actor->xspr.health >> 4;
|
|
if (health < 100)
|
|
{
|
|
int heal = ClipHigh(100 - health, pPlayer->packSlots[0].curAmount);
|
|
actHealDude(pPlayer->actor, heal, 100);
|
|
pPlayer->packSlots[0].curAmount -= heal;
|
|
}
|
|
break;
|
|
}
|
|
case 1:
|
|
v4 = 1;
|
|
nPowerUp = kPwUpDivingSuit;
|
|
break;
|
|
case 2:
|
|
v4 = 1;
|
|
nPowerUp = kPwUpCrystalBall;
|
|
break;
|
|
case 3:
|
|
v4 = 1;
|
|
nPowerUp = kPwUpBeastVision;
|
|
break;
|
|
case 4:
|
|
v4 = 1;
|
|
nPowerUp = kPwUpJumpBoots;
|
|
break;
|
|
default:
|
|
I_Error("Unhandled pack item %d", nPack);
|
|
return;
|
|
}
|
|
}
|
|
pPlayer->packItemTime = 0;
|
|
if (v4)
|
|
powerupSetState(pPlayer, nPowerUp, pPlayer->packSlots[nPack].isActive);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void packPrevItem(PLAYER* pPlayer)
|
|
{
|
|
if (pPlayer->packItemTime > 0)
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int nPrev = pPlayer->packItemId - 1; nPrev >= 0; nPrev--)
|
|
{
|
|
if (pPlayer->packSlots[nPrev].curAmount)
|
|
{
|
|
pPlayer->packItemId = nPrev;
|
|
pPlayer->packItemTime = 600;
|
|
return;
|
|
}
|
|
}
|
|
pPlayer->packItemId = 4;
|
|
if (pPlayer->packSlots[4].curAmount) break;
|
|
}
|
|
}
|
|
|
|
pPlayer->packItemTime = 600;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void packNextItem(PLAYER* pPlayer)
|
|
{
|
|
if (pPlayer->packItemTime > 0)
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int nNext = pPlayer->packItemId + 1; nNext < 5; nNext++)
|
|
{
|
|
if (pPlayer->packSlots[nNext].curAmount)
|
|
{
|
|
pPlayer->packItemId = nNext;
|
|
pPlayer->packItemTime = 600;
|
|
return;
|
|
}
|
|
}
|
|
pPlayer->packItemId = 0;
|
|
if (pPlayer->packSlots[0].curAmount) break;
|
|
}
|
|
}
|
|
pPlayer->packItemTime = 600;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool playerSeqPlaying(PLAYER* pPlayer, int nSeq)
|
|
{
|
|
int nCurSeq = seqGetID(pPlayer->actor);
|
|
if (pPlayer->pDudeInfo->seqStartID + nSeq == nCurSeq && seqGetStatus(pPlayer->actor) >= 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void playerSetRace(PLAYER* pPlayer, int nLifeMode)
|
|
{
|
|
assert(nLifeMode >= kModeHuman && nLifeMode <= kModeHumanGrown);
|
|
DUDEINFO* pDudeInfo = pPlayer->pDudeInfo;
|
|
*pDudeInfo = gPlayerTemplate[nLifeMode];
|
|
pPlayer->lifeMode = nLifeMode;
|
|
|
|
// By NoOne: don't forget to change clipdist for grow and shrink modes
|
|
pPlayer->actor->set_native_clipdist(pDudeInfo->clipdist);
|
|
|
|
for (int i = 0; i < 7; i++)
|
|
pDudeInfo->damageVal[i] = MulScale(Handicap[gSkill], pDudeInfo->startDamage[i], 8);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void playerSetGodMode(PLAYER* pPlayer, bool bGodMode)
|
|
{
|
|
pPlayer->godMode = bGodMode;
|
|
}
|
|
|
|
void playerResetInertia(PLAYER* pPlayer)
|
|
{
|
|
POSTURE* pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture];
|
|
pPlayer->zView = pPlayer->actor->spr.pos.Z - pPosture->eyeAboveZ;
|
|
pPlayer->zWeapon = pPlayer->actor->spr.pos.Z - pPosture->weaponAboveZ;
|
|
viewBackupView(pPlayer->nPlayer);
|
|
}
|
|
|
|
void playerCorrectInertia(PLAYER* pPlayer, const DVector3& oldpos)
|
|
{
|
|
auto zAdj = pPlayer->actor->spr.pos.Z - oldpos.Z;
|
|
pPlayer->zView += zAdj;
|
|
pPlayer->zWeapon += zAdj;
|
|
pPlayer->actor->opos.XY() += pPlayer->actor->spr.pos.XY() - oldpos.XY();
|
|
pPlayer->ozView += zAdj;
|
|
}
|
|
|
|
void playerResetPowerUps(PLAYER* pPlayer)
|
|
{
|
|
for (int i = 0; i < kMaxPowerUps; i++) {
|
|
if (!VanillaMode() && (i == kPwUpJumpBoots || i == kPwUpDivingSuit || i == kPwUpCrystalBall || i == kPwUpBeastVision))
|
|
continue;
|
|
pPlayer->pwUpTime[i] = 0;
|
|
}
|
|
}
|
|
|
|
void playerResetPosture(PLAYER* pPlayer) {
|
|
memcpy(pPlayer->pPosture, gPostureDefaults, sizeof(gPostureDefaults));
|
|
if (!VanillaMode()) {
|
|
pPlayer->bobPhase = 0;
|
|
pPlayer->bobAmp = 0;
|
|
pPlayer->swayAmp = 0;
|
|
pPlayer->bobHeight = 0;
|
|
pPlayer->bobWidth = 0;
|
|
pPlayer->swayHeight = 0;
|
|
pPlayer->swayWidth = 0;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void playerStart(int nPlayer, int bNewLevel)
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[nPlayer];
|
|
InputPacket* pInput = &pPlayer->input;
|
|
ZONE* pStartZone = NULL;
|
|
|
|
// normal start position
|
|
if (gGameOptions.nGameType <= 1)
|
|
pStartZone = &gStartZone[nPlayer];
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// let's check if there is positions of teams is specified
|
|
// if no, pick position randomly, just like it works in vanilla.
|
|
else if (gModernMap && gGameOptions.nGameType == 3 && gTeamsSpawnUsed == true) {
|
|
int maxRetries = 5;
|
|
while (maxRetries-- > 0) {
|
|
if (pPlayer->teamId == 0) pStartZone = &gStartZoneTeam1[Random(3)];
|
|
else pStartZone = &gStartZoneTeam2[Random(3)];
|
|
|
|
if (maxRetries != 0) {
|
|
// check if there is no spawned player in selected zone
|
|
BloodSectIterator it(pStartZone->sector);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (pStartZone->pos.XY() == act->spr.pos.XY() && act->IsPlayerActor()) {
|
|
pStartZone = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pStartZone != NULL)
|
|
break;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
else {
|
|
pStartZone = &gStartZone[Random(8)];
|
|
}
|
|
|
|
auto actor = actSpawnSprite(pStartZone->sector, pStartZone->pos, 6, 1);
|
|
assert(actor->hasX());
|
|
pPlayer->actor = actor;
|
|
DUDEINFO* pDudeInfo = &dudeInfo[kDudePlayer1 + nPlayer - kDudeBase];
|
|
pPlayer->pDudeInfo = pDudeInfo;
|
|
playerSetRace(pPlayer, kModeHuman);
|
|
playerResetPosture(pPlayer);
|
|
seqSpawn(pDudeInfo->seqStartID, actor, -1);
|
|
if (nPlayer == myconnectindex)
|
|
actor->spr.cstat2 |= CSTAT2_SPRITE_MAPPED;
|
|
double top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
actor->spr.pos.Z -= bottom - actor->spr.pos.Z;
|
|
actor->spr.pal = 11 + (pPlayer->teamId & 3);
|
|
actor->spr.angle = pPlayer->angle.ang = pStartZone->angle;
|
|
actor->spr.type = kDudePlayer1 + nPlayer;
|
|
actor->set_native_clipdist(pDudeInfo->clipdist);
|
|
actor->spr.flags = 15;
|
|
actor->xspr.burnTime = 0;
|
|
actor->SetBurnSource(nullptr);
|
|
pPlayer->actor->xspr.health = pDudeInfo->startHealth << 4;
|
|
pPlayer->actor->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
|
|
pPlayer->bloodlust = 0;
|
|
pPlayer->horizon.horiz = pPlayer->horizon.horizoff = q16horiz(0);
|
|
pPlayer->slope = 0;
|
|
pPlayer->fragger = nullptr;
|
|
pPlayer->underwaterTime = 1200;
|
|
pPlayer->bubbleTime = 0;
|
|
pPlayer->restTime = 0;
|
|
pPlayer->kickPower = 0;
|
|
pPlayer->laughCount = 0;
|
|
pPlayer->angle.spin = nullAngle;
|
|
pPlayer->posture = 0;
|
|
pPlayer->voodooTarget = nullptr;
|
|
pPlayer->voodooTargets = 0;
|
|
pPlayer->voodooVar1 = 0;
|
|
pPlayer->vodooVar2 = 0;
|
|
playerResetInertia(pPlayer);
|
|
pPlayer->zWeaponVel = 0;
|
|
pPlayer->relAim.dx = 0x4000;
|
|
pPlayer->relAim.dy = 0;
|
|
pPlayer->relAim.dz = 0;
|
|
pPlayer->aimTarget = nullptr;
|
|
pPlayer->zViewVel = pPlayer->zWeaponVel;
|
|
if (!(gGameOptions.nGameType == 1 && gGameOptions.bKeepKeysOnRespawn && !bNewLevel))
|
|
for (int i = 0; i < 8; i++)
|
|
pPlayer->hasKey[i] = gGameOptions.nGameType >= 2;
|
|
pPlayer->hasFlag = 0;
|
|
for (int i = 0; i < 2; i++)
|
|
pPlayer->ctfFlagState[i] = nullptr;
|
|
for (int i = 0; i < 7; i++)
|
|
pPlayer->damageControl[i] = 0;
|
|
if (pPlayer->godMode)
|
|
playerSetGodMode(pPlayer, 1);
|
|
gInfiniteAmmo = 0;
|
|
gFullMap = 0;
|
|
pPlayer->throwPower = 0;
|
|
pPlayer->deathTime = 0;
|
|
pPlayer->nextWeapon = kWeapNone;
|
|
actor->ZeroVelocity();
|
|
pInput->avel = 0;
|
|
pInput->actions = 0;
|
|
pInput->fvel = 0;
|
|
pInput->svel = 0;
|
|
pInput->horz = 0;
|
|
pPlayer->flickerEffect = 0;
|
|
pPlayer->quakeEffect = 0;
|
|
pPlayer->tiltEffect = 0;
|
|
pPlayer->visibility = 0;
|
|
pPlayer->painEffect = 0;
|
|
pPlayer->blindEffect = 0;
|
|
pPlayer->chokeEffect = 0;
|
|
pPlayer->handTime = 0;
|
|
pPlayer->weaponTimer = 0;
|
|
pPlayer->weaponState = 0;
|
|
pPlayer->weaponQav = kQAVNone;
|
|
pPlayer->qavLastTick = 0;
|
|
pPlayer->qavTimer = 0;
|
|
#ifdef NOONE_EXTENSIONS
|
|
playerQavSceneReset(pPlayer); // reset qav scene
|
|
|
|
// assign or update player's sprite index for conditions
|
|
if (gModernMap) {
|
|
|
|
BloodStatIterator it(kStatModernPlayerLinker);
|
|
while (auto iactor = it.Next())
|
|
{
|
|
if (iactor->xspr.data1 == pPlayer->nPlayer + 1)
|
|
{
|
|
DBloodActor* SpriteOld = iactor->prevmarker;
|
|
trPlayerCtrlLink(iactor, pPlayer, (SpriteOld == nullptr)); // this modifies iactor's prevmarker field!
|
|
if (SpriteOld)
|
|
condUpdateObjectIndex(SpriteOld, iactor->prevmarker);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
pPlayer->hand = 0;
|
|
pPlayer->nWaterPal = 0;
|
|
playerResetPowerUps(pPlayer);
|
|
|
|
if (nPlayer == myconnectindex)
|
|
{
|
|
viewInitializePrediction();
|
|
}
|
|
if (IsUnderwaterSector(actor->sector()))
|
|
{
|
|
pPlayer->posture = 1;
|
|
pPlayer->actor->xspr.medium = kMediumWater;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void playerReset(PLAYER* pPlayer)
|
|
{
|
|
static int dword_136400[] = {
|
|
3, 4, 2, 8, 9, 10, 7, 1, 1, 1, 1, 1, 1, 1
|
|
};
|
|
static int dword_136438[] = {
|
|
3, 4, 2, 8, 9, 10, 7, 1, 1, 1, 1, 1, 1, 1
|
|
};
|
|
assert(pPlayer != NULL);
|
|
for (int i = 0; i < 14; i++)
|
|
{
|
|
pPlayer->hasWeapon[i] = gInfiniteAmmo;
|
|
pPlayer->weaponMode[i] = 0;
|
|
}
|
|
pPlayer->hasWeapon[kWeapPitchFork] = 1;
|
|
pPlayer->curWeapon = kWeapNone;
|
|
pPlayer->qavCallback = -1;
|
|
pPlayer->newWeapon = kWeapPitchFork;
|
|
for (int i = 0; i < 14; i++)
|
|
{
|
|
pPlayer->weaponOrder[0][i] = dword_136400[i];
|
|
pPlayer->weaponOrder[1][i] = dword_136438[i];
|
|
}
|
|
for (int i = 0; i < 12; i++)
|
|
{
|
|
if (gInfiniteAmmo)
|
|
pPlayer->ammoCount[i] = gAmmoInfo[i].max;
|
|
else
|
|
pPlayer->ammoCount[i] = 0;
|
|
}
|
|
for (int i = 0; i < 3; i++)
|
|
pPlayer->armor[i] = 0;
|
|
pPlayer->weaponTimer = 0;
|
|
pPlayer->weaponState = 0;
|
|
pPlayer->weaponQav = kQAVNone;
|
|
pPlayer->qavLoop = 0;
|
|
pPlayer->qavLastTick = 0;
|
|
pPlayer->qavTimer = 0;
|
|
pPlayer->packItemId = -1;
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
pPlayer->packSlots[i].isActive = 0;
|
|
pPlayer->packSlots[i].curAmount = 0;
|
|
}
|
|
#ifdef NOONE_EXTENSIONS
|
|
playerQavSceneReset(pPlayer);
|
|
#endif
|
|
// reset posture (mainly required for resetting movement speed and jump height)
|
|
playerResetPosture(pPlayer);
|
|
|
|
}
|
|
|
|
int team_score[8];
|
|
int team_ticker[8];
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void playerInit(int nPlayer, unsigned int a2)
|
|
{
|
|
if (!(a2 & 1))
|
|
gPlayer[nPlayer] = {};
|
|
|
|
PLAYER* pPlayer = &gPlayer[nPlayer];
|
|
pPlayer->nPlayer = nPlayer;
|
|
pPlayer->teamId = nPlayer;
|
|
if (gGameOptions.nGameType == 3)
|
|
pPlayer->teamId = nPlayer & 1;
|
|
pPlayer->fragCount = 0;
|
|
memset(team_score, 0, sizeof(team_score));
|
|
memset(team_ticker, 0, sizeof(team_ticker));
|
|
memset(pPlayer->fragInfo, 0, sizeof(pPlayer->fragInfo));
|
|
|
|
if (!(a2 & 1))
|
|
playerReset(pPlayer);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool findDroppedLeech(PLAYER* a1, DBloodActor* a2)
|
|
{
|
|
BloodStatIterator it(kStatThing);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (a2 == actor)
|
|
continue;
|
|
if (actor->spr.type == kThingDroppedLifeLeech && actor->GetOwner() == a1->actor)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool PickupItem(PLAYER* pPlayer, DBloodActor* itemactor)
|
|
{
|
|
char buffer[80];
|
|
int pickupSnd = 775;
|
|
int nType = itemactor->spr.type - kItemBase;
|
|
auto plActor = pPlayer->actor;
|
|
|
|
switch (itemactor->spr.type) {
|
|
case kItemShadowCloak:
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (isGrown(pPlayer->actor) || !powerupActivate(pPlayer, nType)) return false;
|
|
#else
|
|
if (!powerupActivate(pPlayer, nType)) return false;
|
|
#endif
|
|
break;
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kItemShroomShrink:
|
|
case kItemShroomGrow:
|
|
|
|
if (gModernMap) {
|
|
switch (itemactor->spr.type) {
|
|
case kItemShroomShrink:
|
|
if (isShrinked(pPlayer->actor)) return false;
|
|
break;
|
|
case kItemShroomGrow:
|
|
if (isGrown(pPlayer->actor)) return false;
|
|
break;
|
|
}
|
|
|
|
powerupActivate(pPlayer, nType);
|
|
}
|
|
|
|
break;
|
|
#endif
|
|
case kItemFlagABase:
|
|
case kItemFlagBBase: {
|
|
if (gGameOptions.nGameType != 3 || !itemactor->hasX()) return 0;
|
|
if (itemactor->spr.type == kItemFlagABase) {
|
|
if (pPlayer->teamId == 1) {
|
|
if ((pPlayer->hasFlag & 1) == 0 && itemactor->xspr.state) {
|
|
pPlayer->hasFlag |= 1;
|
|
pPlayer->ctfFlagState[0] = itemactor;
|
|
trTriggerSprite(itemactor, kCmdOff);
|
|
sprintf(buffer, "%s stole Blue Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8007, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
}
|
|
|
|
if (pPlayer->teamId == 0) {
|
|
|
|
if ((pPlayer->hasFlag & 1) != 0 && !itemactor->xspr.state) {
|
|
pPlayer->hasFlag &= ~1;
|
|
pPlayer->ctfFlagState[0] = nullptr;
|
|
trTriggerSprite(itemactor, kCmdOn);
|
|
sprintf(buffer, "%s returned Blue Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8003, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
|
|
if ((pPlayer->hasFlag & 2) != 0 && itemactor->xspr.state) {
|
|
pPlayer->hasFlag &= ~2;
|
|
pPlayer->ctfFlagState[1] = nullptr;
|
|
team_score[pPlayer->teamId] += 10;
|
|
team_ticker[pPlayer->teamId] += 240;
|
|
evSendGame(81, kCmdOn);
|
|
sprintf(buffer, "%s captured Red Flag!", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8001, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (itemactor->spr.type == kItemFlagBBase) {
|
|
|
|
if (pPlayer->teamId == 0) {
|
|
if ((pPlayer->hasFlag & 2) == 0 && itemactor->xspr.state) {
|
|
pPlayer->hasFlag |= 2;
|
|
pPlayer->ctfFlagState[1] = itemactor;
|
|
trTriggerSprite(itemactor, kCmdOff);
|
|
sprintf(buffer, "%s stole Red Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8006, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
}
|
|
|
|
if (pPlayer->teamId == 1) {
|
|
if ((pPlayer->hasFlag & 2) != 0 && !itemactor->xspr.state)
|
|
{
|
|
pPlayer->hasFlag &= ~2;
|
|
pPlayer->ctfFlagState[1] = nullptr;
|
|
trTriggerSprite(itemactor, kCmdOn);
|
|
sprintf(buffer, "%s returned Red Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8002, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
if ((pPlayer->hasFlag & 1) != 0 && itemactor->xspr.state)
|
|
{
|
|
pPlayer->hasFlag &= ~1;
|
|
pPlayer->ctfFlagState[0] = nullptr;
|
|
team_score[pPlayer->teamId] += 10;
|
|
team_ticker[pPlayer->teamId] += 240;
|
|
evSendGame(80, kCmdOn);
|
|
sprintf(buffer, "%s captured Blue Flag!", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8000, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
case kItemFlagA: {
|
|
if (gGameOptions.nGameType != 3) return 0;
|
|
gBlueFlagDropped = false;
|
|
const bool enemyTeam = (pPlayer->teamId & 1) == 1;
|
|
if (!enemyTeam && itemactor->GetOwner())
|
|
{
|
|
pPlayer->hasFlag &= ~1;
|
|
pPlayer->ctfFlagState[0] = nullptr;
|
|
trTriggerSprite(itemactor->GetOwner(), kCmdOn);
|
|
sprintf(buffer, "%s returned Blue Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8003, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
break;
|
|
}
|
|
pPlayer->hasFlag |= 1;
|
|
pPlayer->ctfFlagState[0] = itemactor->GetOwner();
|
|
if (enemyTeam)
|
|
{
|
|
sprintf(buffer, "%s stole Blue Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8007, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
break;
|
|
}
|
|
case kItemFlagB: {
|
|
if (gGameOptions.nGameType != 3) return 0;
|
|
gRedFlagDropped = false;
|
|
const bool enemyTeam = (pPlayer->teamId & 1) == 0;
|
|
if (!enemyTeam && itemactor->GetOwner())
|
|
{
|
|
pPlayer->hasFlag &= ~2;
|
|
pPlayer->ctfFlagState[1] = nullptr;
|
|
trTriggerSprite(itemactor->GetOwner(), kCmdOn);
|
|
sprintf(buffer, "%s returned Red Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8002, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
break;
|
|
}
|
|
pPlayer->hasFlag |= 2;
|
|
pPlayer->ctfFlagState[1] = itemactor->GetOwner();
|
|
if (enemyTeam)
|
|
{
|
|
sprintf(buffer, "%s stole Red Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8006, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
}
|
|
break;
|
|
}
|
|
case kItemArmorBasic:
|
|
case kItemArmorBody:
|
|
case kItemArmorFire:
|
|
case kItemArmorSpirit:
|
|
case kItemArmorSuper: {
|
|
ARMORDATA* pArmorData = &armorData[itemactor->spr.type - kItemArmorBasic]; bool pickedUp = false;
|
|
if (pPlayer->armor[1] < pArmorData->armor1max) {
|
|
pPlayer->armor[1] = ClipHigh(pPlayer->armor[1] + pArmorData->armor1, pArmorData->armor1max);
|
|
pickedUp = true;
|
|
}
|
|
|
|
if (pPlayer->armor[0] < pArmorData->armor0max) {
|
|
pPlayer->armor[0] = ClipHigh(pPlayer->armor[0] + pArmorData->armor0, pArmorData->armor0max);
|
|
pickedUp = true;
|
|
}
|
|
|
|
if (pPlayer->armor[2] < pArmorData->armor2max) {
|
|
pPlayer->armor[2] = ClipHigh(pPlayer->armor[2] + pArmorData->armor2, pArmorData->armor2max);
|
|
pickedUp = true;
|
|
}
|
|
|
|
if (!pickedUp) return 0;
|
|
pickupSnd = 779;
|
|
break;
|
|
}
|
|
case kItemCrystalBall:
|
|
if (gGameOptions.nGameType == 0 || !packAddItem(pPlayer, gItemData[nType].packSlot)) return 0;
|
|
break;
|
|
case kItemKeySkull:
|
|
case kItemKeyEye:
|
|
case kItemKeyFire:
|
|
case kItemKeyDagger:
|
|
case kItemKeySpider:
|
|
case kItemKeyMoon:
|
|
case kItemKeyKey7:
|
|
if (pPlayer->hasKey[itemactor->spr.type - 99]) return 0;
|
|
pPlayer->hasKey[itemactor->spr.type - 99] = 1;
|
|
pickupSnd = 781;
|
|
break;
|
|
case kItemHealthMedPouch:
|
|
case kItemHealthLifeEssense:
|
|
case kItemHealthLifeSeed:
|
|
case kItemHealthRedPotion: {
|
|
int addPower = gPowerUpInfo[nType].bonusTime;
|
|
#ifdef NOONE_EXTENSIONS
|
|
// allow custom amount for item
|
|
if (gModernMap && itemactor->hasX() && itemactor->xspr.data1 > 0)
|
|
addPower = itemactor->xspr.data1;
|
|
#endif
|
|
|
|
if (!actHealDude(pPlayer->actor, addPower, gPowerUpInfo[nType].maxTime)) return 0;
|
|
return 1;
|
|
}
|
|
case kItemHealthDoctorBag:
|
|
case kItemJumpBoots:
|
|
case kItemDivingSuit:
|
|
case kItemBeastVision:
|
|
if (!packAddItem(pPlayer, gItemData[nType].packSlot)) return 0;
|
|
break;
|
|
default:
|
|
if (!powerupActivate(pPlayer, nType)) return 0;
|
|
return 1;
|
|
}
|
|
|
|
sfxPlay3DSound(plActor->spr.pos, pickupSnd, plActor->sector());
|
|
return 1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool PickupAmmo(PLAYER* pPlayer, DBloodActor* ammoactor)
|
|
{
|
|
const AMMOITEMDATA* pAmmoItemData = &gAmmoItemData[ammoactor->spr.type - kItemAmmoBase];
|
|
int nAmmoType = pAmmoItemData->type;
|
|
|
|
if (pPlayer->ammoCount[nAmmoType] >= gAmmoInfo[nAmmoType].max) return 0;
|
|
#ifdef NOONE_EXTENSIONS
|
|
else if (gModernMap && ammoactor->hasX() && ammoactor->xspr.data1 > 0) // allow custom amount for item
|
|
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + ammoactor->xspr.data1, gAmmoInfo[nAmmoType].max);
|
|
#endif
|
|
else
|
|
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pAmmoItemData->count, gAmmoInfo[nAmmoType].max);
|
|
|
|
if (pAmmoItemData->weaponType) pPlayer->hasWeapon[pAmmoItemData->weaponType] = 1;
|
|
sfxPlay3DSound(pPlayer->actor, 782, -1, 0);
|
|
return 1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool PickupWeapon(PLAYER* pPlayer, DBloodActor* weaponactor)
|
|
{
|
|
const WEAPONITEMDATA* pWeaponItemData = &gWeaponItemData[weaponactor->spr.type - kItemWeaponBase];
|
|
int nWeaponType = pWeaponItemData->type;
|
|
int nAmmoType = pWeaponItemData->ammoType;
|
|
if (!pPlayer->hasWeapon[nWeaponType] || gGameOptions.nWeaponSettings == 2 || gGameOptions.nWeaponSettings == 3) {
|
|
if (weaponactor->spr.type == kItemWeaponLifeLeech && gGameOptions.nGameType > 1 && findDroppedLeech(pPlayer, NULL))
|
|
return 0;
|
|
pPlayer->hasWeapon[nWeaponType] = 1;
|
|
if (nAmmoType == -1) return 0;
|
|
// allow to set custom ammo count for weapon pickups
|
|
#ifdef NOONE_EXTENSIONS
|
|
else if (gModernMap && weaponactor->hasX() && weaponactor->xspr.data1 > 0)
|
|
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + weaponactor->xspr.data1, gAmmoInfo[nAmmoType].max);
|
|
#endif
|
|
else
|
|
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pWeaponItemData->count, gAmmoInfo[nAmmoType].max);
|
|
|
|
int nNewWeapon = WeaponUpgrade(pPlayer, nWeaponType);
|
|
if (nNewWeapon != pPlayer->curWeapon) {
|
|
pPlayer->weaponState = 0;
|
|
pPlayer->nextWeapon = nNewWeapon;
|
|
}
|
|
sfxPlay3DSound(pPlayer->actor, 777, -1, 0);
|
|
return 1;
|
|
}
|
|
|
|
if (!actGetRespawnTime(weaponactor) || nAmmoType == -1 || pPlayer->ammoCount[nAmmoType] >= gAmmoInfo[nAmmoType].max) return 0;
|
|
#ifdef NOONE_EXTENSIONS
|
|
else if (gModernMap && weaponactor->hasX() && weaponactor->xspr.data1 > 0)
|
|
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + weaponactor->xspr.data1, gAmmoInfo[nAmmoType].max);
|
|
#endif
|
|
else
|
|
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pWeaponItemData->count, gAmmoInfo[nAmmoType].max);
|
|
|
|
sfxPlay3DSound(pPlayer->actor, 777, -1, 0);
|
|
return 1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void PickUp(PLAYER* pPlayer, DBloodActor* actor)
|
|
{
|
|
const char* msg = nullptr;
|
|
int nType = actor->spr.type;
|
|
bool pickedUp = 0;
|
|
int customMsg = -1;
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (gModernMap && actor->hasX()) { // allow custom INI message instead "Picked up"
|
|
if (actor->xspr.txID != 3 && actor->xspr.lockMsg > 0)
|
|
customMsg = actor->xspr.lockMsg;
|
|
}
|
|
#endif
|
|
|
|
if (nType >= kItemBase && nType <= kItemMax) {
|
|
pickedUp = PickupItem(pPlayer, actor);
|
|
if (pickedUp && customMsg == -1) msg = GStrings(FStringf("TXTB_ITEM%02d", int(nType - kItemBase + 1)));
|
|
|
|
}
|
|
else if (nType >= kItemAmmoBase && nType < kItemAmmoMax) {
|
|
pickedUp = PickupAmmo(pPlayer, actor);
|
|
if (pickedUp && customMsg == -1) msg = GStrings(FStringf("TXTB_AMMO%02d", int(nType - kItemAmmoBase + 1)));
|
|
|
|
}
|
|
else if (nType >= kItemWeaponBase && nType < kItemWeaponMax) {
|
|
pickedUp = PickupWeapon(pPlayer, actor);
|
|
if (pickedUp && customMsg == -1) msg = GStrings(FStringf("TXTB_WPN%02d", int(nType - kItemWeaponBase + 1)));
|
|
}
|
|
|
|
if (!pickedUp) return;
|
|
else if (actor->hasX())
|
|
{
|
|
if (actor->xspr.Pickup)
|
|
trTriggerSprite(actor, kCmdSpritePickup);
|
|
}
|
|
|
|
if (!actCheckRespawn(actor))
|
|
actPostSprite(actor, kStatFree);
|
|
|
|
pPlayer->pickupEffect = 30;
|
|
if (pPlayer->nPlayer == myconnectindex) {
|
|
if (customMsg > 0) trTextOver(customMsg - 1);
|
|
else if (msg) viewSetMessage(msg, nullptr, MESSAGE_PRIORITY_PICKUP);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CheckPickUp(PLAYER* pPlayer)
|
|
{
|
|
auto plActor = pPlayer->actor;
|
|
auto ppos = plActor->spr.pos;
|
|
auto pSector = plActor->sector();
|
|
BloodStatIterator it(kStatItem);
|
|
while (auto itemactor = it.Next())
|
|
{
|
|
if (itemactor->spr.flags & 32)
|
|
continue;
|
|
double dx = abs(ppos.X - itemactor->spr.pos.X);
|
|
if (dx > 48)
|
|
continue;
|
|
double dy = abs(ppos.Y - itemactor->spr.pos.Y);
|
|
if (dy > 48)
|
|
continue;
|
|
double top, bottom;
|
|
GetActorExtents(plActor, &top, &bottom);
|
|
double vb = 0;
|
|
if (itemactor->spr.pos.Z < top)
|
|
vb = (top - itemactor->spr.pos.Z);
|
|
else if (itemactor->spr.pos.Z > bottom)
|
|
vb = (itemactor->spr.pos.Z - bottom);
|
|
if (vb > 32)
|
|
continue;
|
|
if (DVector2(dx, dy).LengthSquared() > 48*48)
|
|
continue;
|
|
GetActorExtents(itemactor, &top, &bottom);
|
|
if (cansee(ppos, pSector, itemactor->spr.pos, itemactor->sector())
|
|
|| cansee(ppos, pSector, DVector3(itemactor->spr.pos.XY(), top), itemactor->sector())
|
|
|| cansee(ppos, pSector, DVector3(itemactor->spr.pos.XY(), bottom), itemactor->sector()))
|
|
PickUp(pPlayer, itemactor);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int ActionScan(PLAYER* pPlayer, HitInfo* out)
|
|
{
|
|
auto plActor = pPlayer->actor;
|
|
*out = {};
|
|
auto pos = DVector3(plActor->spr.angle.ToVector() * 1024., pPlayer->slope * inttoworld);
|
|
int hit = HitScan(pPlayer->actor, pPlayer->zView, pos, 0x10000040, 128);
|
|
double hitDist = (plActor->spr.pos.XY() - gHitInfo.hitpos.XY()).Length();
|
|
if (hitDist < 64)
|
|
{
|
|
switch (hit)
|
|
{
|
|
case 3:
|
|
{
|
|
auto hitactor = gHitInfo.actor();
|
|
if (!hitactor || !hitactor->hasX()) return -1;
|
|
out->hitActor = hitactor;
|
|
if (hitactor->spr.statnum == kStatThing)
|
|
{
|
|
if (hitactor->spr.type == kThingDroppedLifeLeech)
|
|
{
|
|
if (gGameOptions.nGameType > 1 && findDroppedLeech(pPlayer, hitactor))
|
|
return -1;
|
|
hitactor->xspr.data4 = pPlayer->nPlayer;
|
|
hitactor->xspr.isTriggered = 0;
|
|
}
|
|
}
|
|
if (hitactor->xspr.Push)
|
|
return 3;
|
|
if (hitactor->spr.statnum == kStatDude)
|
|
{
|
|
int nMass = getDudeInfo(hitactor->spr.type)->mass;
|
|
if (nMass)
|
|
{
|
|
hitactor->spr.pos += pos * (FixedToFloat<8>(0xccccc) / nMass);
|
|
}
|
|
if (hitactor->xspr.Push && !hitactor->xspr.state && !hitactor->xspr.isTriggered)
|
|
trTriggerSprite(hitactor, kCmdSpritePush);
|
|
}
|
|
break;
|
|
}
|
|
case 0:
|
|
case 4:
|
|
{
|
|
auto pWall = gHitInfo.hitWall;
|
|
out->hitWall = gHitInfo.hitWall;
|
|
if (pWall->hasX() && pWall->xw().triggerPush)
|
|
return 0;
|
|
if (pWall->twoSided())
|
|
{
|
|
auto sect = pWall->nextSector();
|
|
out->hitWall = nullptr;
|
|
out->hitSector = sect;
|
|
if (sect->hasX() && sect->xs().Wallpush)
|
|
return 6;
|
|
}
|
|
break;
|
|
}
|
|
case 1:
|
|
case 2:
|
|
{
|
|
auto pSector = gHitInfo.hitSector;
|
|
out->hitSector = gHitInfo.hitSector;
|
|
if (pSector->hasX() && pSector->xs().Push)
|
|
return 6;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
out->hitSector = plActor->sector();
|
|
if (plActor->sector()->hasX() && plActor->sector()->xs().Push)
|
|
return 6;
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Player's sprite angle function, called in ProcessInput() or from gi->GetInput() as required.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void UpdatePlayerSpriteAngle(PLAYER* pPlayer)
|
|
{
|
|
pPlayer->actor->spr.angle = pPlayer->angle.ang;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Player's slope tilting wrapper function function, called in ProcessInput() or from gi->GetInput() as required.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void doslopetilting(PLAYER* pPlayer, double const scaleAdjust = 1)
|
|
{
|
|
auto plActor = pPlayer->actor;
|
|
int const florhit = pPlayer->actor->hit.florhit.type;
|
|
bool const va = plActor->xspr.height < 16 && (florhit == kHitSector || florhit == 0) ? 1 : 0;
|
|
pPlayer->horizon.calcviewpitch(plActor->spr.pos.XY(), plActor->spr.angle, va, plActor->sector()->floorstat & CSTAT_SECTOR_SLOPE, plActor->sector(), scaleAdjust);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void ProcessInput(PLAYER* pPlayer)
|
|
{
|
|
enum
|
|
{
|
|
Item_MedKit = 0,
|
|
Item_CrystalBall = 1,
|
|
Item_BeastVision = 2,
|
|
Item_JumpBoots = 3
|
|
};
|
|
|
|
pPlayer->horizon.resetadjustment();
|
|
pPlayer->angle.resetadjustment();
|
|
|
|
DBloodActor* actor = pPlayer->actor;
|
|
POSTURE* pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture];
|
|
InputPacket* pInput = &pPlayer->input;
|
|
|
|
// Originally, this was never able to be true due to sloppy input code in the original game.
|
|
// Allow it to become true behind a CVAR to offer an alternate playing experience if desired.
|
|
pPlayer->isRunning = !!(pInput->actions & SB_RUN) && !cl_bloodvanillarun;
|
|
|
|
if ((pInput->actions & SB_BUTTON_MASK) || pInput->fvel || pInput->svel || pInput->avel)
|
|
pPlayer->restTime = 0;
|
|
else if (pPlayer->restTime >= 0)
|
|
pPlayer->restTime += 4;
|
|
WeaponProcess(pPlayer);
|
|
if (actor->xspr.health == 0)
|
|
{
|
|
bool bSeqStat = playerSeqPlaying(pPlayer, 16);
|
|
DBloodActor* fragger = pPlayer->fragger;
|
|
if (fragger)
|
|
{
|
|
pPlayer->angle.addadjustment(deltaangle(pPlayer->angle.ang, VecToAngle(fragger->spr.pos.XY() - actor->spr.pos.XY())));
|
|
}
|
|
pPlayer->deathTime += 4;
|
|
if (!bSeqStat)
|
|
pPlayer->horizon.addadjustment(q16horiz(MulScale(0x8000 - (Cos(ClipHigh(pPlayer->deathTime << 3, 1024)) >> 15), gi->playerHorizMax(), 16) - pPlayer->horizon.horiz.asq16()));
|
|
if (pPlayer->curWeapon)
|
|
pInput->setNewWeapon(pPlayer->curWeapon);
|
|
if (pInput->actions & SB_OPEN)
|
|
{
|
|
if (bSeqStat)
|
|
{
|
|
if (pPlayer->deathTime > 360)
|
|
seqSpawn(pPlayer->pDudeInfo->seqStartID + 14, pPlayer->actor, nPlayerSurviveClient);
|
|
}
|
|
else if (seqGetStatus(pPlayer->actor) < 0)
|
|
{
|
|
if (pPlayer->actor)
|
|
pPlayer->actor->spr.type = kThingBloodChunks;
|
|
actPostSprite(pPlayer->actor, kStatThing);
|
|
seqSpawn(pPlayer->pDudeInfo->seqStartID + 15, pPlayer->actor, -1);
|
|
playerReset(pPlayer);
|
|
if (gGameOptions.nGameType == 0 && numplayers == 1)
|
|
{
|
|
gameaction = ga_autoloadgame;
|
|
}
|
|
else
|
|
playerStart(pPlayer->nPlayer);
|
|
}
|
|
pInput->actions &= ~SB_OPEN;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ((pInput->fvel || pInput->svel) && (pPlayer->posture == 1 || actor->xspr.height < 256))
|
|
{
|
|
const double speed = 1. - (actor->xspr.height < 256 ? actor->xspr.height * (1. / 256.) : 0);
|
|
const double& fvAccel = pInput->fvel > 0 ? pPosture->frontAccel : pPosture->backAccel;
|
|
const double& svAccel = pPosture->sideAccel;
|
|
actor->vel.XY() += DVector2(pInput->fvel * fvAccel, pInput->svel * svAccel).Rotated(actor->spr.angle) * speed;
|
|
}
|
|
|
|
if (SyncInput())
|
|
{
|
|
pPlayer->angle.applyinput(pInput->avel, &pInput->actions);
|
|
}
|
|
|
|
// unconditionally update the player's sprite angle
|
|
// in case game is forcing synchronised input.
|
|
UpdatePlayerSpriteAngle(pPlayer);
|
|
|
|
if (!(pInput->actions & SB_JUMP))
|
|
pPlayer->cantJump = 0;
|
|
|
|
switch (pPlayer->posture) {
|
|
case 1:
|
|
if (pInput->actions & SB_JUMP)
|
|
actor->vel.Z -= pPosture->normalJumpZ;//0x5b05;
|
|
if (pInput->actions & SB_CROUCH)
|
|
actor->vel.Z += pPosture->normalJumpZ;//0x5b05;
|
|
break;
|
|
case 2:
|
|
if (!(pInput->actions & SB_CROUCH))
|
|
pPlayer->posture = 0;
|
|
break;
|
|
default:
|
|
if (!pPlayer->cantJump && (pInput->actions & SB_JUMP) && actor->xspr.height == 0) {
|
|
#ifdef NOONE_EXTENSIONS
|
|
if ((packItemActive(pPlayer, 4) && pPosture->pwupJumpZ != 0) || pPosture->normalJumpZ != 0)
|
|
#endif
|
|
sfxPlay3DSound(actor, 700, 0, 0);
|
|
|
|
if (packItemActive(pPlayer, 4)) actor->vel.Z = pPosture->pwupJumpZ; //-0x175555;
|
|
else actor->vel.Z = pPosture->normalJumpZ; //-0xbaaaa;
|
|
pPlayer->cantJump = 1;
|
|
}
|
|
|
|
if (pInput->actions & SB_CROUCH)
|
|
pPlayer->posture = 2;
|
|
break;
|
|
}
|
|
if (pInput->actions & SB_OPEN)
|
|
{
|
|
HitInfo result;
|
|
|
|
int hit = ActionScan(pPlayer, &result);
|
|
switch (hit)
|
|
{
|
|
case 6:
|
|
{
|
|
auto pSector = result.hitSector;
|
|
auto pXSector = &pSector->xs();
|
|
int key = pXSector->Key;
|
|
if (pXSector->locked && pPlayer->nPlayer == myconnectindex)
|
|
{
|
|
viewSetMessage(GStrings("TXTB_LOCKED"));
|
|
auto snd = 3062;
|
|
if (sndCheckPlaying(snd))
|
|
sndStopSample(snd);
|
|
sndStartSample(snd, 255, 2, 0);
|
|
}
|
|
if (!key || pPlayer->hasKey[key])
|
|
trTriggerSector(pSector, kCmdSpritePush);
|
|
else if (pPlayer->nPlayer == myconnectindex)
|
|
{
|
|
viewSetMessage(GStrings("TXTB_KEY"));
|
|
auto snd = 3063;
|
|
if (sndCheckPlaying(snd))
|
|
sndStopSample(snd);
|
|
sndStartSample(snd, 255, 2, 0);
|
|
}
|
|
break;
|
|
}
|
|
case 0:
|
|
{
|
|
auto pWall = result.hitWall;
|
|
auto pXWall = &pWall->xw();
|
|
int key = pXWall->key;
|
|
if (pXWall->locked && pPlayer->nPlayer == myconnectindex)
|
|
{
|
|
viewSetMessage(GStrings("TXTB_LOCKED"));
|
|
auto snd = 3062;
|
|
if (sndCheckPlaying(snd))
|
|
sndStopSample(snd);
|
|
sndStartSample(snd, 255, 2, 0);
|
|
}
|
|
if (!key || pPlayer->hasKey[key])
|
|
trTriggerWall(pWall, kCmdWallPush);
|
|
else if (pPlayer->nPlayer == myconnectindex)
|
|
{
|
|
viewSetMessage(GStrings("TXTB_KEY"));
|
|
auto snd = 3063;
|
|
if (sndCheckPlaying(snd))
|
|
sndStopSample(snd);
|
|
sndStartSample(snd, 255, 2, 0);
|
|
}
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
auto act = result.actor();
|
|
int key = act->xspr.key;
|
|
if (actor->xspr.locked && pPlayer->nPlayer == myconnectindex && act->xspr.lockMsg)
|
|
trTextOver(act->xspr.lockMsg);
|
|
if (!key || pPlayer->hasKey[key])
|
|
trTriggerSprite(act, kCmdSpritePush);
|
|
else if (pPlayer->nPlayer == myconnectindex)
|
|
{
|
|
viewSetMessage(GStrings("TXTB_KEY"));
|
|
sndStartSample(3063, 255, 2, 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (pPlayer->handTime > 0)
|
|
pPlayer->handTime = ClipLow(pPlayer->handTime - 4 * (6 - gGameOptions.nDifficulty), 0);
|
|
if (pPlayer->handTime <= 0 && pPlayer->hand)
|
|
{
|
|
DBloodActor* pactor = pPlayer->actor;
|
|
auto spawned = actSpawnDude(pactor, kDudeHand, pPlayer->actor->fClipdist() * 0.5);
|
|
if (spawned)
|
|
{
|
|
spawned->spr.angle += DAngle180;
|
|
spawned->vel.XY() = pPlayer->actor->vel.XY() + (64. / 3.) * pPlayer->actor->spr.angle.ToVector();
|
|
spawned->vel.Z = pPlayer->actor->vel.Z;
|
|
}
|
|
pPlayer->hand = 0;
|
|
}
|
|
pInput->actions &= ~SB_OPEN;
|
|
}
|
|
|
|
if (SyncInput())
|
|
{
|
|
pPlayer->horizon.applyinput(pInput->horz, &pInput->actions);
|
|
doslopetilting(pPlayer);
|
|
}
|
|
|
|
pPlayer->angle.unlockinput();
|
|
pPlayer->horizon.unlockinput();
|
|
|
|
pPlayer->slope = -pPlayer->horizon.horiz.asq16() >> 9;
|
|
if (pInput->actions & SB_INVPREV)
|
|
{
|
|
pInput->actions &= ~SB_INVPREV;
|
|
packPrevItem(pPlayer);
|
|
}
|
|
if (pInput->actions & SB_INVNEXT)
|
|
{
|
|
pInput->actions &= ~SB_INVNEXT;
|
|
packNextItem(pPlayer);
|
|
}
|
|
if (pInput->actions & SB_INVUSE)
|
|
{
|
|
pInput->actions &= ~SB_INVUSE;
|
|
if (pPlayer->packSlots[pPlayer->packItemId].curAmount > 0)
|
|
packUseItem(pPlayer, pPlayer->packItemId);
|
|
}
|
|
if (pInput->isItemUsed(Item_BeastVision))
|
|
{
|
|
pInput->clearItemUsed(Item_BeastVision);
|
|
if (pPlayer->packSlots[3].curAmount > 0)
|
|
packUseItem(pPlayer, 3);
|
|
}
|
|
if (pInput->isItemUsed(Item_CrystalBall))
|
|
{
|
|
pInput->clearItemUsed(Item_CrystalBall);
|
|
if (pPlayer->packSlots[2].curAmount > 0)
|
|
packUseItem(pPlayer, 2);
|
|
}
|
|
if (pInput->isItemUsed(Item_JumpBoots))
|
|
{
|
|
pInput->clearItemUsed(Item_JumpBoots);
|
|
if (pPlayer->packSlots[4].curAmount > 0)
|
|
packUseItem(pPlayer, 4);
|
|
}
|
|
if (pInput->isItemUsed(Item_MedKit))
|
|
{
|
|
pInput->clearItemUsed(Item_MedKit);
|
|
if (pPlayer->packSlots[0].curAmount > 0)
|
|
packUseItem(pPlayer, 0);
|
|
}
|
|
if (pInput->actions & SB_HOLSTER)
|
|
{
|
|
pInput->actions &= ~SB_HOLSTER;
|
|
if (pPlayer->curWeapon)
|
|
{
|
|
WeaponLower(pPlayer);
|
|
viewSetMessage("Holstering weapon");
|
|
}
|
|
}
|
|
CheckPickUp(pPlayer);
|
|
}
|
|
|
|
void playerProcess(PLAYER* pPlayer)
|
|
{
|
|
DBloodActor* actor = pPlayer->actor;
|
|
POSTURE* pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture];
|
|
powerupProcess(pPlayer);
|
|
double top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
double dzflor = (bottom - actor->spr.pos.Z) / 4;
|
|
double dzceil = (actor->spr.pos.Z - top) / 4;
|
|
int dw = actor->int_clipdist();
|
|
if (!gNoClip)
|
|
{
|
|
auto pSector = actor->sector();
|
|
if (pushmove(actor->spr.pos, &pSector, dw, dzceil, dzflor, CLIPMASK0) == -1)
|
|
actDamageSprite(actor, actor, kDamageFall, 500 << 4);
|
|
if (actor->sector() != pSector)
|
|
{
|
|
if (pSector == nullptr)
|
|
{
|
|
pSector = actor->sector();
|
|
actDamageSprite(actor, actor, kDamageFall, 500 << 4);
|
|
}
|
|
else
|
|
ChangeActorSect(actor, pSector);
|
|
}
|
|
}
|
|
ProcessInput(pPlayer);
|
|
pPlayer->zViewVel = interpolatedvalue(pPlayer->zViewVel, actor->vel.Z, FixedToFloat(0x7000));
|
|
double dz = pPlayer->actor->spr.pos.Z - pPosture->eyeAboveZ - pPlayer->zView;
|
|
if (dz > 0)
|
|
pPlayer->zViewVel += dz * FixedToFloat(0xa000);
|
|
else
|
|
pPlayer->zViewVel += dz * FixedToFloat(0x1800);
|
|
pPlayer->zView += pPlayer->zViewVel;
|
|
pPlayer->zWeaponVel = interpolatedvalue(pPlayer->zWeaponVel, actor->vel.Z, FixedToFloat(0x5000));
|
|
dz = pPlayer->actor->spr.pos.Z - pPosture->weaponAboveZ - pPlayer->zWeapon;
|
|
if (dz > 0)
|
|
pPlayer->zWeaponVel += dz * FixedToFloat(0x8000);
|
|
else
|
|
pPlayer->zWeaponVel += dz * FixedToFloat(0xc00);
|
|
pPlayer->zWeapon += pPlayer->zWeaponVel;
|
|
pPlayer->bobPhase = ClipLow(pPlayer->bobPhase - 4, 0);
|
|
int nSpeed = int(actor->vel.XY().Length());
|
|
if (pPlayer->posture == 1)
|
|
{
|
|
pPlayer->bobAmp = (pPlayer->bobAmp + 17) & 2047;
|
|
pPlayer->swayAmp = (pPlayer->swayAmp + 17) & 2047;
|
|
pPlayer->bobHeight = pPosture->bobV * 10 * BobVal(pPlayer->bobAmp * 2);
|
|
pPlayer->bobWidth = pPosture->bobH * pPlayer->bobPhase * BobVal(pPlayer->bobAmp - 256);
|
|
pPlayer->swayHeight = pPosture->swayV * pPlayer->bobPhase * BobVal(pPlayer->swayAmp * 2);
|
|
pPlayer->swayWidth = pPosture->swayH * pPlayer->bobPhase * BobVal(pPlayer->swayAmp - 0x155);
|
|
}
|
|
else
|
|
{
|
|
if (actor->xspr.height < 256)
|
|
{
|
|
bool running = pPlayer->isRunning && !cl_bloodvanillabobbing;
|
|
pPlayer->bobAmp = (pPlayer->bobAmp + pPosture->pace[running] * 4) & 2047;
|
|
pPlayer->swayAmp = (pPlayer->swayAmp + (pPosture->pace[running] * 4) / 2) & 2047;
|
|
if (running)
|
|
{
|
|
if (pPlayer->bobPhase < 60)
|
|
pPlayer->bobPhase = ClipHigh(pPlayer->bobPhase + nSpeed, 60);
|
|
}
|
|
else
|
|
{
|
|
if (pPlayer->bobPhase < 30)
|
|
pPlayer->bobPhase = ClipHigh(pPlayer->bobPhase + nSpeed, 30);
|
|
}
|
|
}
|
|
pPlayer->bobHeight = pPosture->bobV * pPlayer->bobPhase * BobVal(pPlayer->bobAmp * 2);
|
|
pPlayer->bobWidth = pPosture->bobH * pPlayer->bobPhase * BobVal(pPlayer->bobAmp - 256);
|
|
pPlayer->swayHeight = pPosture->swayV * pPlayer->bobPhase * BobVal(pPlayer->swayAmp * 2);
|
|
pPlayer->swayWidth = pPosture->swayH * pPlayer->bobPhase * BobVal(pPlayer->swayAmp - 0x155);
|
|
}
|
|
pPlayer->flickerEffect = 0;
|
|
pPlayer->quakeEffect = ClipLow(pPlayer->quakeEffect - 4, 0);
|
|
pPlayer->tiltEffect = ClipLow(pPlayer->tiltEffect - 4, 0);
|
|
pPlayer->visibility = ClipLow(pPlayer->visibility - 4, 0);
|
|
pPlayer->painEffect = ClipLow(pPlayer->painEffect - 4, 0);
|
|
pPlayer->blindEffect = ClipLow(pPlayer->blindEffect - 4, 0);
|
|
pPlayer->pickupEffect = ClipLow(pPlayer->pickupEffect - 4, 0);
|
|
if (pPlayer->nPlayer == myconnectindex && pPlayer->actor->xspr.health == 0)
|
|
pPlayer->hand = 0;
|
|
if (!actor->xspr.health)
|
|
return;
|
|
pPlayer->isUnderwater = 0;
|
|
if (pPlayer->posture == 1)
|
|
{
|
|
pPlayer->isUnderwater = 1;
|
|
auto link = actor->sector()->lowerLink;
|
|
if (link && (link->spr.type == kMarkerLowGoo || link->spr.type == kMarkerLowWater))
|
|
{
|
|
if (getceilzofslopeptr(actor->sector(), actor->spr.pos) > pPlayer->zView)
|
|
pPlayer->isUnderwater = 0;
|
|
}
|
|
}
|
|
if (!pPlayer->isUnderwater)
|
|
{
|
|
pPlayer->underwaterTime = 1200;
|
|
pPlayer->chokeEffect = 0;
|
|
if (packItemActive(pPlayer, 1))
|
|
packUseItem(pPlayer, 1);
|
|
}
|
|
int nType = kDudePlayer1 - kDudeBase;
|
|
switch (pPlayer->posture)
|
|
{
|
|
case 1:
|
|
seqSpawn(dudeInfo[nType].seqStartID + 9, actor, -1);
|
|
break;
|
|
case 2:
|
|
seqSpawn(dudeInfo[nType].seqStartID + 10, actor, -1);
|
|
break;
|
|
default:
|
|
if (!nSpeed)
|
|
seqSpawn(dudeInfo[nType].seqStartID, actor, -1);
|
|
else
|
|
seqSpawn(dudeInfo[nType].seqStartID + 8, actor, -1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DBloodActor* playerFireMissile(PLAYER* pPlayer, int xyoff, int dx, int dy, int dz, int nType)
|
|
{
|
|
return actFireMissile(pPlayer->actor, xyoff, pPlayer->zWeapon * zworldtoint - pPlayer->actor->int_pos().Z, dx, dy, dz, nType);
|
|
}
|
|
|
|
DBloodActor* playerFireThing(PLAYER* pPlayer, int xyoff, int zvel, int thingType, int nSpeed)
|
|
{
|
|
assert(thingType >= kThingBase && thingType < kThingMax);
|
|
return actFireThing(pPlayer->actor, xyoff, pPlayer->zWeapon * zworldtoint - pPlayer->actor->int_pos().Z, pPlayer->slope + zvel, thingType, nSpeed);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void playerFrag(PLAYER* pKiller, PLAYER* pVictim)
|
|
{
|
|
assert(pKiller != NULL);
|
|
assert(pVictim != NULL);
|
|
|
|
char buffer[128] = "";
|
|
int nKiller = pKiller->actor->spr.type - kDudePlayer1;
|
|
assert(nKiller >= 0 && nKiller < kMaxPlayers);
|
|
int nVictim = pVictim->actor->spr.type - kDudePlayer1;
|
|
assert(nVictim >= 0 && nVictim < kMaxPlayers);
|
|
if (nKiller == nVictim)
|
|
{
|
|
pVictim->fragger = nullptr;
|
|
if (VanillaMode() || gGameOptions.nGameType != 1)
|
|
{
|
|
pVictim->fragCount--;
|
|
pVictim->fragInfo[nVictim]--;
|
|
}
|
|
if (gGameOptions.nGameType == 3)
|
|
team_score[pVictim->teamId]--;
|
|
int nMessage = Random(5);
|
|
int nSound = gSuicide[nMessage].Kills;
|
|
if (pVictim->nPlayer == myconnectindex && pVictim->handTime <= 0)
|
|
{
|
|
strcpy(buffer, GStrings("TXTB_KILLSELF"));
|
|
if (gGameOptions.nGameType > 0 && nSound >= 0)
|
|
sndStartSample(nSound, 255, 2, 0);
|
|
}
|
|
else
|
|
{
|
|
sprintf(buffer, gSuicide[nMessage].message, PlayerName(nVictim));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (VanillaMode() || gGameOptions.nGameType != 1)
|
|
{
|
|
pKiller->fragCount++;
|
|
pKiller->fragInfo[nKiller]++;
|
|
}
|
|
if (gGameOptions.nGameType == 3)
|
|
{
|
|
if (pKiller->teamId == pVictim->teamId)
|
|
team_score[pKiller->teamId]--;
|
|
else
|
|
{
|
|
team_score[pKiller->teamId]++;
|
|
team_ticker[pKiller->teamId] += 120;
|
|
}
|
|
}
|
|
int nMessage = Random(25);
|
|
int nSound = gVictory[nMessage].Kills;
|
|
const char* pzMessage = gVictory[nMessage].message;
|
|
sprintf(buffer, pzMessage, PlayerName(nKiller), PlayerName(nVictim));
|
|
if (gGameOptions.nGameType > 0 && nSound >= 0 && pKiller->nPlayer == myconnectindex)
|
|
sndStartSample(nSound, 255, 2, 0);
|
|
}
|
|
viewSetMessage(buffer);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void FragPlayer(PLAYER* pPlayer, DBloodActor* killer)
|
|
{
|
|
if (killer && killer->IsPlayerActor())
|
|
{
|
|
PLAYER* pKiller = &gPlayer[killer->spr.type - kDudePlayer1];
|
|
playerFrag(pKiller, pPlayer);
|
|
int nTeam1 = pKiller->teamId & 1;
|
|
int nTeam2 = pPlayer->teamId & 1;
|
|
if (nTeam1 == 0)
|
|
{
|
|
if (nTeam1 != nTeam2)
|
|
evSendGame(15, kCmdToggle);
|
|
else
|
|
evSendGame(16, kCmdToggle);
|
|
}
|
|
else
|
|
{
|
|
if (nTeam1 == nTeam2)
|
|
evSendGame(16, kCmdToggle);
|
|
else
|
|
evSendGame(15, kCmdToggle);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int playerDamageArmor(PLAYER* pPlayer, DAMAGE_TYPE nType, int nDamage)
|
|
{
|
|
DAMAGEINFO* pDamageInfo = &damageInfo[nType];
|
|
int nArmorType = pDamageInfo->armorType;
|
|
if (nArmorType >= 0 && pPlayer->armor[nArmorType])
|
|
{
|
|
#if 0
|
|
int vbp = (nDamage * 7) / 8 - nDamage / 4;
|
|
int v8 = pPlayer->at33e[nArmorType];
|
|
int t = nDamage / 4 + vbp * v8 / 3200;
|
|
v8 -= t;
|
|
#endif
|
|
int v8 = pPlayer->armor[nArmorType];
|
|
int t = scale(v8, 0, 3200, nDamage / 4, (nDamage * 7) / 8);
|
|
v8 -= t;
|
|
nDamage -= t;
|
|
pPlayer->armor[nArmorType] = ClipLow(v8, 0);
|
|
}
|
|
return nDamage;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void flagDropped(PLAYER* pPlayer, int a2)
|
|
{
|
|
DBloodActor* playeractor = pPlayer->actor;
|
|
DBloodActor* actor;
|
|
char buffer[80];
|
|
switch (a2)
|
|
{
|
|
case kItemFlagA:
|
|
pPlayer->hasFlag &= ~1;
|
|
actor = actDropObject(playeractor, kItemFlagA);
|
|
if (actor) actor->SetOwner(pPlayer->ctfFlagState[0]);
|
|
gBlueFlagDropped = true;
|
|
sprintf(buffer, "%s dropped Blue Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8005, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
break;
|
|
case kItemFlagB:
|
|
pPlayer->hasFlag &= ~2;
|
|
actor = actDropObject(playeractor, kItemFlagB);
|
|
if (actor) actor->SetOwner(pPlayer->ctfFlagState[1]);
|
|
gRedFlagDropped = true;
|
|
sprintf(buffer, "%s dropped Red Flag", PlayerName(pPlayer->nPlayer));
|
|
sndStartSample(8004, 255, 2, 0);
|
|
viewSetMessage(buffer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int playerDamageSprite(DBloodActor* source, PLAYER* pPlayer, DAMAGE_TYPE nDamageType, int nDamage)
|
|
{
|
|
assert(pPlayer != NULL);
|
|
if (pPlayer->damageControl[nDamageType] || pPlayer->godMode)
|
|
return 0;
|
|
nDamage = playerDamageArmor(pPlayer, nDamageType, nDamage);
|
|
pPlayer->painEffect = ClipHigh(pPlayer->painEffect + (nDamage >> 3), 600);
|
|
|
|
DBloodActor* pActor = pPlayer->actor;
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pActor->spr.type);
|
|
int nDeathSeqID = -1;
|
|
int nKneelingPlayer = -1;
|
|
bool va = playerSeqPlaying(pPlayer, 16);
|
|
if (!pActor->xspr.health)
|
|
{
|
|
if (va)
|
|
{
|
|
switch (nDamageType)
|
|
{
|
|
case kDamageSpirit:
|
|
nDeathSeqID = 18;
|
|
sfxPlay3DSound(pPlayer->actor, 716, 0, 0);
|
|
break;
|
|
case kDamageExplode:
|
|
GibSprite(pActor, GIBTYPE_7, NULL, NULL);
|
|
GibSprite(pActor, GIBTYPE_15, NULL, NULL);
|
|
pPlayer->actor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
nDeathSeqID = 17;
|
|
break;
|
|
default:
|
|
{
|
|
double top, bottom;
|
|
GetActorExtents(pActor, &top, &bottom);
|
|
DVector3 gibPos(pActor->spr.pos.XY(), top);
|
|
DVector3 gibVel(pActor->vel.XY() * 0.5, -FixedToFloat(0xccccc));
|
|
GibSprite(pActor, GIBTYPE_27, &gibPos, &gibVel);
|
|
GibSprite(pActor, GIBTYPE_7, NULL, NULL);
|
|
fxSpawnBlood(pActor, nDamage << 4);
|
|
fxSpawnBlood(pActor, nDamage << 4);
|
|
nDeathSeqID = 17;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int nHealth = pActor->xspr.health - nDamage;
|
|
pActor->xspr.health = ClipLow(nHealth, 0);
|
|
if (pActor->xspr.health > 0 && pActor->xspr.health < 16)
|
|
{
|
|
nDamageType = kDamageBullet;
|
|
pActor->xspr.health = 0;
|
|
nHealth = -25;
|
|
}
|
|
if (pActor->xspr.health > 0)
|
|
{
|
|
DAMAGEINFO* pDamageInfo = &damageInfo[nDamageType];
|
|
int nSound;
|
|
if (nDamage >= (10 << 4))
|
|
nSound = pDamageInfo->Kills[0];
|
|
else
|
|
nSound = pDamageInfo->Kills[Random(3)];
|
|
if (nDamageType == kDamageDrown && pActor->xspr.medium == kMediumWater && !pPlayer->hand)
|
|
nSound = 714;
|
|
sfxPlay3DSound(pPlayer->actor, nSound, 0, 6);
|
|
return nDamage;
|
|
}
|
|
sfxKill3DSound(pPlayer->actor, -1, 441);
|
|
if (gGameOptions.nGameType == 3 && pPlayer->hasFlag) {
|
|
if (pPlayer->hasFlag & 1) flagDropped(pPlayer, kItemFlagA);
|
|
if (pPlayer->hasFlag & 2) flagDropped(pPlayer, kItemFlagB);
|
|
}
|
|
pPlayer->deathTime = 0;
|
|
pPlayer->qavLoop = 0;
|
|
pPlayer->curWeapon = kWeapNone;
|
|
pPlayer->fragger = source;
|
|
pPlayer->voodooTargets = 0;
|
|
if (nDamageType == kDamageExplode && nDamage < (9 << 4))
|
|
nDamageType = kDamageFall;
|
|
switch (nDamageType)
|
|
{
|
|
case kDamageExplode:
|
|
sfxPlay3DSound(pPlayer->actor, 717, 0, 0);
|
|
GibSprite(pActor, GIBTYPE_7, NULL, NULL);
|
|
GibSprite(pActor, GIBTYPE_15, NULL, NULL);
|
|
pPlayer->actor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
nDeathSeqID = 2;
|
|
break;
|
|
case kDamageBurn:
|
|
sfxPlay3DSound(pPlayer->actor, 718, 0, 0);
|
|
nDeathSeqID = 3;
|
|
break;
|
|
case kDamageDrown:
|
|
nDeathSeqID = 1;
|
|
break;
|
|
default:
|
|
if (nHealth < -20 && gGameOptions.nGameType >= 2 && Chance(0x4000))
|
|
{
|
|
DAMAGEINFO* pDamageInfo = &damageInfo[nDamageType];
|
|
sfxPlay3DSound(pPlayer->actor, pDamageInfo->at10[0], 0, 2);
|
|
nDeathSeqID = 16;
|
|
nKneelingPlayer = nPlayerKneelClient;
|
|
powerupActivate(pPlayer, kPwUpDeliriumShroom);
|
|
pActor->SetTarget(source);
|
|
evPostActor(pPlayer->actor, 15, kCallbackFinishHim);
|
|
}
|
|
else
|
|
{
|
|
sfxPlay3DSound(pPlayer->actor, 716, 0, 0);
|
|
nDeathSeqID = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (nDeathSeqID < 0)
|
|
return nDamage;
|
|
if (nDeathSeqID != 16)
|
|
{
|
|
powerupClear(pPlayer);
|
|
if (pActor->sector()->hasX() && pActor->sector()->xs().Exit)
|
|
trTriggerSector(pActor->sector(), kCmdSectorExit);
|
|
pActor->spr.flags |= 7;
|
|
for (int p = connecthead; p >= 0; p = connectpoint2[p])
|
|
{
|
|
if (gPlayer[p].fragger == pPlayer->actor && gPlayer[p].deathTime > 0)
|
|
gPlayer[p].fragger = nullptr;
|
|
}
|
|
FragPlayer(pPlayer, source);
|
|
trTriggerSprite(pActor, kCmdOff);
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// allow drop items and keys in multiplayer
|
|
if (gModernMap && gGameOptions.nGameType != 0 && pPlayer->actor->xspr.health <= 0) {
|
|
|
|
DBloodActor* pItem = nullptr;
|
|
if (pPlayer->actor->xspr.dropMsg && (pItem = actDropItem(pActor, pPlayer->actor->xspr.dropMsg)) != NULL)
|
|
evPostActor(pItem, 500, kCallbackRemove);
|
|
|
|
if (pPlayer->actor->xspr.key) {
|
|
|
|
int i; // if all players have this key, don't drop it
|
|
for (i = connecthead; i >= 0; i = connectpoint2[i]) {
|
|
if (!gPlayer[i].hasKey[pPlayer->actor->xspr.key])
|
|
break;
|
|
}
|
|
|
|
if (i == 0 && (pItem = actDropKey(pActor, (pPlayer->actor->xspr.key + kItemKeyBase) - 1)) != NULL)
|
|
evPostActor(pItem, 500, kCallbackRemove);
|
|
|
|
}
|
|
|
|
|
|
}
|
|
#endif
|
|
|
|
}
|
|
assert(getSequence(pDudeInfo->seqStartID + nDeathSeqID) != NULL);
|
|
seqSpawn(pDudeInfo->seqStartID + nDeathSeqID, pPlayer->actor, nKneelingPlayer);
|
|
return nDamage;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int UseAmmo(PLAYER* pPlayer, int nAmmoType, int nDec)
|
|
{
|
|
if (gInfiniteAmmo)
|
|
return 9999;
|
|
if (nAmmoType == -1)
|
|
return 9999;
|
|
pPlayer->ammoCount[nAmmoType] = ClipLow(pPlayer->ammoCount[nAmmoType] - nDec, 0);
|
|
return pPlayer->ammoCount[nAmmoType];
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void voodooTarget(PLAYER* pPlayer)
|
|
{
|
|
DBloodActor* actor = pPlayer->actor;
|
|
int v4 = pPlayer->aim.dz;
|
|
double dz = pPlayer->zWeapon - pPlayer->actor->spr.pos.Z;
|
|
if (UseAmmo(pPlayer, 9, 0) < 8)
|
|
{
|
|
pPlayer->voodooTargets = 0;
|
|
return;
|
|
}
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
int ang1 = (pPlayer->voodooVar1 + pPlayer->vodooVar2) & 2047;
|
|
actFireVectorf(actor, 0, dz, bcos(ang1), bsin(ang1), v4, kVectorVoodoo10);
|
|
int ang2 = (pPlayer->voodooVar1 + 2048 - pPlayer->vodooVar2) & 2047;
|
|
actFireVectorf(actor, 0, dz, bcos(ang2), bsin(ang2), v4, kVectorVoodoo10);
|
|
}
|
|
pPlayer->voodooTargets = ClipLow(pPlayer->voodooTargets - 1, 0);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void playerLandingSound(PLAYER* pPlayer)
|
|
{
|
|
static int surfaceSound[] = {
|
|
-1,
|
|
600,
|
|
601,
|
|
602,
|
|
603,
|
|
604,
|
|
605,
|
|
605,
|
|
605,
|
|
600,
|
|
605,
|
|
605,
|
|
605,
|
|
604,
|
|
603
|
|
};
|
|
SPRITEHIT* pHit = &pPlayer->actor->hit;
|
|
if (pHit->florhit.type != kHitNone)
|
|
{
|
|
if (!gGameOptions.bFriendlyFire && pHit->florhit.type == kHitSprite && IsTargetTeammate(pPlayer, pHit->florhit.actor()))
|
|
return;
|
|
int nSurf = tileGetSurfType(pHit->florhit);
|
|
if (nSurf)
|
|
sfxPlay3DSound(pPlayer->actor, surfaceSound[nSurf], -1, 0);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void PlayerSurvive(int, DBloodActor* actor)
|
|
{
|
|
char buffer[80];
|
|
actHealDude(actor, 1, 2);
|
|
if (gGameOptions.nGameType > 0 && numplayers > 1)
|
|
{
|
|
sfxPlay3DSound(actor, 3009, 0, 6);
|
|
if (actor->IsPlayerActor())
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[actor->spr.type - kDudePlayer1];
|
|
if (pPlayer->nPlayer == myconnectindex)
|
|
viewSetMessage(GStrings("TXT_LIVEAGAIM"));
|
|
else
|
|
{
|
|
sprintf(buffer, "%s lives again!", PlayerName(pPlayer->nPlayer));
|
|
viewSetMessage(buffer);
|
|
}
|
|
pPlayer->newWeapon = kWeapPitchFork;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void PlayerKneelsOver(int, DBloodActor* actor)
|
|
{
|
|
for (int p = connecthead; p >= 0; p = connectpoint2[p])
|
|
{
|
|
if (gPlayer[p].actor == actor)
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[p];
|
|
playerDamageSprite(pPlayer->fragger, pPlayer, kDamageSpirit, 500 << 4);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, Aim& w, Aim* def)
|
|
{
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("x", w.dx)
|
|
("y", w.dx)
|
|
("z", w.dx)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, PACKINFO& w, PACKINFO* def)
|
|
{
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("isactive", w.isActive)
|
|
("curamount", w.curAmount)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, POSTURE& w, POSTURE* def)
|
|
{
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("frontaccel", w.frontAccel, def->frontAccel)
|
|
("sideaccel", w.sideAccel, def->sideAccel)
|
|
("backaccel", w.backAccel, def->backAccel)
|
|
("pace0", w.pace[0], def->pace[0])
|
|
("pace1", w.pace[1], def->pace[1])
|
|
("bobv", w.bobV, def->bobV)
|
|
("bobh", w.bobH, def->bobH)
|
|
("swayv", w.swayV, def->swayV)
|
|
("swayh", w.swayH, def->swayH)
|
|
("eyeabovez", w.eyeAboveZ, def->eyeAboveZ)
|
|
("weaponabovez", w.weaponAboveZ, def->weaponAboveZ)
|
|
("xoffset", w.xOffset, def->xOffset)
|
|
("zoffset", w.zOffset, def->zOffset)
|
|
("normaljumpz", w.normalJumpZ, def->normalJumpZ)
|
|
("pwupjumpz", w.pwupJumpZ, def->pwupJumpZ)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, PLAYER& w, PLAYER* def)
|
|
{
|
|
if (arc.isReading()) playerResetPosture(&w);
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("spritenum", w.actor)
|
|
("horizon", w.horizon)
|
|
("angle", w.angle)
|
|
("newweapon", w.newWeapon)
|
|
("used1", w.used1)
|
|
("weaponqav", w.weaponQav)
|
|
("qavcallback", w.qavCallback)
|
|
("isrunning", w.isRunning)
|
|
("posture", w.posture)
|
|
("sceneqav", w.sceneQav)
|
|
("bobphase", w.bobPhase)
|
|
("bobamp", w.bobAmp)
|
|
("bobheight", w.bobHeight)
|
|
("bobwidth", w.bobWidth)
|
|
("swayphase", w.swayPhase)
|
|
("swayamp", w.swayAmp)
|
|
("swayheight", w.swayHeight)
|
|
("swaywidth", w.swayWidth)
|
|
("nplayer", w.nPlayer)
|
|
("lifemode", w.lifeMode)
|
|
("bloodlust", w.bloodlust)
|
|
("zview", w.zView)
|
|
("zviewvel", w.zViewVel)
|
|
("zweapon", w.zWeapon)
|
|
("zweaponvel", w.zWeaponVel)
|
|
("slope", w.slope)
|
|
("underwater", w.isUnderwater)
|
|
.Array("haskey", w.hasKey, 8)
|
|
("hasflag", w.hasFlag)
|
|
.Array("ctfflagstate", w.ctfFlagState, 2)
|
|
.Array("dmgcontrol", w.damageControl, 7)
|
|
("curweapon", w.curWeapon)
|
|
("nextweapon", w.nextWeapon)
|
|
("weapontimer", w.weaponTimer)
|
|
("weaponstate", w.weaponState)
|
|
("weaponammo", w.weaponAmmo)
|
|
.Array("hasweapon", w.hasWeapon, countof(w.hasWeapon))
|
|
.Array("weaponmode", w.weaponMode, countof(w.weaponMode))
|
|
.Array("weaponorder", &w.weaponOrder[0][kWeapNone], +kWeapMax * 2)
|
|
.Array("ammocount", w.ammoCount, countof(w.ammoCount))
|
|
("qavloop", w.qavLoop)
|
|
("qavlastTick", w.qavLastTick)
|
|
("qavtimer", w.qavTimer)
|
|
("fusetime", w.fuseTime)
|
|
("throwtime", w.throwTime)
|
|
("throwpower", w.throwPower)
|
|
("aim", w.aim)
|
|
("relaim", w.relAim)
|
|
("aimtarget", w.aimTarget)
|
|
("aimtargetscount", w.aimTargetsCount)
|
|
.Array("aimtargets", w.aimTargets, countof(w.aimTargets))
|
|
("deathtime", w.deathTime)
|
|
.Array("pwuptime", w.pwUpTime, countof(w.pwUpTime))
|
|
("fragcount", w.fragCount)
|
|
.Array("fraginfo", w.fragInfo, countof(w.fragInfo))
|
|
("teamid", w.teamId)
|
|
("fraggerid", w.fragger)
|
|
("undserwatertime", w.underwaterTime)
|
|
("bubbletime", w.bubbleTime)
|
|
("resttime", w.restTime)
|
|
("kickpower", w.kickPower)
|
|
("laughcount", w.laughCount)
|
|
("godmode", w.godMode)
|
|
("fallscream", w.fallScream)
|
|
("cantjump", w.cantJump)
|
|
("packitemtime", w.packItemTime)
|
|
("packitemid", w.packItemId)
|
|
.Array("packslots", w.packSlots, countof(w.packSlots))
|
|
.Array("armor", w.armor, countof(w.armor))
|
|
("voodootarget", w.voodooTarget)
|
|
("voodootargets", w.voodooTargets)
|
|
("voodoovar1", w.voodooVar1)
|
|
("voodoovar2", w.vodooVar2)
|
|
("flickereffect", w.flickerEffect)
|
|
("tilteffect", w.tiltEffect)
|
|
("visibility", w.visibility)
|
|
("paineffect", w.painEffect)
|
|
("blindeffect", w.blindEffect)
|
|
("chokeeffect", w.chokeEffect)
|
|
("handtime", w.handTime)
|
|
("hand", w.hand)
|
|
("pickupeffect", w.pickupEffect)
|
|
("flasheffect", w.flashEffect)
|
|
("quakeeffect", w.quakeEffect)
|
|
("player_par", w.player_par)
|
|
("waterpal", w.nWaterPal)
|
|
.Array("posturedata", &w.pPosture[0][0], &gPostureDefaults[0][0], kModeMax * kPostureMax) // only save actual changes in this.
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, TRPLAYERCTRL& w, TRPLAYERCTRL* def)
|
|
{
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("index", w.qavScene.initiator)
|
|
("dummy", w.qavScene.dummy)
|
|
.EndObject();
|
|
}
|
|
if (arc.isReading()) w.qavScene.qavResrc = nullptr;
|
|
return arc;
|
|
}
|
|
#endif
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void SerializePlayers(FSerializer& arc)
|
|
{
|
|
if (arc.BeginObject("players"))
|
|
{
|
|
arc("numplayers", gNetPlayers)
|
|
.Array("teamscore", team_score, gNetPlayers)
|
|
.Array("players", gPlayer, gNetPlayers)
|
|
#ifdef NOONE_EXTENSIONS
|
|
.Array("playerctrl", gPlayerCtrl, gNetPlayers)
|
|
#endif
|
|
.EndObject();
|
|
}
|
|
|
|
if (arc.isReading())
|
|
{
|
|
for (int i = 0; i < gNetPlayers; i++)
|
|
{
|
|
gPlayer[i].pDudeInfo = &dudeInfo[gPlayer[i].actor->spr.type - kDudeBase];
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// load qav scene
|
|
if (gPlayer[i].sceneQav != -1)
|
|
{
|
|
QAV* pQav = playerQavSceneLoad(gPlayer[i].sceneQav);
|
|
if (pQav)
|
|
{
|
|
gPlayerCtrl[i].qavScene.qavResrc = pQav;
|
|
//gPlayerCtrl[i].qavScene.qavResrc->Preload();
|
|
}
|
|
else
|
|
{
|
|
gPlayer[i].sceneQav = -1;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, newWeapon)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, weaponQav)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, qavCallback)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, isRunning)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, posture) // stand, crouch, swim
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, sceneQav) // by NoOne: used to keep qav id
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, bobPhase)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, bobAmp)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, bobHeight)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, bobWidth)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, swayPhase)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, swayAmp)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, swayHeight)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, swayWidth)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, nPlayer) // Connect id
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, lifeMode)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, zView)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, zViewVel)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, zWeapon)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, zWeaponVel)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, slope)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, isUnderwater)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, hasKey)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, hasFlag)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, damageControl)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, curWeapon)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, nextWeapon)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, weaponTimer)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, weaponState)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, weaponAmmo) //rename
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, hasWeapon)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, weaponMode)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, weaponOrder)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, ammoCount)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, qavLoop)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, fuseTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, throwTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, throwPower)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, aim) // world
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, aimTargetsCount)
|
|
//DEFINE_FIELD_X(BloodPlayer, PLAYER, aimTargets)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, deathTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, pwUpTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, teamId)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, fragCount)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, fragInfo)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, underwaterTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, bubbleTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, restTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, kickPower)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, laughCount)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, godMode)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, fallScream)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, cantJump)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, packItemTime) // pack timer
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, packItemId) // pack id 1: diving suit, 2: crystal ball, 3:
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, packSlots) // at325 1]: diving suit, [2]: crystal ball,
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, armor) // armor
|
|
//DEFINE_FIELD_X(BloodPlayer, PLAYER, voodooTarget)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, flickerEffect)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, tiltEffect)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, visibility)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, painEffect)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, blindEffect)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, chokeEffect)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, handTime)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, hand) // if true, there is hand start choking the player
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, pickupEffect)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, flashEffect) // if true, reduce pPlayer->visibility counter
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, quakeEffect)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, player_par)
|
|
DEFINE_FIELD_X(BloodPlayer, PLAYER, nWaterPal)
|
|
|
|
END_BLD_NS
|