raze/source/games/blood/src/player.cpp

2486 lines
85 KiB
C++
Raw Normal View History

2019-09-19 22:42:45 +00:00
//-------------------------------------------------------------------------
/*
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!
2019-09-19 22:42:45 +00:00
#include <stdlib.h>
#include <string.h>
#include "automap.h"
2019-09-19 22:42:45 +00:00
#include "compat.h"
#include "build.h"
2019-09-19 22:42:45 +00:00
#include "blood.h"
#include "gstrings.h"
#include "gamestate.h"
2020-09-06 10:44:58 +00:00
#include "automap.h"
2019-09-19 22:42:45 +00:00
BEGIN_BLD_NS
2019-09-19 22:42:45 +00:00
PLAYER gPlayer[kMaxPlayers];
PLAYER *gMe, *gView;
bool gBlueFlagDropped = false;
bool gRedFlagDropped = false;
// V = has effect in game, X = no effect in game
2019-09-19 22:42:45 +00:00
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)
2019-09-19 22:42:45 +00:00
};
int Handicap[] = {
144, 208, 256, 304, 368
};
POSTURE gPostureDefaults[kModeMax][kPostureMax] = {
2019-09-19 22:42:45 +00:00
// normal human
{
{ 0x4000, 0x4000, 0x4000, 14, 17, 24, 16, 32, 80, 0x1600, 0x1200, 0xc00, 0x90, -0xbaaaa, -0x175555 },
{ 0x1200, 0x1200, 0x1200, 14, 17, 24, 16, 32, 80, 0x1400, 0x1000, -0x600, 0xb0, 0x5b05, 0 },
{ 0x2000, 0x2000, 0x2000, 22, 28, 24, 16, 16, 40, 0x800, 0x600, -0x600, 0xb0, 0, 0 },
2019-09-19 22:42:45 +00:00
},
// normal beast
{
{ 0x4000, 0x4000, 0x4000, 14, 17, 24, 16, 32, 80, 0x1600, 0x1200, 0xc00, 0x90, -0xbaaaa, -0x175555 },
{ 0x1200, 0x1200, 0x1200, 14, 17, 24, 16, 32, 80, 0x1400, 0x1000, -0x600, 0xb0, 0x5b05, 0 },
{ 0x2000, 0x2000, 0x2000, 22, 28, 24, 16, 16, 40, 0x800, 0x600, -0x600, 0xb0, 0, 0 },
2019-09-19 22:42:45 +00:00
},
// shrink human
{
{ 10384, 10384, 10384, 14, 17, 24, 16, 32, 80, 5632, 4608, 3072, 144, -564586, -1329173 },
{ 2108, 2108, 2108, 14, 17, 24, 16, 32, 80, 5120, 4096, -1536, 176, 0x5b05, 0 },
{ 2192, 2192, 2192, 22, 28, 24, 16, 16, 40, 2048, 1536, -1536, 176, 0, 0 },
2019-09-19 22:42:45 +00:00
},
// grown human
{
{ 19384, 19384, 19384, 14, 17, 24, 16, 32, 80, 5632, 4608, 3072, 144, -1014586, -1779173 },
{ 5608, 5608, 5608, 14, 17, 24, 16, 32, 80, 5120, 4096, -1536, 176, 0x5b05, 0 },
{ 11192, 11192, 11192, 22, 28, 24, 16, 16, 40, 2048, 1536, -1536, 176, 0, 0 },
2019-09-19 22:42:45 +00:00
},
};
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 {
2020-11-21 22:40:08 +00:00
int armor0;
int armor0max;
int armor1;
int armor1max;
int armor2;
int armor2max;
2019-09-19 22:42:45 +00:00
};
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 {
2020-11-21 22:40:08 +00:00
const char *message;
int Kills;
2019-09-19 22:42:45 +00:00
};
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 {
2020-11-21 22:40:08 +00:00
const char *message;
int Kills;
2019-09-19 22:42:45 +00:00
};
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 {
2020-11-21 22:40:08 +00:00
int armorType;
int Kills[3];
2019-09-19 22:42:45 +00:00
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 }
};
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];
}
2019-09-19 22:42:45 +00:00
char powerupActivate(PLAYER *pPlayer, int nPowerUp)
{
if (powerupCheck(pPlayer, nPowerUp) > 0 && gPowerUpInfo[nPowerUp].pickupOnce)
2019-09-19 22:42:45 +00:00
return 0;
if (!pPlayer->pwUpTime[nPowerUp])
pPlayer->pwUpTime[nPowerUp] = gPowerUpInfo[nPowerUp].bonusTime;
2019-09-19 22:42:45 +00:00
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;
2021-08-29 11:44:04 +00:00
else if (isGrown(pPlayer->actor())) playerDeactivateShrooms(pPlayer);
else playerSizeShrink(pPlayer, 2);
break;
case kItemShroomGrow:
if (!gModernMap) break;
2021-08-29 11:44:04 +00:00
else if (isShrinked(pPlayer->actor())) playerDeactivateShrooms(pPlayer);
else {
playerSizeGrow(pPlayer, 2);
if (powerupCheck(&gPlayer[pPlayer->pSprite->type - kDudePlayer1], kPwUpShadowCloak) > 0) {
powerupDeactivate(pPlayer, kPwUpShadowCloak);
pPlayer->pwUpTime[kPwUpShadowCloak] = 0;
}
2019-09-19 22:42:45 +00:00
2021-08-28 07:50:01 +00:00
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 == gMe && 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 == gMe && 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;
2019-09-19 22:42:45 +00:00
}
sfxPlay3DSound(pPlayer->pSprite, 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);
2021-08-28 07:50:01 +00:00
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 == gMe && VanillaMode() ? true : pPlayer->pwUpTime[24] == 0)
sfxSetReverb(0);
break;
case kItemReflectShots:
if (pPlayer == gMe && 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;
2019-09-19 22:42:45 +00:00
}
}
void powerupSetState(PLAYER *pPlayer, int nPowerUp, char bState)
{
if (!bState)
powerupActivate(pPlayer, nPowerUp);
else
powerupDeactivate(pPlayer, nPowerUp);
}
void powerupProcess(PLAYER *pPlayer)
{
pPlayer->packItemTime = ClipLow(pPlayer->packItemTime-4, 0);
2019-09-19 22:42:45 +00:00
for (int i = kMaxPowerUps-1; i >= 0; i--)
{
int nPack = powerupToPackItem(i);
if (nPack >= 0)
{
if (pPlayer->packSlots[nPack].isActive)
2019-09-19 22:42:45 +00:00
{
pPlayer->pwUpTime[i] = ClipLow(pPlayer->pwUpTime[i]-4, 0);
if (pPlayer->pwUpTime[i])
pPlayer->packSlots[nPack].curAmount = (100*pPlayer->pwUpTime[i])/gPowerUpInfo[i].bonusTime;
2019-09-19 22:42:45 +00:00
else
{
powerupDeactivate(pPlayer, i);
if (pPlayer->packItemId == nPack)
pPlayer->packItemId = 0;
2019-09-19 22:42:45 +00:00
}
}
}
else if (pPlayer->pwUpTime[i] > 0)
2019-09-19 22:42:45 +00:00
{
pPlayer->pwUpTime[i] = ClipLow(pPlayer->pwUpTime[i]-4, 0);
if (!pPlayer->pwUpTime[i])
2019-09-19 22:42:45 +00:00
powerupDeactivate(pPlayer, i);
}
}
}
void powerupClear(PLAYER *pPlayer)
{
for (int i = kMaxPowerUps-1; i >= 0; i--)
{
pPlayer->pwUpTime[i] = 0;
2019-09-19 22:42:45 +00:00
}
}
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;
2019-09-19 22:42:45 +00:00
}
return nPowerUp;
}
int powerupToPackItem(int nPowerUp)
{
switch (nPowerUp) {
case kPwUpDivingSuit:
return 1;
case kPwUpCrystalBall:
return 2;
case kPwUpBeastVision:
return 3;
case kPwUpJumpBoots:
return 4;
2019-09-19 22:42:45 +00:00
}
return -1;
}
char packAddItem(PLAYER *pPlayer, unsigned int nPack)
{
if (nPack <= 4)
{
if (pPlayer->packSlots[nPack].curAmount >= 100)
2019-09-19 22:42:45 +00:00
return 0;
pPlayer->packSlots[nPack].curAmount = 100;
2019-09-19 22:42:45 +00:00
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;
2019-09-19 22:42:45 +00:00
}
else
I_Error("Unhandled pack item %d", nPack);
2019-09-19 22:42:45 +00:00
return 1;
}
int packCheckItem(PLAYER *pPlayer, int nPack)
{
return pPlayer->packSlots[nPack].curAmount;
2019-09-19 22:42:45 +00:00
}
char packItemActive(PLAYER *pPlayer, int nPack)
{
return pPlayer->packSlots[nPack].isActive;
2019-09-19 22:42:45 +00:00
}
void packUseItem(PLAYER *pPlayer, int nPack)
{
char v4 = 0;
int nPowerUp = -1;
if (pPlayer->packSlots[nPack].curAmount > 0)
2019-09-19 22:42:45 +00:00
{
pPlayer->packItemId = nPack;
2019-09-19 22:42:45 +00:00
switch (nPack)
{
case 0:
{
XSPRITE *pXSprite = pPlayer->pXSprite;
unsigned int health = pXSprite->health>>4;
if (health < 100)
{
int heal = ClipHigh(100-health, pPlayer->packSlots[0].curAmount);
actHealDude(pPlayer->actor(), heal, 100);
pPlayer->packSlots[0].curAmount -= heal;
2019-09-19 22:42:45 +00:00
}
break;
}
case 1:
v4 = 1;
nPowerUp = kPwUpDivingSuit;
2019-09-19 22:42:45 +00:00
break;
case 2:
v4 = 1;
nPowerUp = kPwUpCrystalBall;
2019-09-19 22:42:45 +00:00
break;
case 3:
v4 = 1;
nPowerUp = kPwUpBeastVision;
2019-09-19 22:42:45 +00:00
break;
case 4:
v4 = 1;
nPowerUp = kPwUpJumpBoots;
2019-09-19 22:42:45 +00:00
break;
default:
I_Error("Unhandled pack item %d", nPack);
2019-09-19 22:42:45 +00:00
return;
}
}
pPlayer->packItemTime = 0;
2019-09-19 22:42:45 +00:00
if (v4)
powerupSetState(pPlayer, nPowerUp, pPlayer->packSlots[nPack].isActive);
2019-09-19 22:42:45 +00:00
}
void packPrevItem(PLAYER *pPlayer)
{
if (pPlayer->packItemTime > 0)
2019-09-19 22:42:45 +00:00
{
for (int i = 0; i < 2; i++)
2019-09-19 22:42:45 +00:00
{
for (int nPrev = pPlayer->packItemId-1; nPrev >= 0; nPrev--)
2019-09-19 22:42:45 +00:00
{
if (pPlayer->packSlots[nPrev].curAmount)
{
pPlayer->packItemId = nPrev;
pPlayer->packItemTime = 600;
return;
}
2019-09-19 22:42:45 +00:00
}
pPlayer->packItemId = 4;
if (pPlayer->packSlots[4].curAmount) break;
2019-09-19 22:42:45 +00:00
}
}
pPlayer->packItemTime = 600;
2019-09-19 22:42:45 +00:00
}
void packNextItem(PLAYER* pPlayer)
2019-09-19 22:42:45 +00:00
{
if (pPlayer->packItemTime > 0)
2019-09-19 22:42:45 +00:00
{
for (int i = 0; i < 2; i++)
2019-09-19 22:42:45 +00:00
{
for (int nNext = pPlayer->packItemId + 1; nNext < 5; nNext++)
2019-09-19 22:42:45 +00:00
{
if (pPlayer->packSlots[nNext].curAmount)
{
pPlayer->packItemId = nNext;
pPlayer->packItemTime = 600;
return;
}
2019-09-19 22:42:45 +00:00
}
pPlayer->packItemId = 0;
if (pPlayer->packSlots[0].curAmount) break;
2019-09-19 22:42:45 +00:00
}
}
pPlayer->packItemTime = 600;
2019-09-19 22:42:45 +00:00
}
char playerSeqPlaying(PLAYER * pPlayer, int nSeq)
{
int nCurSeq = seqGetID(3, pPlayer->pSprite->extra);
if (pPlayer->pDudeInfo->seqStartID+nSeq == nCurSeq && seqGetStatus(3,pPlayer->pSprite->extra) >= 0)
return 1;
return 0;
}
void playerSetRace(PLAYER *pPlayer, int nLifeMode)
{
assert(nLifeMode >= kModeHuman && nLifeMode <= kModeHumanGrown);
2019-09-19 22:42:45 +00:00
DUDEINFO *pDudeInfo = pPlayer->pDudeInfo;
*pDudeInfo = gPlayerTemplate[nLifeMode];
pPlayer->lifeMode = nLifeMode;
2019-09-19 22:42:45 +00:00
// By NoOne: don't forget to change clipdist for grow and shrink modes
pPlayer->pSprite->clipdist = pDudeInfo->clipdist;
for (int i = 0; i < 7; i++)
2020-12-02 23:30:19 +00:00
pDudeInfo->damageVal[i] = MulScale(Handicap[gSkill], pDudeInfo->startDamage[i], 8);
2019-09-19 22:42:45 +00:00
}
void playerSetGodMode(PLAYER *pPlayer, bool bGodMode)
2019-09-19 22:42:45 +00:00
{
pPlayer->godMode = bGodMode;
2019-09-19 22:42:45 +00:00
}
void playerResetInertia(PLAYER *pPlayer)
{
POSTURE *pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture];
pPlayer->zView = pPlayer->pSprite->z-pPosture->eyeAboveZ;
pPlayer->zWeapon = pPlayer->pSprite->z-pPosture->weaponAboveZ;
viewBackupView(pPlayer->nPlayer);
2019-09-19 22:42:45 +00:00
}
2019-09-07 16:39:26 +00:00
void playerCorrectInertia(PLAYER* pPlayer, vec3_t const *oldpos)
{
pPlayer->zView += pPlayer->pSprite->z-oldpos->z;
pPlayer->zWeapon += pPlayer->pSprite->z-oldpos->z;
viewCorrectViewOffsets(pPlayer->nPlayer, oldpos);
2019-09-07 16:39:26 +00:00
}
2019-09-19 22:42:45 +00:00
void playerResetPowerUps(PLAYER* pPlayer)
{
for (int i = 0; i < kMaxPowerUps; i++) {
if (!VanillaMode() && (i == kPwUpJumpBoots || i == kPwUpDivingSuit || i == kPwUpCrystalBall || i == kPwUpBeastVision))
2019-09-19 22:42:45 +00:00
continue;
pPlayer->pwUpTime[i] = 0;
2019-09-19 22:42:45 +00:00
}
}
void playerResetPosture(PLAYER* pPlayer) {
memcpy(pPlayer->pPosture, gPostureDefaults, sizeof(gPostureDefaults));
}
void playerStart(int nPlayer, int bNewLevel)
2019-09-19 22:42:45 +00:00
{
PLAYER* pPlayer = &gPlayer[nPlayer];
InputPacket* pInput = &pPlayer->input;
2019-09-19 22:42:45 +00:00
ZONE* pStartZone = NULL;
2019-09-19 22:42:45 +00:00
// normal start position
if (gGameOptions.nGameType <= 1)
pStartZone = &gStartZone[nPlayer];
#ifdef NOONE_EXTENSIONS
// let's check if there is positions of teams is specified
2019-09-19 22:42:45 +00:00
// if no, pick position randomly, just like it works in vanilla.
else if (gModernMap && gGameOptions.nGameType == 3 && gTeamsSpawnUsed == true) {
2019-09-19 22:42:45 +00:00
int maxRetries = 5;
while (maxRetries-- > 0) {
if (pPlayer->teamId == 0) pStartZone = &gStartZoneTeam1[Random(3)];
2019-09-19 22:42:45 +00:00
else pStartZone = &gStartZoneTeam2[Random(3)];
if (maxRetries != 0) {
// check if there is no spawned player in selected zone
int i;
SectIterator it(pStartZone->sectnum);
while ((i = it.NextIndex()) >= 0)
{
2019-09-19 22:42:45 +00:00
spritetype* pSprite = &sprite[i];
if (pStartZone->x == pSprite->x && pStartZone->y == pSprite->y && IsPlayerSprite(pSprite)) {
pStartZone = NULL;
break;
}
}
}
if (pStartZone != NULL)
break;
}
}
#endif
else {
2019-09-19 22:42:45 +00:00
pStartZone = &gStartZone[Random(8)];
}
auto actor = actSpawnSprite(pStartZone->sectnum, pStartZone->x, pStartZone->y, pStartZone->z, 6, 1);
spritetype* pSprite = &actor->s();
assert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
2019-09-19 22:42:45 +00:00
XSPRITE *pXSprite = &xsprite[pSprite->extra];
pPlayer->pSprite = pSprite;
pPlayer->pXSprite = pXSprite;
pPlayer->nSprite = pSprite->index;
2019-09-19 22:42:45 +00:00
DUDEINFO *pDudeInfo = &dudeInfo[kDudePlayer1 + nPlayer - kDudeBase];
pPlayer->pDudeInfo = pDudeInfo;
playerSetRace(pPlayer, kModeHuman);
playerResetPosture(pPlayer);
2019-09-19 22:42:45 +00:00
seqSpawn(pDudeInfo->seqStartID, 3, pSprite->extra, -1);
if (pPlayer == gMe)
actor->s().cstat2 |= CSTAT2_SPRITE_MAPPED;
2019-09-19 22:42:45 +00:00
int top, bottom;
GetSpriteExtents(pSprite, &top, &bottom);
pSprite->z -= bottom - pSprite->z;
pSprite->pal = 11+(pPlayer->teamId&3);
pSprite->ang = pStartZone->ang;
pPlayer->angle.ang = buildang(pSprite->ang);
2019-09-19 22:42:45 +00:00
pSprite->type = kDudePlayer1+nPlayer;
pSprite->clipdist = pDudeInfo->clipdist;
pSprite->flags = 15;
2019-09-19 22:42:45 +00:00
pXSprite->burnTime = 0;
2021-08-29 17:22:51 +00:00
actor->SetBurnSource(nullptr);
2019-09-19 22:42:45 +00:00
pPlayer->pXSprite->health = pDudeInfo->startHealth<<4;
pPlayer->pSprite->cstat &= (unsigned short)~32768;
pPlayer->bloodlust = 0;
pPlayer->horizon.horiz = pPlayer->horizon.horizoff = q16horiz(0);
pPlayer->slope = 0;
2021-10-13 21:56:03 +00:00
pPlayer->setFragger(0);
pPlayer->underwaterTime = 1200;
pPlayer->bubbleTime = 0;
pPlayer->restTime = 0;
pPlayer->kickPower = 0;
pPlayer->laughCount = 0;
pPlayer->angle.spin = 0;
pPlayer->posture = 0;
pPlayer->voodooTarget = nullptr;
pPlayer->voodooTargets = 0;
pPlayer->voodooVar1 = 0;
pPlayer->vodooVar2 = 0;
2019-09-19 22:42:45 +00:00
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;
2019-09-19 22:42:45 +00:00
for (int i = 0; i < 8; i++)
pPlayer->used2[i] = -1;
2019-09-19 22:42:45 +00:00
for (int i = 0; i < 7; i++)
pPlayer->damageControl[i] = 0;
if (pPlayer->godMode)
2019-09-19 22:42:45 +00:00
playerSetGodMode(pPlayer, 1);
gInfiniteAmmo = 0;
gFullMap = 0;
pPlayer->throwPower = 0;
pPlayer->deathTime = 0;
pPlayer->nextWeapon = kWeapNone;
2021-08-27 15:04:34 +00:00
actor->xvel() = actor->yvel() = actor->zvel() = 0;
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
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
// assign or update player's sprite index for conditions
if (gModernMap) {
2021-09-02 20:16:18 +00:00
BloodStatIterator it(kStatModernPlayerLinker);
while (auto iactor = it.Next())
2020-10-15 15:15:45 +00:00
{
2021-09-02 20:16:18 +00:00
XSPRITE* pXCtrl = &iactor->x();
if (pXCtrl->data1 == pPlayer->nPlayer + 1)
{
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
int nSpriteOld = pXCtrl->sysData1;
2021-09-02 20:16:18 +00:00
trPlayerCtrlLink(iactor, pPlayer, (nSpriteOld < 0) ? true : false);
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
if (nSpriteOld > 0)
condUpdateObjectIndex(&bloodActors[nSpriteOld], &bloodActors[pXCtrl->sysData1]);
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
}
}
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
}
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
#endif
pPlayer->hand = 0;
2019-09-19 22:42:45 +00:00
pPlayer->nWaterPal = 0;
playerResetPowerUps(pPlayer);
if (pPlayer == gMe)
{
viewInitializePrediction();
}
if (IsUnderwaterSector(pSprite->sectnum))
{
pPlayer->posture = 1;
pPlayer->pXSprite->medium = kMediumWater;
2019-09-19 22:42:45 +00:00
}
}
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);
2019-09-19 22:42:45 +00:00
for (int i = 0; i < 14; i++)
{
pPlayer->hasWeapon[i] = gInfiniteAmmo;
pPlayer->weaponMode[i] = 0;
2019-09-19 22:42:45 +00:00
}
pPlayer->hasWeapon[1] = 1;
pPlayer->curWeapon = kWeapNone;
pPlayer->qavCallback = -1;
pPlayer->newWeapon = kWeapPitchFork;
2019-09-19 22:42:45 +00:00
for (int i = 0; i < 14; i++)
{
pPlayer->weaponOrder[0][i] = dword_136400[i];
pPlayer->weaponOrder[1][i] = dword_136438[i];
2019-09-19 22:42:45 +00:00
}
for (int i = 0; i < 12; i++)
{
if (gInfiniteAmmo)
pPlayer->ammoCount[i] = gAmmoInfo[i].max;
2019-09-19 22:42:45 +00:00
else
pPlayer->ammoCount[i] = 0;
2019-09-19 22:42:45 +00:00
}
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;
2019-09-19 22:42:45 +00:00
}
#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];
2019-09-19 22:42:45 +00:00
void playerInit(int nPlayer, unsigned int a2)
{
PLAYER *pPlayer = &gPlayer[nPlayer];
if (!(a2&1))
memset(pPlayer, 0, sizeof(PLAYER));
pPlayer->nPlayer = nPlayer;
pPlayer->teamId = nPlayer;
2019-09-19 22:42:45 +00:00
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));
2019-09-19 22:42:45 +00:00
if (!(a2&1))
playerReset(pPlayer);
}
2020-11-21 19:10:45 +00:00
char findDroppedLeech(PLAYER *a1, spritetype *a2)
2019-09-19 22:42:45 +00:00
{
2020-10-15 15:15:45 +00:00
int nSprite;
StatIterator it(kStatThing);
while ((nSprite = it.NextIndex()) >= 0)
2019-09-19 22:42:45 +00:00
{
if (a2 && a2->index == nSprite)
continue;
spritetype *pSprite = &sprite[nSprite];
if (pSprite->type == kThingDroppedLifeLeech && pSprite->owner == a1->nSprite)
2019-09-19 22:42:45 +00:00
return 1;
}
return 0;
}
char PickupItem(PLAYER *pPlayer, spritetype *pItem) {
auto itemactor = &bloodActors[pItem->index];
spritetype *pSprite = pPlayer->pSprite; XSPRITE *pXSprite = pPlayer->pXSprite;
char buffer[80]; int pickupSnd = 775; int nType = pItem->type - kItemBase;
switch (pItem->type) {
case kItemShadowCloak:
#ifdef NOONE_EXTENSIONS
2021-08-29 11:44:04 +00:00
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 (pItem->type) {
case kItemShroomShrink:
2021-08-29 11:44:04 +00:00
if (isShrinked(pPlayer->actor())) return false;
break;
case kItemShroomGrow:
2021-08-29 11:44:04 +00:00
if (isGrown(pPlayer->actor())) return false;
break;
}
powerupActivate(pPlayer, nType);
}
2019-09-19 22:42:45 +00:00
break;
#endif
case kItemFlagABase:
case kItemFlagBBase: {
if (gGameOptions.nGameType != 3 || pItem->extra <= 0) return 0;
XSPRITE * pXItem = &xsprite[pItem->extra];
if (pItem->type == kItemFlagABase) {
if (pPlayer->teamId == 1) {
if ((pPlayer->hasFlag & 1) == 0 && pXItem->state) {
pPlayer->hasFlag |= 1;
pPlayer->used2[0] = pItem->index;
trTriggerSprite(pItem->index, pXItem, kCmdOff);
sprintf(buffer, "%s stole Blue Flag", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8007, 255, 2, 0);
viewSetMessage(buffer);
}
}
if (pPlayer->teamId == 0) {
if ((pPlayer->hasFlag & 1) != 0 && !pXItem->state) {
pPlayer->hasFlag &= ~1;
pPlayer->used2[0] = -1;
trTriggerSprite(pItem->index, pXItem, kCmdOn);
sprintf(buffer, "%s returned Blue Flag", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8003, 255, 2, 0);
viewSetMessage(buffer);
}
if ((pPlayer->hasFlag & 2) != 0 && pXItem->state) {
pPlayer->hasFlag &= ~2;
pPlayer->used2[1] = -1;
team_score[pPlayer->teamId] += 10;
team_ticker[pPlayer->teamId] += 240;
evSendGame(81, kCmdOn);
sprintf(buffer, "%s captured Red Flag!", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8001, 255, 2, 0);
viewSetMessage(buffer);
}
}
2019-09-19 22:42:45 +00:00
}
else if (pItem->type == kItemFlagBBase) {
if (pPlayer->teamId == 0) {
if ((pPlayer->hasFlag & 2) == 0 && pXItem->state) {
pPlayer->hasFlag |= 2;
pPlayer->used2[1] = pItem->index;
trTriggerSprite(pItem->index, pXItem, kCmdOff);
sprintf(buffer, "%s stole Red Flag", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8006, 255, 2, 0);
viewSetMessage(buffer);
}
}
if (pPlayer->teamId == 1) {
if ((pPlayer->hasFlag & 2) != 0 && !pXItem->state)
2019-09-19 22:42:45 +00:00
{
pPlayer->hasFlag &= ~2;
pPlayer->used2[1] = -1;
trTriggerSprite(pItem->index, pXItem, kCmdOn);
sprintf(buffer, "%s returned Red Flag", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8002, 255, 2, 0);
viewSetMessage(buffer);
}
if ((pPlayer->hasFlag & 1) != 0 && pXItem->state)
2019-09-19 22:42:45 +00:00
{
pPlayer->hasFlag &= ~1;
pPlayer->used2[0] = -1;
team_score[pPlayer->teamId] += 10;
team_ticker[pPlayer->teamId] += 240;
evSendGame(80, kCmdOn);
sprintf(buffer, "%s captured Blue Flag!", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8000, 255, 2, 0);
viewSetMessage(buffer);
}
}
}
}
return 0;
2021-08-27 20:30:34 +00:00
case kItemFlagA: {
if (gGameOptions.nGameType != 3) return 0;
gBlueFlagDropped = false;
2021-08-27 20:30:34 +00:00
const bool enemyTeam = (pPlayer->teamId&1) == 1;
if (!enemyTeam && (pItem->owner >= 0) && (pItem->owner < kMaxSprites)) {
pPlayer->hasFlag &= ~1;
pPlayer->used2[0] = -1;
spritetype* pOwner = &sprite[pItem->owner];
XSPRITE* pXOwner = &xsprite[pOwner->extra];
trTriggerSprite(pOwner->index, pXOwner, kCmdOn);
2021-08-27 20:37:45 +00:00
sprintf(buffer, "%s returned Blue Flag", PlayerName(pPlayer->nPlayer));
2021-08-27 20:30:34 +00:00
sndStartSample(8003, 255, 2, 0);
viewSetMessage(buffer);
break;
}
pPlayer->hasFlag |= 1;
pPlayer->used2[0] = pItem->owner;
if (enemyTeam)
{
2021-08-27 20:37:45 +00:00
sprintf(buffer, "%s stole Blue Flag", PlayerName(pPlayer->nPlayer));
2021-08-27 20:30:34 +00:00
sndStartSample(8007, 255, 2, 0);
viewSetMessage(buffer);
}
break;
2021-08-27 20:30:34 +00:00
}
case kItemFlagB: {
if (gGameOptions.nGameType != 3) return 0;
gRedFlagDropped = false;
2021-08-27 20:30:34 +00:00
const bool enemyTeam = (pPlayer->teamId&1) == 0;
if (!enemyTeam && (pItem->owner >= 0) && (pItem->owner < kMaxSprites)) {
pPlayer->hasFlag &= ~2;
pPlayer->used2[1] = -1;
spritetype* pOwner = &sprite[pItem->owner];
XSPRITE* pXOwner = &xsprite[pOwner->extra];
trTriggerSprite(pOwner->index, pXOwner, kCmdOn);
2021-08-27 20:37:45 +00:00
sprintf(buffer, "%s returned Red Flag", PlayerName(pPlayer->nPlayer));
2021-08-27 20:30:34 +00:00
sndStartSample(8002, 255, 2, 0);
viewSetMessage(buffer);
break;
}
pPlayer->hasFlag |= 2;
pPlayer->used2[1] = pItem->owner;
if (enemyTeam)
{
2021-08-27 20:37:45 +00:00
sprintf(buffer, "%s stole Red Flag", PlayerName(pPlayer->nPlayer));
2021-08-27 20:30:34 +00:00
sndStartSample(8006, 255, 2, 0);
viewSetMessage(buffer);
}
break;
2021-08-27 20:30:34 +00:00
}
case kItemArmorBasic:
case kItemArmorBody:
case kItemArmorFire:
case kItemArmorSpirit:
case kItemArmorSuper: {
ARMORDATA *pArmorData = &armorData[pItem->type - kItemArmorBasic]; bool pickedUp = false;
2020-11-21 22:40:08 +00:00
if (pPlayer->armor[1] < pArmorData->armor1max) {
pPlayer->armor[1] = ClipHigh(pPlayer->armor[1]+pArmorData->armor1, pArmorData->armor1max);
pickedUp = true;
}
2020-11-21 22:40:08 +00:00
if (pPlayer->armor[0] < pArmorData->armor0max) {
pPlayer->armor[0] = ClipHigh(pPlayer->armor[0]+pArmorData->armor0, pArmorData->armor0max);
pickedUp = true;
}
2020-11-21 22:40:08 +00:00
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;
2019-09-19 22:42:45 +00:00
}
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[pItem->type-99]) return 0;
pPlayer->hasKey[pItem->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 && sprite[pItem->index].extra >= 0 && xsprite[sprite[pItem->index].extra].data1 > 0)
addPower = xsprite[sprite[pItem->index].extra].data1;
#endif
if (!actHealDude(pPlayer->actor(), addPower, gPowerUpInfo[nType].maxTime)) return 0;
return 1;
2019-09-19 22:42:45 +00:00
}
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;
2019-09-19 22:42:45 +00:00
}
2019-09-19 22:42:45 +00:00
sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, pickupSnd, pSprite->sectnum);
return 1;
}
char PickupAmmo(PLAYER* pPlayer, spritetype* pAmmo) {
const AMMOITEMDATA* pAmmoItemData = &gAmmoItemData[pAmmo->type - kItemAmmoBase];
int nAmmoType = pAmmoItemData->type;
2019-09-19 22:42:45 +00:00
if (pPlayer->ammoCount[nAmmoType] >= gAmmoInfo[nAmmoType].max) return 0;
#ifdef NOONE_EXTENSIONS
else if (gModernMap && pAmmo->extra >= 0 && xsprite[pAmmo->extra].data1 > 0) // allow custom amount for item
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + xsprite[pAmmo->extra].data1, gAmmoInfo[nAmmoType].max);
#endif
else
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType]+pAmmoItemData->count, gAmmoInfo[nAmmoType].max);
2019-09-19 22:42:45 +00:00
if (pAmmoItemData->weaponType) pPlayer->hasWeapon[pAmmoItemData->weaponType] = 1;
2019-09-19 22:42:45 +00:00
sfxPlay3DSound(pPlayer->pSprite, 782, -1, 0);
return 1;
}
2021-08-30 18:21:49 +00:00
char PickupWeapon(PLAYER *pPlayer, spritetype *pWeapon)
{
auto actor = &bloodActors[pWeapon->index];
const WEAPONITEMDATA *pWeaponItemData = &gWeaponItemData[pWeapon->type - kItemWeaponBase];
int nWeaponType = pWeaponItemData->type;
int nAmmoType = pWeaponItemData->ammoType;
if (!pPlayer->hasWeapon[nWeaponType] || gGameOptions.nWeaponSettings == 2 || gGameOptions.nWeaponSettings == 3) {
2020-11-21 19:10:45 +00:00
if (pWeapon->type == kItemWeaponLifeLeech && gGameOptions.nGameType > 1 && findDroppedLeech(pPlayer, NULL))
2019-09-19 22:42:45 +00:00
return 0;
pPlayer->hasWeapon[nWeaponType] = 1;
2019-09-19 22:42:45 +00:00
if (nAmmoType == -1) return 0;
// allow to set custom ammo count for weapon pickups
#ifdef NOONE_EXTENSIONS
else if (gModernMap && pWeapon->extra >= 0 && xsprite[pWeapon->extra].data1 > 0)
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + xsprite[pWeapon->extra].data1, gAmmoInfo[nAmmoType].max);
#endif
else
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pWeaponItemData->count, gAmmoInfo[nAmmoType].max);
2019-09-19 22:42:45 +00:00
int nNewWeapon = WeaponUpgrade(pPlayer, nWeaponType);
if (nNewWeapon != pPlayer->curWeapon) {
pPlayer->weaponState = 0;
pPlayer->nextWeapon = nNewWeapon;
2019-09-19 22:42:45 +00:00
}
sfxPlay3DSound(pPlayer->pSprite, 777, -1, 0);
return 1;
}
2021-08-30 18:21:49 +00:00
if (!actGetRespawnTime(actor) || nAmmoType == -1 || pPlayer->ammoCount[nAmmoType] >= gAmmoInfo[nAmmoType].max) return 0;
#ifdef NOONE_EXTENSIONS
else if (gModernMap && pWeapon->extra >= 0 && xsprite[pWeapon->extra].data1 > 0)
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + xsprite[pWeapon->extra].data1, gAmmoInfo[nAmmoType].max);
#endif
else
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType]+pWeaponItemData->count, gAmmoInfo[nAmmoType].max);
2019-09-19 22:42:45 +00:00
sfxPlay3DSound(pPlayer->pSprite, 777, -1, 0);
return 1;
}
void PickUp(PLAYER *pPlayer, spritetype *pSprite)
{
2021-08-30 18:21:49 +00:00
auto actor = &bloodActors[pSprite->index];
const char *msg = nullptr;
2019-09-19 22:42:45 +00:00
int nType = pSprite->type;
char pickedUp = 0;
int customMsg = -1;
#ifdef NOONE_EXTENSIONS
if (gModernMap) { // allow custom INI message instead "Picked up"
XSPRITE* pXSprite = (pSprite->extra >= 0) ? &xsprite[pSprite->extra] : NULL;
if (pXSprite != NULL && pXSprite->txID != 3 && pXSprite->lockMsg > 0)
customMsg = pXSprite->lockMsg;
}
#endif
if (nType >= kItemBase && nType <= kItemMax) {
pickedUp = PickupItem(pPlayer, pSprite);
if (pickedUp && customMsg == -1) msg = GStrings(FStringf("TXTB_ITEM%02d", int(nType - kItemBase +1)));
} else if (nType >= kItemAmmoBase && nType < kItemAmmoMax) {
pickedUp = PickupAmmo(pPlayer, pSprite);
if (pickedUp && customMsg == -1) msg = GStrings(FStringf("TXTB_AMMO%02d", int(nType - kItemAmmoBase +1)));
} else if (nType >= kItemWeaponBase && nType < kItemWeaponMax) {
pickedUp = PickupWeapon(pPlayer, pSprite);
if (pickedUp && customMsg == -1) msg = GStrings(FStringf("TXTB_WPN%02d", int(nType - kItemWeaponBase +1)));
}
if (!pickedUp) return;
else if (pSprite->extra > 0) {
XSPRITE *pXSprite = &xsprite[pSprite->extra];
if (pXSprite->Pickup)
trTriggerSprite(pSprite->index, pXSprite, kCmdSpritePickup);
}
2021-08-30 18:21:49 +00:00
if (!actCheckRespawn(actor))
actPostSprite(actor, kStatFree);
pPlayer->pickupEffect = 30;
if (pPlayer == gMe) {
if (customMsg > 0) trTextOver(customMsg - 1);
else if (msg) viewSetMessage(msg, 0, MESSAGE_PRIORITY_PICKUP);
2019-09-19 22:42:45 +00:00
}
}
void CheckPickUp(PLAYER *pPlayer)
{
spritetype *pSprite = pPlayer->pSprite;
int x = pSprite->x;
int y = pSprite->y;
int z = pSprite->z;
int nSector = pSprite->sectnum;
int nNextSprite;
2020-10-15 15:15:45 +00:00
int nSprite;
StatIterator it(kStatItem);
while ((nSprite = it.NextIndex()) >= 0)
{
2019-09-19 22:42:45 +00:00
spritetype *pItem = &sprite[nSprite];
nNextSprite = nextspritestat[nSprite];
if (pItem->flags&32)
2019-09-19 22:42:45 +00:00
continue;
int dx = abs(x-pItem->x)>>4;
2019-09-19 22:42:45 +00:00
if (dx > 48)
continue;
int dy = abs(y-pItem->y)>>4;
2019-09-19 22:42:45 +00:00
if (dy > 48)
continue;
int top, bottom;
GetSpriteExtents(pSprite, &top, &bottom);
int vb = 0;
if (pItem->z < top)
vb = (top-pItem->z)>>8;
else if (pItem->z > bottom)
vb = (pItem->z-bottom)>>8;
if (vb > 32)
continue;
if (approxDist(dx,dy) > 48)
continue;
GetSpriteExtents(pItem, &top, &bottom);
if (cansee(x, y, z, nSector, pItem->x, pItem->y, pItem->z, pItem->sectnum)
|| cansee(x, y, z, nSector, pItem->x, pItem->y, top, pItem->sectnum)
|| cansee(x, y, z, nSector, pItem->x, pItem->y, bottom, pItem->sectnum))
PickUp(pPlayer, pItem);
}
}
int ActionScan(PLAYER *pPlayer, int *a2, int *a3)
{
*a2 = 0;
*a3 = 0;
spritetype *pSprite = pPlayer->pSprite;
int x = bcos(pSprite->ang);
int y = bsin(pSprite->ang);
int z = pPlayer->slope;
int hit = HitScan(pSprite, pPlayer->zView, x, y, z, 0x10000040, 128);
2019-09-19 22:42:45 +00:00
int hitDist = approxDist(pSprite->x-gHitInfo.hitx, pSprite->y-gHitInfo.hity)>>4;
if (hitDist < 64)
{
switch (hit)
{
case 3:
*a2 = gHitInfo.hitactor ? gHitInfo.hitactor->s().index : -1;
2019-09-19 22:42:45 +00:00
*a3 = sprite[*a2].extra;
if (*a3 > 0 && sprite[*a2].statnum == kStatThing)
2019-09-19 22:42:45 +00:00
{
spritetype *pSprite = &sprite[*a2];
XSPRITE *pXSprite = &xsprite[*a3];
if (pSprite->type == kThingDroppedLifeLeech)
2019-09-19 22:42:45 +00:00
{
2020-11-21 19:10:45 +00:00
if (gGameOptions.nGameType > 1 && findDroppedLeech(pPlayer, pSprite))
2019-09-19 22:42:45 +00:00
return -1;
pXSprite->data4 = pPlayer->nPlayer;
2019-09-19 22:42:45 +00:00
pXSprite->isTriggered = 0;
}
}
if (*a3 > 0 && xsprite[*a3].Push)
return 3;
if (sprite[*a2].statnum == kStatDude)
2019-09-19 22:42:45 +00:00
{
spritetype *pSprite = &sprite[*a2];
XSPRITE *pXSprite = &xsprite[*a3];
int nMass = getDudeInfo(pSprite->type)->mass;
2019-09-19 22:42:45 +00:00
if (nMass)
{
int t2 = DivScale(0xccccc, nMass, 8);
xvel[*a2] += MulScale(x, t2, 16);
yvel[*a2] += MulScale(y, t2, 16);
zvel[*a2] += MulScale(z, t2, 16);
2019-09-19 22:42:45 +00:00
}
if (pXSprite->Push && !pXSprite->state && !pXSprite->isTriggered)
trTriggerSprite(*a2, pXSprite, kCmdSpritePush);
2019-09-19 22:42:45 +00:00
}
break;
case 0:
case 4:
*a2 = gHitInfo.hitwall;
*a3 = wall[*a2].extra;
if (*a3 > 0 && xwall[*a3].triggerPush)
return 0;
if (wall[*a2].nextsector >= 0)
{
*a2 = wall[*a2].nextsector;
*a3 = sector[*a2].extra;
if (*a3 > 0 && xsector[*a3].Wallpush)
return 6;
}
break;
case 1:
case 2:
*a2 = gHitInfo.hitsect;
*a3 = sector[*a2].extra;
if (*a3 > 0 && xsector[*a3].Push)
return 6;
break;
}
}
*a2 = pSprite->sectnum;
*a3 = sector[*a2].extra;
if (*a3 > 0 && xsector[*a3].Push)
return 6;
return -1;
}
//---------------------------------------------------------------------------
//
// Player's sprite angle function, called in ProcessInput() or from gi->GetInput() as required.
//
//---------------------------------------------------------------------------
void UpdatePlayerSpriteAngle(PLAYER *pPlayer)
{
pPlayer->pSprite->ang = pPlayer->angle.ang.asbuild();
}
//---------------------------------------------------------------------------
//
// 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* const pSprite = pPlayer->pSprite;
auto* const pXSprite = pPlayer->pXSprite;
2021-08-29 17:09:29 +00:00
int const florhit = pPlayer->actor()->hit.florhit.type;
char const va = pXSprite->height < 16 && (florhit == kHitSector || florhit == 0) ? 1 : 0;
pPlayer->horizon.calcviewpitch(pSprite->pos.vec2, buildang(pSprite->ang), va, sector[pSprite->sectnum].floorstat & 2, pSprite->sectnum, scaleAdjust);
}
2019-09-19 22:42:45 +00:00
void ProcessInput(PLAYER *pPlayer)
{
enum
{
Item_MedKit = 0,
Item_CrystalBall = 1,
Item_BeastVision = 2,
Item_JumpBoots = 3
};
pPlayer->horizon.resetadjustment();
pPlayer->angle.resetadjustment();
2019-09-19 22:42:45 +00:00
spritetype *pSprite = pPlayer->pSprite;
XSPRITE *pXSprite = pPlayer->pXSprite;
int nSprite = pPlayer->nSprite;
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;
2019-09-19 22:42:45 +00:00
WeaponProcess(pPlayer);
if (pXSprite->health == 0)
{
char bSeqStat = playerSeqPlaying(pPlayer, 16);
if (pPlayer->fraggerId != -1)
2019-09-19 22:42:45 +00:00
{
pPlayer->angle.addadjustment(getincanglebam(pPlayer->angle.ang, bvectangbam(sprite[pPlayer->fraggerId].x - pSprite->x, sprite[pPlayer->fraggerId].y - pSprite->y)));
2019-09-19 22:42:45 +00:00
}
pPlayer->deathTime += 4;
2019-09-19 22:42:45 +00:00
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)
2019-09-19 22:42:45 +00:00
{
if (bSeqStat)
{
if (pPlayer->deathTime > 360)
2019-09-19 22:42:45 +00:00
seqSpawn(pPlayer->pDudeInfo->seqStartID+14, 3, pPlayer->pSprite->extra, nPlayerSurviveClient);
}
else if (seqGetStatus(3, pPlayer->pSprite->extra) < 0)
{
if (pPlayer->pSprite)
pPlayer->pSprite->type = kThingBloodChunks;
actPostSprite(pPlayer->actor(), kStatThing);
2019-09-19 22:42:45 +00:00
seqSpawn(pPlayer->pDudeInfo->seqStartID+15, 3, pPlayer->pSprite->extra, -1);
playerReset(pPlayer);
if (gGameOptions.nGameType == 0 && numplayers == 1)
{
gameaction = ga_autoloadgame;
2019-09-19 22:42:45 +00:00
}
else
playerStart(pPlayer->nPlayer);
2019-09-19 22:42:45 +00:00
}
pInput->actions &= ~SB_OPEN;
2019-09-19 22:42:45 +00:00
}
return;
}
if (pPlayer->posture == 1)
2019-09-19 22:42:45 +00:00
{
int x = Cos(pSprite->ang);
int y = Sin(pSprite->ang);
if (pInput->fvel)
2019-09-19 22:42:45 +00:00
{
int forward = pInput->fvel;
2019-09-19 22:42:45 +00:00
if (forward > 0)
forward = MulScale(pPosture->frontAccel, forward, 8);
2019-09-19 22:42:45 +00:00
else
forward = MulScale(pPosture->backAccel, forward, 8);
xvel[nSprite] += MulScale(forward, x, 30);
yvel[nSprite] += MulScale(forward, y, 30);
2019-09-19 22:42:45 +00:00
}
if (pInput->svel)
2019-09-19 22:42:45 +00:00
{
int strafe = pInput->svel;
strafe = MulScale(pPosture->sideAccel, strafe, 8);
xvel[nSprite] += MulScale(strafe, y, 30);
yvel[nSprite] -= MulScale(strafe, x, 30);
2019-09-19 22:42:45 +00:00
}
}
else if (pXSprite->height < 256)
{
int speed = 0x10000;
if (pXSprite->height > 0)
speed -= DivScale(pXSprite->height, 256, 16);
2019-09-19 22:42:45 +00:00
int x = Cos(pSprite->ang);
int y = Sin(pSprite->ang);
if (pInput->fvel)
2019-09-19 22:42:45 +00:00
{
int forward = pInput->fvel;
2019-09-19 22:42:45 +00:00
if (forward > 0)
forward = MulScale(pPosture->frontAccel, forward, 8);
2019-09-19 22:42:45 +00:00
else
forward = MulScale(pPosture->backAccel, forward, 8);
2019-09-19 22:42:45 +00:00
if (pXSprite->height)
forward = MulScale(forward, speed, 16);
xvel[nSprite] += MulScale(forward, x, 30);
yvel[nSprite] += MulScale(forward, y, 30);
2019-09-19 22:42:45 +00:00
}
if (pInput->svel)
2019-09-19 22:42:45 +00:00
{
int strafe = pInput->svel;
strafe = MulScale(pPosture->sideAccel, strafe, 8);
2019-09-19 22:42:45 +00:00
if (pXSprite->height)
strafe = MulScale(strafe, speed, 16);
xvel[nSprite] += MulScale(strafe, y, 30);
yvel[nSprite] -= MulScale(strafe, x, 30);
2019-09-19 22:42:45 +00:00
}
}
if (SyncInput())
2019-09-19 22:42:45 +00:00
{
pPlayer->angle.applyinput(pInput->avel, &pInput->actions);
2019-09-19 22:42:45 +00:00
}
// unconditionally update the player's sprite angle
// in case game is forcing synchronised input.
UpdatePlayerSpriteAngle(pPlayer);
if (!(pInput->actions & SB_JUMP))
pPlayer->cantJump = 0;
2019-09-19 22:42:45 +00:00
switch (pPlayer->posture) {
2019-09-19 22:42:45 +00:00
case 1:
if (pInput->actions & SB_JUMP)
zvel[nSprite] -= pPosture->normalJumpZ;//0x5b05;
if (pInput->actions & SB_CROUCH)
zvel[nSprite] += pPosture->normalJumpZ;//0x5b05;
2019-09-19 22:42:45 +00:00
break;
case 2:
if (!(pInput->actions & SB_CROUCH))
pPlayer->posture = 0;
2019-09-19 22:42:45 +00:00
break;
default:
if (!pPlayer->cantJump && (pInput->actions & SB_JUMP) && pXSprite->height == 0) {
#ifdef NOONE_EXTENSIONS
if ((packItemActive(pPlayer, 4) && pPosture->pwupJumpZ != 0) || pPosture->normalJumpZ != 0)
#endif
sfxPlay3DSound(pSprite, 700, 0, 0);
if (packItemActive(pPlayer, 4)) zvel[nSprite] = pPosture->pwupJumpZ; //-0x175555;
else zvel[nSprite] = pPosture->normalJumpZ; //-0xbaaaa;
pPlayer->cantJump = 1;
2019-09-19 22:42:45 +00:00
}
if (pInput->actions & SB_CROUCH)
pPlayer->posture = 2;
2019-09-19 22:42:45 +00:00
break;
}
if (pInput->actions & SB_OPEN)
2019-09-19 22:42:45 +00:00
{
int a2, a3;
int hit = ActionScan(pPlayer, &a2, &a3);
switch (hit)
{
case 6:
if (a3 > 0 && a3 <= 2048)
{
XSECTOR *pXSector = &xsector[a3];
int key = pXSector->Key;
if (pXSector->locked && pPlayer == gMe)
{
viewSetMessage(GStrings("TXTB_LOCKED"));
auto snd = 3062;
if (sndCheckPlaying(snd))
sndStopSample(snd);
sndStartSample(snd, 255, 2, 0);
2019-09-19 22:42:45 +00:00
}
if (!key || pPlayer->hasKey[key])
trTriggerSector(a2, pXSector, kCmdSpritePush);
2019-09-19 22:42:45 +00:00
else if (pPlayer == gMe)
{
viewSetMessage(GStrings("TXTB_KEY"));
auto snd = 3063;
if (sndCheckPlaying(snd))
sndStopSample(snd);
sndStartSample(snd, 255, 2, 0);
2019-09-19 22:42:45 +00:00
}
}
break;
case 0:
{
XWALL *pXWall = &xwall[a3];
int key = pXWall->key;
if (pXWall->locked && pPlayer == gMe)
{
viewSetMessage(GStrings("TXTB_LOCKED"));
auto snd = 3062;
if (sndCheckPlaying(snd))
sndStopSample(snd);
sndStartSample(snd, 255, 2, 0);
2019-09-19 22:42:45 +00:00
}
if (!key || pPlayer->hasKey[key])
trTriggerWall(a2, pXWall, kCmdWallPush);
2019-09-19 22:42:45 +00:00
else if (pPlayer == gMe)
{
viewSetMessage(GStrings("TXTB_KEY"));
auto snd = 3063;
if (sndCheckPlaying(snd))
sndStopSample(snd);
sndStartSample(snd, 255, 2, 0);
2019-09-19 22:42:45 +00:00
}
break;
}
case 3:
{
XSPRITE *pXSprite = &xsprite[a3];
int key = pXSprite->key;
if (pXSprite->locked && pPlayer == gMe && pXSprite->lockMsg)
trTextOver(pXSprite->lockMsg);
if (!key || pPlayer->hasKey[key])
trTriggerSprite(a2, pXSprite, kCmdSpritePush);
2019-09-19 22:42:45 +00:00
else if (pPlayer == gMe)
{
viewSetMessage(GStrings("TXTB_KEY"));
2019-09-19 22:42:45 +00:00
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)
2019-09-19 22:42:45 +00:00
{
2020-12-05 17:32:49 +00:00
auto pactor = &bloodActors[pPlayer->pSprite->index];
auto spawned = actSpawnDude(pactor, kDudeHand, pPlayer->pSprite->clipdist<<1, 0);
spritetype* pSprite2 = &spawned->s();
2019-09-19 22:42:45 +00:00
pSprite2->ang = (pPlayer->pSprite->ang+1024)&2047;
int nSprite = pPlayer->pSprite->index;
int x = bcos(pPlayer->pSprite->ang);
int y = bsin(pPlayer->pSprite->ang);
xvel[pSprite2->index] = xvel[nSprite] + MulScale(0x155555, x, 14);
yvel[pSprite2->index] = yvel[nSprite] + MulScale(0x155555, y, 14);
2019-09-19 22:42:45 +00:00
zvel[pSprite2->index] = zvel[nSprite];
pPlayer->hand = 0;
2019-09-19 22:42:45 +00:00
}
pInput->actions &= ~SB_OPEN;
2019-09-19 22:42:45 +00:00
}
if (SyncInput())
2019-09-19 22:42:45 +00:00
{
pPlayer->horizon.applyinput(pInput->horz, &pInput->actions);
doslopetilting(pPlayer);
2019-09-19 22:42:45 +00:00
}
// disable synchronised input and input locks if set by game.
resetForcedSyncInput();
pPlayer->angle.unlockinput();
pPlayer->horizon.unlockinput();
pPlayer->slope = -pPlayer->horizon.horiz.asq16() >> 9;
if (pInput->actions & SB_INVPREV)
2019-09-19 22:42:45 +00:00
{
pInput->actions&= ~SB_INVPREV;
2019-09-19 22:42:45 +00:00
packPrevItem(pPlayer);
}
if (pInput->actions & SB_INVNEXT)
2019-09-19 22:42:45 +00:00
{
pInput->actions &= ~SB_INVNEXT;
2019-09-19 22:42:45 +00:00
packNextItem(pPlayer);
}
if (pInput->actions & SB_INVUSE)
2019-09-19 22:42:45 +00:00
{
pInput->actions &= ~SB_INVUSE;
if (pPlayer->packSlots[pPlayer->packItemId].curAmount > 0)
packUseItem(pPlayer, pPlayer->packItemId);
2019-09-19 22:42:45 +00:00
}
if (pInput->isItemUsed(Item_BeastVision))
2019-09-19 22:42:45 +00:00
{
pInput->clearItemUsed(Item_BeastVision);
if (pPlayer->packSlots[3].curAmount > 0)
2019-09-19 22:42:45 +00:00
packUseItem(pPlayer, 3);
}
if (pInput->isItemUsed(Item_CrystalBall))
2019-09-19 22:42:45 +00:00
{
pInput->clearItemUsed(Item_CrystalBall);
if (pPlayer->packSlots[2].curAmount > 0)
2019-09-19 22:42:45 +00:00
packUseItem(pPlayer, 2);
}
if (pInput->isItemUsed(Item_JumpBoots))
2019-09-19 22:42:45 +00:00
{
pInput->clearItemUsed(Item_JumpBoots);
if (pPlayer->packSlots[4].curAmount > 0)
2019-09-19 22:42:45 +00:00
packUseItem(pPlayer, 4);
}
if (pInput->isItemUsed(Item_MedKit))
2019-09-19 22:42:45 +00:00
{
pInput->clearItemUsed(Item_MedKit);
if (pPlayer->packSlots[0].curAmount > 0)
2019-09-19 22:42:45 +00:00
packUseItem(pPlayer, 0);
}
if (pInput->actions & SB_HOLSTER)
2019-09-19 22:42:45 +00:00
{
pInput->actions &= ~SB_HOLSTER;
if (pPlayer->curWeapon)
2019-09-19 22:42:45 +00:00
{
WeaponLower(pPlayer);
viewSetMessage("Holstering weapon");
}
}
CheckPickUp(pPlayer);
}
void playerProcess(PLAYER *pPlayer)
{
auto actor = pPlayer->actor();
2019-09-19 22:42:45 +00:00
spritetype *pSprite = pPlayer->pSprite;
XSPRITE *pXSprite = pPlayer->pXSprite;
POSTURE* pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture];
2019-09-19 22:42:45 +00:00
powerupProcess(pPlayer);
int top, bottom;
GetSpriteExtents(pSprite, &top, &bottom);
int dzb = (bottom-pSprite->z)/4;
int dzt = (pSprite->z-top)/4;
int dw = pSprite->clipdist<<2;
if (!gNoClip)
{
int nSector = pSprite->sectnum;
if (pushmove(&pSprite->pos, &nSector, dw, dzt, dzb, CLIPMASK0) == -1)
actDamageSprite(actor, actor, kDamageFall, 500<<4);
2019-09-19 22:42:45 +00:00
if (pSprite->sectnum != nSector)
{
if (nSector == -1)
{
nSector = pSprite->sectnum;
actDamageSprite(actor, actor, kDamageFall, 500<<4);
2019-09-19 22:42:45 +00:00
}
assert(nSector >= 0 && nSector < kMaxSectors);
ChangeActorSect(actor, nSector);
2019-09-19 22:42:45 +00:00
}
}
ProcessInput(pPlayer);
int nSpeed = approxDist(actor->xvel(), actor->yvel());
pPlayer->zViewVel = interpolatedvalue(pPlayer->zViewVel, actor->zvel(), 0x7000);
int dz = pPlayer->pSprite->z-pPosture->eyeAboveZ-pPlayer->zView;
2019-09-19 22:42:45 +00:00
if (dz > 0)
pPlayer->zViewVel += MulScale(dz<<8, 0xa000, 16);
2019-09-19 22:42:45 +00:00
else
pPlayer->zViewVel += MulScale(dz<<8, 0x1800, 16);
pPlayer->zView += pPlayer->zViewVel>>8;
pPlayer->zWeaponVel = interpolatedvalue(pPlayer->zWeaponVel, actor->zvel(), 0x5000);
dz = pPlayer->pSprite->z-pPosture->weaponAboveZ-pPlayer->zWeapon;
2019-09-19 22:42:45 +00:00
if (dz > 0)
pPlayer->zWeaponVel += MulScale(dz<<8, 0x8000, 16);
2019-09-19 22:42:45 +00:00
else
pPlayer->zWeaponVel += MulScale(dz<<8, 0xc00, 16);
pPlayer->zWeapon += pPlayer->zWeaponVel>>8;
pPlayer->bobPhase = ClipLow(pPlayer->bobPhase-4, 0);
nSpeed >>= FRACBITS;
if (pPlayer->posture == 1)
2019-09-19 22:42:45 +00:00
{
pPlayer->bobAmp = (pPlayer->bobAmp+17)&2047;
pPlayer->swayAmp = (pPlayer->swayAmp+17)&2047;
pPlayer->bobHeight = MulScale(pPosture->bobV*10, Sin(pPlayer->bobAmp*2), 30);
pPlayer->bobWidth = MulScale(pPosture->bobH*pPlayer->bobPhase, Sin(pPlayer->bobAmp-256), 30);
pPlayer->swayHeight = MulScale(pPosture->swayV*pPlayer->bobPhase, Sin(pPlayer->swayAmp*2), 30);
pPlayer->swayWidth = MulScale(pPosture->swayH*pPlayer->bobPhase, Sin(pPlayer->swayAmp-0x155), 30);
2019-09-19 22:42:45 +00:00
}
else
{
if (pXSprite->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)
2019-09-19 22:42:45 +00:00
{
if (pPlayer->bobPhase < 60)
pPlayer->bobPhase = ClipHigh(pPlayer->bobPhase+nSpeed, 60);
2019-09-19 22:42:45 +00:00
}
else
{
if (pPlayer->bobPhase < 30)
pPlayer->bobPhase = ClipHigh(pPlayer->bobPhase+nSpeed, 30);
2019-09-19 22:42:45 +00:00
}
}
pPlayer->bobHeight = MulScale(pPosture->bobV*pPlayer->bobPhase, Sin(pPlayer->bobAmp*2), 30);
pPlayer->bobWidth = MulScale(pPosture->bobH*pPlayer->bobPhase, Sin(pPlayer->bobAmp-256), 30);
pPlayer->swayHeight = MulScale(pPosture->swayV*pPlayer->bobPhase, Sin(pPlayer->swayAmp*2), 30);
pPlayer->swayWidth = MulScale(pPosture->swayH*pPlayer->bobPhase, Sin(pPlayer->swayAmp-0x155), 30);
}
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);
2019-09-19 22:42:45 +00:00
if (pPlayer == gMe && gMe->pXSprite->health == 0)
pPlayer->hand = 0;
2019-09-19 22:42:45 +00:00
if (!pXSprite->health)
return;
pPlayer->isUnderwater = 0;
if (pPlayer->posture == 1)
2019-09-19 22:42:45 +00:00
{
pPlayer->isUnderwater = 1;
2019-09-19 22:42:45 +00:00
int nSector = pSprite->sectnum;
int nLink = gLowerLink[nSector];
if (nLink > 0 && (sprite[nLink].type == kMarkerLowGoo || sprite[nLink].type == kMarkerLowWater))
2019-09-19 22:42:45 +00:00
{
if (getceilzofslope(nSector, pSprite->x, pSprite->y) > pPlayer->zView)
pPlayer->isUnderwater = 0;
2019-09-19 22:42:45 +00:00
}
}
if (!pPlayer->isUnderwater)
2019-09-19 22:42:45 +00:00
{
pPlayer->underwaterTime = 1200;
pPlayer->chokeEffect = 0;
2019-09-19 22:42:45 +00:00
if (packItemActive(pPlayer, 1))
packUseItem(pPlayer, 1);
}
int nType = kDudePlayer1-kDudeBase;
switch (pPlayer->posture)
2019-09-19 22:42:45 +00:00
{
case 1:
seqSpawn(dudeInfo[nType].seqStartID+9, actor, -1);
2019-09-19 22:42:45 +00:00
break;
case 2:
seqSpawn(dudeInfo[nType].seqStartID+10, actor, -1);
2019-09-19 22:42:45 +00:00
break;
default:
if (!nSpeed)
seqSpawn(dudeInfo[nType].seqStartID, actor, -1);
2019-09-19 22:42:45 +00:00
else
seqSpawn(dudeInfo[nType].seqStartID+8, actor, -1);
2019-09-19 22:42:45 +00:00
break;
}
}
spritetype *playerFireMissile(PLAYER *pPlayer, int a2, int a3, int a4, int a5, int a6)
{
2020-12-05 20:23:53 +00:00
auto misl = actFireMissile(&bloodActors[pPlayer->pSprite->index], a2, pPlayer->zWeapon-pPlayer->pSprite->z, a3, a4, a5, a6);
return misl ? &misl->s() : nullptr;
2019-09-19 22:42:45 +00:00
}
spritetype * playerFireThing(PLAYER *pPlayer, int a2, int a3, int thingType, int a5)
{
assert(thingType >= kThingBase && thingType < kThingMax);
2020-12-05 20:23:53 +00:00
auto misl = actFireThing(&bloodActors[pPlayer->pSprite->index], a2, pPlayer->zWeapon-pPlayer->pSprite->z, pPlayer->slope+a3, thingType, a5);
return misl ? &misl->s() : nullptr;
2019-09-19 22:42:45 +00:00
}
void playerFrag(PLAYER *pKiller, PLAYER *pVictim)
{
assert(pKiller != NULL);
assert(pVictim != NULL);
2019-09-19 22:42:45 +00:00
char buffer[128] = "";
int nKiller = pKiller->pSprite->type-kDudePlayer1;
assert(nKiller >= 0 && nKiller < kMaxPlayers);
2019-09-19 22:42:45 +00:00
int nVictim = pVictim->pSprite->type-kDudePlayer1;
assert(nVictim >= 0 && nVictim < kMaxPlayers);
2019-09-19 22:42:45 +00:00
if (nKiller == nVictim)
{
pVictim->fraggerId = -1;
2019-09-21 09:54:56 +00:00
if (VanillaMode() || gGameOptions.nGameType != 1)
{
pVictim->fragCount--;
pVictim->fragInfo[nVictim]--;
2019-09-21 09:54:56 +00:00
}
2019-09-19 22:42:45 +00:00
if (gGameOptions.nGameType == 3)
team_score[pVictim->teamId]--;
2019-09-19 22:42:45 +00:00
int nMessage = Random(5);
int nSound = gSuicide[nMessage].Kills;
if (pVictim == gMe && gMe->handTime <= 0)
2019-09-19 22:42:45 +00:00
{
strcpy(buffer, GStrings("TXTB_KILLSELF"));
2019-09-19 22:42:45 +00:00
if (gGameOptions.nGameType > 0 && nSound >= 0)
sndStartSample(nSound, 255, 2, 0);
}
else
{
sprintf(buffer, gSuicide[nMessage].message, PlayerName(nVictim));
}
2019-09-19 22:42:45 +00:00
}
else
{
2019-09-21 09:54:56 +00:00
if (VanillaMode() || gGameOptions.nGameType != 1)
{
pKiller->fragCount++;
pKiller->fragInfo[nKiller]++;
2019-09-21 09:54:56 +00:00
}
2019-09-19 22:42:45 +00:00
if (gGameOptions.nGameType == 3)
{
if (pKiller->teamId == pVictim->teamId)
team_score[pKiller->teamId]--;
2019-09-19 22:42:45 +00:00
else
{
team_score[pKiller->teamId]++;
team_ticker[pKiller->teamId]+=120;
2019-09-19 22:42:45 +00:00
}
}
int nMessage = Random(25);
int nSound = gVictory[nMessage].Kills;
2020-11-21 22:40:08 +00:00
const char* pzMessage = gVictory[nMessage].message;
sprintf(buffer, pzMessage, PlayerName(nKiller), PlayerName(nVictim));
2019-09-19 22:42:45 +00:00
if (gGameOptions.nGameType > 0 && nSound >= 0 && pKiller == gMe)
sndStartSample(nSound, 255, 2, 0);
}
viewSetMessage(buffer);
}
void FragPlayer(PLAYER *pPlayer, int nSprite)
{
spritetype *pSprite = NULL;
if (nSprite >= 0)
pSprite = &sprite[nSprite];
if (pSprite && IsPlayerSprite(pSprite))
{
PLAYER *pKiller = &gPlayer[pSprite->type - kDudePlayer1];
2019-09-19 22:42:45 +00:00
playerFrag(pKiller, pPlayer);
int nTeam1 = pKiller->teamId&1;
int nTeam2 = pPlayer->teamId&1;
2019-09-19 22:42:45 +00:00
if (nTeam1 == 0)
{
if (nTeam1 != nTeam2)
evSendGame(15, kCmdToggle);
2019-09-19 22:42:45 +00:00
else
evSendGame(16, kCmdToggle);
2019-09-19 22:42:45 +00:00
}
else
{
if (nTeam1 == nTeam2)
evSendGame(16, kCmdToggle);
2019-09-19 22:42:45 +00:00
else
evSendGame(15, kCmdToggle);
2019-09-19 22:42:45 +00:00
}
}
}
int playerDamageArmor(PLAYER *pPlayer, DAMAGE_TYPE nType, int nDamage)
{
DAMAGEINFO *pDamageInfo = &damageInfo[nType];
2020-11-21 22:40:08 +00:00
int nArmorType = pDamageInfo->armorType;
if (nArmorType >= 0 && pPlayer->armor[nArmorType])
2019-09-19 22:42:45 +00:00
{
#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];
2019-09-19 22:42:45 +00:00
int t = scale(v8, 0, 3200, nDamage/4, (nDamage*7)/8);
v8 -= t;
nDamage -= t;
pPlayer->armor[nArmorType] = ClipLow(v8, 0);
2019-09-19 22:42:45 +00:00
}
return nDamage;
}
2020-11-21 19:10:45 +00:00
spritetype *flagDropped(PLAYER *pPlayer, int a2)
2019-09-19 22:42:45 +00:00
{
auto actor = pPlayer->actor();
2019-09-19 22:42:45 +00:00
char buffer[80];
spritetype *pSprite = NULL;
switch (a2)
{
case kItemFlagA:
pPlayer->hasFlag &= ~1;
pSprite = &actDropObject(actor, kItemFlagA)->s();
2019-09-19 22:42:45 +00:00
if (pSprite)
pSprite->owner = pPlayer->used2[0];
gBlueFlagDropped = true;
sprintf(buffer, "%s dropped Blue Flag", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8005, 255, 2, 0);
viewSetMessage(buffer);
break;
case kItemFlagB:
pPlayer->hasFlag &= ~2;
pSprite = &actDropObject(actor, kItemFlagB)->s();
2019-09-19 22:42:45 +00:00
if (pSprite)
pSprite->owner = pPlayer->used2[1];
gRedFlagDropped = true;
sprintf(buffer, "%s dropped Red Flag", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
sndStartSample(8004, 255, 2, 0);
viewSetMessage(buffer);
break;
}
return pSprite;
}
2020-12-02 23:30:19 +00:00
int playerDamageSprite(DBloodActor* source, PLAYER *pPlayer, DAMAGE_TYPE nDamageType, int nDamage)
2019-09-19 22:42:45 +00:00
{
2020-12-02 23:30:19 +00:00
int nSource = source ? source->s().index : -1;
assert(pPlayer != NULL);
if (pPlayer->damageControl[nDamageType] || pPlayer->godMode)
2019-09-19 22:42:45 +00:00
return 0;
nDamage = playerDamageArmor(pPlayer, nDamageType, nDamage);
pPlayer->painEffect = ClipHigh(pPlayer->painEffect+(nDamage>>3), 600);
2019-09-19 22:42:45 +00:00
spritetype *pSprite = pPlayer->pSprite;
XSPRITE *pXSprite = pPlayer->pXSprite;
auto pActor = pPlayer->actor();
2019-09-19 22:42:45 +00:00
int nXSprite = pSprite->extra;
int nXSector = sector[pSprite->sectnum].extra;
DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
int nDeathSeqID = -1;
int nKneelingPlayer = -1;
2019-09-19 22:42:45 +00:00
int nSprite = pSprite->index;
char va = playerSeqPlaying(pPlayer, 16);
if (!pXSprite->health)
{
if (va)
{
switch (nDamageType)
{
case kDamageSpirit:
2019-09-19 22:42:45 +00:00
nDeathSeqID = 18;
sfxPlay3DSound(pSprite, 716, 0, 0);
break;
case kDamageExplode:
2021-08-29 20:22:16 +00:00
GibSprite(pActor, GIBTYPE_7, NULL, NULL);
GibSprite(pActor, GIBTYPE_15, NULL, NULL);
2019-09-19 22:42:45 +00:00
pPlayer->pSprite->cstat |= 32768;
nDeathSeqID = 17;
break;
default:
{
int top, bottom;
GetSpriteExtents(pSprite, &top, &bottom);
CGibPosition gibPos(pSprite->x, pSprite->y, top);
CGibVelocity gibVel(xvel[pSprite->index]>>1, yvel[pSprite->index]>>1, -0xccccc);
2021-08-29 20:22:16 +00:00
GibSprite(pActor, GIBTYPE_27, &gibPos, &gibVel);
GibSprite(pActor, GIBTYPE_7, NULL, NULL);
2021-08-26 22:07:33 +00:00
fxSpawnBlood(pActor, nDamage<<4);
fxSpawnBlood(pActor, nDamage<<4);
2019-09-19 22:42:45 +00:00
nDeathSeqID = 17;
break;
}
}
}
}
else
{
int nHealth = pXSprite->health-nDamage;
pXSprite->health = ClipLow(nHealth, 0);
if (pXSprite->health > 0 && pXSprite->health < 16)
{
nDamageType = kDamageBullet;
2019-09-19 22:42:45 +00:00
pXSprite->health = 0;
nHealth = -25;
}
if (pXSprite->health > 0)
{
DAMAGEINFO *pDamageInfo = &damageInfo[nDamageType];
int nSound;
if (nDamage >= (10<<4))
nSound = pDamageInfo->Kills[0];
2019-09-19 22:42:45 +00:00
else
nSound = pDamageInfo->Kills[Random(3)];
if (nDamageType == kDamageDrown && pXSprite->medium == kMediumWater && !pPlayer->hand)
2019-09-19 22:42:45 +00:00
nSound = 714;
sfxPlay3DSound(pSprite, nSound, 0, 6);
return nDamage;
}
sfxKill3DSound(pPlayer->pSprite, -1, 441);
if (gGameOptions.nGameType == 3 && pPlayer->hasFlag) {
2020-11-21 19:10:45 +00:00
if (pPlayer->hasFlag&1) flagDropped(pPlayer, kItemFlagA);
if (pPlayer->hasFlag&2) flagDropped(pPlayer, kItemFlagB);
2019-09-19 22:42:45 +00:00
}
pPlayer->deathTime = 0;
pPlayer->qavLoop = 0;
pPlayer->curWeapon = kWeapNone;
pPlayer->fraggerId = nSource;
pPlayer->voodooTargets = 0;
if (nDamageType == kDamageExplode && nDamage < (9<<4))
nDamageType = kDamageFall;
2019-09-19 22:42:45 +00:00
switch (nDamageType)
{
case kDamageExplode:
2019-09-19 22:42:45 +00:00
sfxPlay3DSound(pSprite, 717, 0, 0);
2021-08-29 20:22:16 +00:00
GibSprite(pActor, GIBTYPE_7, NULL, NULL);
GibSprite(pActor, GIBTYPE_15, NULL, NULL);
2019-09-19 22:42:45 +00:00
pPlayer->pSprite->cstat |= 32768;
nDeathSeqID = 2;
break;
case kDamageBurn:
2019-09-19 22:42:45 +00:00
sfxPlay3DSound(pSprite, 718, 0, 0);
nDeathSeqID = 3;
break;
case kDamageDrown:
2019-09-19 22:42:45 +00:00
nDeathSeqID = 1;
break;
default:
if (nHealth < -20 && gGameOptions.nGameType >= 2 && Chance(0x4000))
{
DAMAGEINFO *pDamageInfo = &damageInfo[nDamageType];
sfxPlay3DSound(pSprite, pDamageInfo->at10[0], 0, 2);
nDeathSeqID = 16;
nKneelingPlayer = nPlayerKneelClient;
powerupActivate(pPlayer, kPwUpDeliriumShroom);
pActor->SetTarget(source);
evPostActor(&bloodActors[pSprite->index], 15, kCallbackFinishHim);
2019-09-19 22:42:45 +00:00
}
else
{
sfxPlay3DSound(pSprite, 716, 0, 0);
nDeathSeqID = 1;
}
break;
}
}
if (nDeathSeqID < 0)
return nDamage;
if (nDeathSeqID != 16)
{
powerupClear(pPlayer);
if (nXSector > 0 && xsector[nXSector].Exit)
trTriggerSector(pSprite->sectnum, &xsector[nXSector], kCmdSectorExit);
pSprite->flags |= 7;
2019-09-19 22:42:45 +00:00
for (int p = connecthead; p >= 0; p = connectpoint2[p])
{
if (gPlayer[p].fraggerId == nSprite && gPlayer[p].deathTime > 0)
gPlayer[p].fraggerId = -1;
2019-09-19 22:42:45 +00:00
}
FragPlayer(pPlayer, nSource);
trTriggerSprite(nSprite, pXSprite, kCmdOff);
2020-12-06 20:56:09 +00:00
#ifdef NOONE_EXTENSIONS
// allow drop items and keys in multiplayer
if (gModernMap && gGameOptions.nGameType != 0 && pPlayer->pXSprite->health <= 0) {
2021-06-02 19:00:39 +00:00
DBloodActor* pItem = nullptr;
if (pPlayer->pXSprite->dropMsg && (pItem = actDropItem(pActor, pPlayer->pXSprite->dropMsg)) != NULL)
evPostActor(pItem, 500, kCallbackRemove);
2020-12-06 20:56:09 +00:00
if (pPlayer->pXSprite->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->pXSprite->key])
break;
}
2021-06-02 19:00:39 +00:00
if (i == 0 && (pItem = actDropKey(pActor, (pPlayer->pXSprite->key + kItemKeyBase) - 1)) != NULL)
evPostActor(pItem, 500, kCallbackRemove);
2020-12-06 20:56:09 +00:00
}
}
#endif
2019-09-19 22:42:45 +00:00
}
assert(getSequence(pDudeInfo->seqStartID + nDeathSeqID) != NULL);
seqSpawn(pDudeInfo->seqStartID+nDeathSeqID, 3, nXSprite, nKneelingPlayer);
2019-09-19 22:42:45 +00:00
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];
2019-09-19 22:42:45 +00:00
}
2020-11-21 19:10:45 +00:00
void voodooTarget(PLAYER *pPlayer)
2019-09-19 22:42:45 +00:00
{
2021-08-30 18:28:24 +00:00
auto actor = &bloodActors[pPlayer->pSprite->index];
int v4 = pPlayer->aim.dz;
int dz = pPlayer->zWeapon-pPlayer->pSprite->z;
2019-09-19 22:42:45 +00:00
if (UseAmmo(pPlayer, 9, 0) < 8)
{
pPlayer->voodooTargets = 0;
2019-09-19 22:42:45 +00:00
return;
}
for (int i = 0; i < 4; i++)
{
int ang1 = (pPlayer->voodooVar1+pPlayer->vodooVar2)&2047;
actFireVector(actor, 0, dz, bcos(ang1), bsin(ang1), v4, kVectorVoodoo10);
int ang2 = (pPlayer->voodooVar1+2048-pPlayer->vodooVar2)&2047;
actFireVector(actor, 0, dz, bcos(ang2), bsin(ang2), v4, kVectorVoodoo10);
2019-09-19 22:42:45 +00:00
}
pPlayer->voodooTargets = ClipLow(pPlayer->voodooTargets-1, 0);
2019-09-19 22:42:45 +00:00
}
void playerLandingSound(PLAYER *pPlayer)
{
static int surfaceSound[] = {
-1,
600,
601,
602,
603,
604,
605,
605,
605,
600,
605,
605,
605,
604,
603
};
spritetype *pSprite = pPlayer->pSprite;
2021-08-29 17:09:29 +00:00
SPRITEHIT* pHit = &pPlayer->actor()->hit;
if (pHit->florhit.type != kHitNone)
2019-09-19 22:42:45 +00:00
{
if (!gGameOptions.bFriendlyFire && pHit->florhit.type == kHitSprite && IsTargetTeammate(pPlayer, &pHit->florhit.actor->s()))
2019-09-22 07:53:42 +00:00
return;
2021-08-27 19:49:18 +00:00
int nSurf = tileGetSurfType(pHit->florhit);
2019-09-19 22:42:45 +00:00
if (nSurf)
sfxPlay3DSound(pSprite, surfaceSound[nSurf], -1, 0);
}
}
void PlayerSurvive(int, DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
XSPRITE* pXSprite = &actor->x();
spritetype* pSprite = &actor->s();
2019-09-19 22:42:45 +00:00
char buffer[80];
actHealDude(actor, 1, 2);
2019-09-19 22:42:45 +00:00
if (gGameOptions.nGameType > 0 && numplayers > 1)
{
sfxPlay3DSound(pSprite, 3009, 0, 6);
if (IsPlayerSprite(pSprite))
{
PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
if (pPlayer == gMe)
viewSetMessage(GStrings("TXT_LIVEAGAIM"));
2019-09-19 22:42:45 +00:00
else
{
sprintf(buffer, "%s lives again!", PlayerName(pPlayer->nPlayer));
2019-09-19 22:42:45 +00:00
viewSetMessage(buffer);
}
pPlayer->newWeapon = kWeapPitchFork;
2019-09-19 22:42:45 +00:00
}
}
}
void PlayerKneelsOver(int, DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
XSPRITE* pXSprite = &actor->x();
2019-09-19 22:42:45 +00:00
for (int p = connecthead; p >= 0; p = connectpoint2[p])
{
if (gPlayer[p].pXSprite == pXSprite)
{
PLAYER *pPlayer = &gPlayer[p];
playerDamageSprite(pPlayer->fragger(), pPlayer, kDamageSpirit, 500<<4);
2019-09-19 22:42:45 +00:00
return;
}
}
}
2020-11-22 13:24:55 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
FSerializer& Serialize(FSerializer& arc, const char* keyname, Aim& w, Aim* def)
2019-09-19 22:42:45 +00:00
{
2020-11-22 13:24:55 +00:00
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;
}
2019-09-19 22:42:45 +00:00
2020-11-22 13:24:55 +00:00
FSerializer& Serialize(FSerializer& arc, const char* keyname, POSTURE& w, POSTURE* def)
2019-09-19 22:42:45 +00:00
{
2020-11-22 13:24:55 +00:00
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;
}
2020-11-22 13:24:55 +00:00
FSerializer& Serialize(FSerializer& arc, const char* keyname, PLAYER& w, PLAYER* def)
{
if (arc.isReading()) playerResetPosture(&w);
if (arc.BeginObject(keyname))
{
arc("spritenum", w.nSprite)
("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("used2", w.used2, 8)
.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][0], 14*2)
.Array("ammocount", w.ammoCount, countof(w.ammoCount))
("qavloop", w.qavLoop)
("qavlastTick", w.qavLastTick)
("qavtimer", w.qavTimer)
2020-11-22 13:24:55 +00:00
("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.fraggerId)
("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.
2020-11-22 13:24:55 +00:00
.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)
2020-11-22 13:24:55 +00:00
("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].pSprite = &sprite[gPlayer[i].nSprite];
gPlayer[i].pXSprite = &xsprite[gPlayer[i].pSprite->extra];
gPlayer[i].pDudeInfo = &dudeInfo[gPlayer[i].pSprite->type - kDudeBase];
#ifdef NOONE_EXTENSIONS
// load qav scene
if (gPlayer[i].sceneQav != -1)
{
QAV* pQav = playerQavSceneLoad(gPlayer[i].sceneQav);
2020-11-22 13:24:55 +00:00
if (pQav)
{
gPlayerCtrl[i].qavScene.qavResrc = pQav;
//gPlayerCtrl[i].qavScene.qavResrc->Preload();
2020-11-22 13:24:55 +00:00
}
else
{
gPlayer[i].sceneQav = -1;
}
}
2020-11-22 13:24:55 +00:00
#endif
}
2019-09-19 22:42:45 +00:00
}
}
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, nSprite)
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