player.cpp
1 //------------------------------------------------------------------------- 2 /* 3 Copyright (C) 2010-2019 EDuke32 developers and contributors 4 Copyright (C) 2019 Nuke.YKT 5 6 This file is part of NBlood. 7 8 NBlood is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License version 2 10 as published by the Free Software Foundation. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 16 See the GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 */ 22 //------------------------------------------------------------------------- 23 #include <stdlib.h> 24 #include <string.h> 25 #include "compat.h" 26 #include "build.h" 27 #include "mmulti.h" 28 #include "actor.h" 29 #include "blood.h" 30 #include "callback.h" 31 #include "config.h" 32 #include "controls.h" 33 #include "demo.h" 34 #include "eventq.h" 35 #include "fx.h" 36 #include "gib.h" 37 #include "globals.h" 38 #include "levels.h" 39 #include "loadsave.h" 40 #include "map2d.h" 41 #include "network.h" 42 #include "player.h" 43 #include "seq.h" 44 #include "sfx.h" 45 #include "sound.h" 46 #include "tile.h" 47 #include "triggers.h" 48 #include "trig.h" 49 #include "view.h" 50 #include "warp.h" 51 #include "weapon.h" 52 #include "common_game.h" 53 #include "messages.h" 54 #ifdef NOONE_EXTENSIONS 55 #include "nnexts.h" 56 #include "nnextsif.h" 57 #endif 58 59 PLAYER gPlayer[kMaxPlayers]; 60 PLAYER *gMe, *gView; 61 62 PROFILE gProfile[kMaxPlayers]; 63 PROFILE gProfileNet[kMaxPlayers]; 64 65 bool gBlueFlagDropped = false; 66 bool gRedFlagDropped = false; 67 68 int gPlayerScores[kMaxPlayers]; 69 int gPlayerCoopLives[kMaxPlayers]; 70 ClockTicks gPlayerScoreTicks[kMaxPlayers]; 71 72 int gPlayerRoundScoreLimit = 0; 73 int gPlayerRoundTimeLimit = 0; 74 char gPlayerRoundEnding = 0; 75 static char gPlayerRoundLimitAnnounce = -1; 76 77 int gPlayerLastKiller; 78 int gPlayerLastVictim; 79 ClockTicks gPlayerKillMsgTicks; 80 81 int gMultiKillsFrags[kMaxPlayers]; 82 ClockTicks gMultiKillsTicks[kMaxPlayers]; 83 84 int gAnnounceKillingSpreePlayer; 85 ClockTicks gAnnounceKillingSpreeTicks; 86 87 static char gDominationCount[kMaxPlayers][kMaxPlayers]; 88 89 int gPlayerSpeed = 0; 90 91 // V = has effect in game, X = no effect in game 92 POWERUPINFO gPowerUpInfo[kMaxPowerUps] = { 93 { -1, 1, 1, 1 }, // 00: V keys 94 { -1, 1, 1, 1 }, // 01: V keys 95 { -1, 1, 1, 1 }, // 02: V keys 96 { -1, 1, 1, 1 }, // 03: V keys 97 { -1, 1, 1, 1 }, // 04: V keys 98 { -1, 1, 1, 1 }, // 05: V keys 99 { -1, 1, 1, 1 }, // 06: V keys 100 { -1, 0, 100, 100 }, // 07: V doctor's bag 101 { -1, 0, 50, 100 }, // 08: V medicine pouch 102 { -1, 0, 20, 100 }, // 09: V life essense 103 { -1, 0, 100, 200 }, // 10: V life seed 104 { -1, 0, 2, 200 }, // 11: V red potion 105 { 783, 0, 3600, 432000 }, // 12: V feather fall 106 { 896, 0, 3600, 432000 }, // 13: V cloak of invisibility 107 { 825, 1, 3600, 432000 }, // 14: V death mask (invulnerability) 108 { 827, 0, 3600, 432000 }, // 15: V jump boots 109 { 828, 0, 3600, 432000 }, // 16: X raven flight 110 { 829, 0, 3600, 1728000 }, // 17: V guns akimbo/quad damage 111 { 830, 0, 3600, 432000 }, // 18: V diving suit 112 { 831, 0, 3600, 432000 }, // 19: V gas mask 113 { -1, 0, 3600, 432000 }, // 20: X clone 114 { 2566, 0, 3600, 432000 }, // 21: V crystal ball 115 { 836, 0, 3600, 432000 }, // 22: X decoy 116 { 853, 0, 3600, 432000 }, // 23: V doppleganger 117 { 2428, 0, 3600, 432000 }, // 24: V reflective shots 118 { 839, 0, 3600, 432000 }, // 25: V beast vision 119 { 768, 0, 3600, 432000 }, // 26: X cloak of shadow (useless) 120 { 840, 0, 3600, 432000 }, // 27: X rage shroom 121 { 841, 0, 900, 432000 }, // 28: V delirium shroom 122 { 842, 0, 3600, 432000 }, // 29: V grow shroom (gModernMap only) 123 { 843, 0, 3600, 432000 }, // 30: V shrink shroom (gModernMap only) 124 { -1, 0, 3600, 432000 }, // 31: X death mask (useless) 125 { -1, 0, 3600, 432000 }, // 32: X wine goblet 126 { -1, 0, 3600, 432000 }, // 33: X wine bottle 127 { -1, 0, 3600, 432000 }, // 34: X skull grail 128 { -1, 0, 3600, 432000 }, // 35: X silver grail 129 { -1, 0, 3600, 432000 }, // 36: X tome 130 { -1, 0, 3600, 432000 }, // 37: X black chest 131 { -1, 0, 3600, 432000 }, // 38: X wooden chest 132 { 837, 1, 3600, 432000 }, // 39: V asbestos armor 133 { -1, 0, 1, 432000 }, // 40: V basic armor 134 { -1, 0, 1, 432000 }, // 41: V body armor 135 { -1, 0, 1, 432000 }, // 42: V fire armor 136 { -1, 0, 1, 432000 }, // 43: V spirit armor 137 { -1, 0, 1, 432000 }, // 44: V super armor 138 { 0, 0, 0, 0 }, // 45: ? unknown 139 { 0, 0, 0, 0 }, // 46: ? unknown 140 { 0, 0, 0, 0 }, // 47: ? unknown 141 { 0, 0, 0, 0 }, // 48: ? unknown 142 { 0, 0, 0, 0 }, // 49: X dummy 143 { 833, 1, 1, 1 } // 50: V kModernItemLevelMap (gModernMap only) 144 }; 145 146 int Handicap[] = { 147 144, 208, 256, 304, 368 148 }; 149 150 POSTURE gPostureDefaults[kModeMax][kPostureMax] = { 151 152 // normal human 153 { 154 { 0x4000, 0x4000, 0x4000, 14, 17, 24, 16, 32, 80, 0x1600, 0x1200, 0xc00, 0x90, -0xbaaaa, -0x175555 }, 155 { 0x1200, 0x1200, 0x1200, 14, 17, 24, 16, 32, 80, 0x1400, 0x1000, -0x600, 0xb0, 0x5b05, 0 }, 156 { 0x2000, 0x2000, 0x2000, 22, 28, 24, 16, 16, 40, 0x800, 0x600, -0x600, 0xb0, 0, 0 }, 157 }, 158 159 // normal beast 160 { 161 { 0x4000, 0x4000, 0x4000, 14, 17, 24, 16, 32, 80, 0x1600, 0x1200, 0xc00, 0x90, -0xbaaaa, -0x175555 }, 162 { 0x1200, 0x1200, 0x1200, 14, 17, 24, 16, 32, 80, 0x1400, 0x1000, -0x600, 0xb0, 0x5b05, 0 }, 163 { 0x2000, 0x2000, 0x2000, 22, 28, 24, 16, 16, 40, 0x800, 0x600, -0x600, 0xb0, 0, 0 }, 164 }, 165 166 // shrink human 167 { 168 { 10384, 10384, 10384, 14, 17, 24, 16, 32, 80, 5632, 4608, 3072, 144, -564586, -1329173 }, 169 { 2108, 2108, 2108, 14, 17, 24, 16, 32, 80, 5120, 4096, -1536, 176, 0x5b05, 0 }, 170 { 2192, 2192, 2192, 22, 28, 24, 16, 16, 40, 2048, 1536, -1536, 176, 0, 0 }, 171 }, 172 173 // grown human 174 { 175 { 19384, 19384, 19384, 14, 17, 24, 16, 32, 80, 5632, 4608, 3072, 144, -1014586, -1779173 }, 176 { 5608, 5608, 5608, 14, 17, 24, 16, 32, 80, 5120, 4096, -1536, 176, 0x5b05, 0 }, 177 { 11192, 11192, 11192, 22, 28, 24, 16, 16, 40, 2048, 1536, -1536, 176, 0, 0 }, 178 }, 179 }; 180 181 AMMOINFO gAmmoInfo[] = { 182 { 0, -1 }, 183 { 100, -1 }, 184 { 100, 4 }, 185 { 500, 5 }, 186 { 100, -1 }, 187 { 50, -1 }, 188 { 2880, -1 }, 189 { 250, -1 }, 190 { 100, -1 }, 191 { 100, -1 }, 192 { 50, -1 }, 193 { 50, -1 }, 194 }; 195 196 struct ARMORDATA { 197 int at0; 198 int at4; 199 int at8; 200 int atc; 201 int at10; 202 int at14; 203 }; 204 ARMORDATA armorData[5] = { 205 { 0x320, 0x640, 0x320, 0x640, 0x320, 0x640 }, 206 { 0x640, 0x640, 0, 0x640, 0, 0x640 }, 207 { 0, 0x640, 0x640, 0x640, 0, 0x640 }, 208 { 0, 0x640, 0, 0x640, 0x640, 0x640 }, 209 { 0xc80, 0xc80, 0xc80, 0xc80, 0xc80, 0xc80 } 210 }; 211 212 void PlayerSurvive(int, int); 213 void PlayerKneelsOver(int, int); 214 215 int nPlayerSurviveClient = seqRegisterClient(PlayerSurvive); 216 int nPlayerKneelClient = seqRegisterClient(PlayerKneelsOver); 217 218 struct KILLMSG { 219 const char *pzMessage; 220 int nSound; 221 }; 222 223 KILLMSG gVictory[] = { 224 { "\r%s\r boned \r%s\r like a fish", 4100 }, 225 { "\r%s\r castrated \r%s\r", 4101 }, 226 { "\r%s\r creamed \r%s\r", 4102 }, 227 { "\r%s\r destroyed \r%s\r", 4103 }, 228 { "\r%s\r diced \r%s\r", 4104 }, 229 { "\r%s\r disemboweled \r%s\r", 4105 }, 230 { "\r%s\r flattened \r%s\r", 4106 }, 231 { "\r%s\r gave \r%s\r Anal Justice", 4107 }, 232 { "\r%s\r gave AnAl MaDnEsS to \r%s\r", 4108 }, 233 { "\r%s\r hurt \r%s\r real bad", 4109 }, 234 { "\r%s\r killed \r%s\r", 4110 }, 235 { "\r%s\r made mincemeat out of \r%s\r", 4111 }, 236 { "\r%s\r massacred \r%s\r", 4112 }, 237 { "\r%s\r mutilated \r%s\r", 4113 }, 238 { "\r%s\r reamed \r%s\r", 4114 }, 239 { "\r%s\r ripped \r%s\r a new orifice", 4115 }, 240 { "\r%s\r slaughtered \r%s\r", 4116 }, 241 { "\r%s\r sliced \r%s\r", 4117 }, 242 { "\r%s\r smashed \r%s\r", 4118 }, 243 { "\r%s\r sodomized \r%s\r", 4119 }, 244 { "\r%s\r splattered \r%s\r", 4120 }, 245 { "\r%s\r squashed \r%s\r", 4121 }, 246 { "\r%s\r throttled \r%s\r", 4122 }, 247 { "\r%s\r wasted \r%s\r", 4123 }, 248 { "\r%s\r body bagged \r%s\r", 4124 }, 249 }; 250 251 KILLMSG gSuicide[] = { 252 { "\r%s\r is excrement", 4202 }, 253 { "\r%s\r is hamburger", 4203 }, 254 { "\r%s\r suffered scrotum separation", 4204 }, 255 { "\r%s\r volunteered for population control", 4206 }, 256 { "\r%s\r has suicided", 4207 }, 257 }; 258 259 KILLMSG gKillingSpreeFrag = {"\r%s\r put a stop to \r%s\r's killing spree", 4110}; 260 261 KILLMSG gKillingSpreeSuicide = {"\r%s\r was looking good until they suicided", 4207}; 262 263 struct DAMAGEINFO { 264 int at0; 265 int at4[3]; 266 int at10[3]; 267 }; 268 269 DAMAGEINFO damageInfo[kDamageMax] = { 270 { -1, 731, 732, 733, 710, 710, 710 }, 271 { 1, 742, 743, 744, 711, 711, 711 }, 272 { 0, 731, 732, 733, 712, 712, 712 }, 273 { 1, 731, 732, 733, 713, 713, 713 }, 274 { -1, 724, 724, 724, 714, 714, 714 }, 275 { 2, 731, 732, 733, 715, 715, 715 }, 276 { 0, 0, 0, 0, 0, 0, 0 } 277 }; 278 279 uint32_t PLAYER::CalcNonSpriteChecksum(void) 280 { 281 // This was originally written to calculate the checksum 282 // the way OUWB v1.21 does. Therefore, certain bits may 283 // be skipped or calculated in a different order. 284 int i; 285 uint32_t sum = 0; 286 sum += used1&0xFFFFFFFF; 287 sum += weaponQav&0xFFFFFFFF; 288 sum += qavCallback&0xFFFFFFFF; 289 sum += (!!isRunning) | ((posture&0xFFFFFF)<<8); 290 sum += ((posture>>24)&255) | ((sceneQav&0xFFFFFF)<<8); 291 sum += ((sceneQav>>24)&255) | ((bobPhase&0xFFFFFF)<<8); 292 sum += ((bobPhase>>24)&255) | ((bobAmp&0xFFFFFF)<<8); 293 sum += ((bobAmp>>24)&255) | ((bobHeight&0xFFFFFF)<<8); 294 sum += ((bobHeight>>24)&255) | ((bobWidth&0xFFFFFF)<<8); 295 sum += ((bobWidth>>24)&255) | ((swayPhase&0xFFFFFF)<<8); 296 sum += ((swayPhase>>24)&255) | ((swayAmp&0xFFFFFF)<<8); 297 sum += ((swayAmp>>24)&255) | ((swayHeight&0xFFFFFF)<<8); 298 sum += ((swayHeight>>24)&255) | ((swayWidth&0xFFFFFF)<<8); 299 sum += ((swayWidth>>24)&255) | ((nPlayer&0xFFFFFF)<<8); 300 sum += ((nPlayer>>24)&255) | ((nSprite&0xFFFFFF)<<8); 301 sum += ((nSprite>>24)&255) | ((lifeMode&0xFFFFFF)<<8); 302 sum += ((lifeMode>>24)&255) | ((bloodlust&0xFFFFFF)<<8); 303 sum += ((bloodlust>>24)&255) | ((zView&0xFFFFFF)<<8); 304 sum += ((zView>>24)&255) | ((zViewVel&0xFFFFFF)<<8); 305 sum += ((zViewVel>>24)&255) | ((zWeapon&0xFFFFFF)<<8); 306 sum += ((zWeapon>>24)&255) | ((zWeaponVel&0xFFFFFF)<<8); 307 308 int32_t look = fix16_to_int(q16look), horiz = fix16_to_int(q16horiz), 309 slopehoriz = fix16_to_int(q16slopehoriz); 310 311 sum += ((zWeaponVel>>24)&255) | ((look&0xFFFFFF)<<8); 312 sum += ((look>>24)&255) | ((horiz&0xFFFFFF)<<8); 313 sum += ((horiz>>24)&255) | ((slopehoriz&0xFFFFFF)<<8); 314 sum += ((slopehoriz>>24)&255) | ((slope&0xFFFFFF)<<8); 315 sum += ((slope>>24)&255) | ((!!isUnderwater)<<8) | 316 ((!!hasKey[0])<<16) | ((!!hasKey[1])<<24); 317 sum += (!!hasKey[2]) | ((!!hasKey[3])<<8) | 318 ((!!hasKey[4])<<16) | ((!!hasKey[5])<<24); 319 sum += (!!hasKey[6]) | ((!!hasKey[7])<<8) | 320 ((hasFlag&255)<<16) | ((used2[0]&255)<<24); 321 sum += ((used2[0]>>8)&255) | ((used2[1]&65535)<<16) | ((used2[2]&255)<<24); 322 sum += ((used2[2]>>8)&255) | ((used2[3]&65535)<<16) | ((used2[4]&255)<<24); 323 sum += ((used2[4]>>8)&255) | ((used2[5]&65535)<<16) | ((used2[6]&255)<<24); 324 sum += ((used2[6]>>8)&255) | ((used2[7]&65535)<<16) | 325 ((damageControl[0]&255)<<24); 326 for (i = 0; i < kDamageMax-1; ++i) 327 sum += ((damageControl[i]>>8)&0xFFFFFF) | ((damageControl[i+1]&255)<<24); 328 sum += ((damageControl[kDamageMax-1]>>8)&0xFFFFFF) | ((curWeapon&255)<<24); 329 sum += (nextWeapon&255) | ((weaponTimer&0xFFFFFF)<<8); 330 sum += ((weaponTimer>>24)&255) | ((weaponState&0xFFFFFF)<<8); 331 sum += ((weaponState>>24)&255) | ((weaponAmmo&0xFFFFFF)<<8); 332 sum += ((weaponAmmo>>24)&255) | (!!hasWeapon[0]<<8) | 333 (!!hasWeapon[1]<<16) | (!!hasWeapon[2]<<24); 334 sum += (!!hasWeapon[3]) | (!!hasWeapon[4]<<8) | 335 (!!hasWeapon[5]<<16) | (!!hasWeapon[6]<<24); 336 sum += (!!hasWeapon[7]) | (!!hasWeapon[8]<<8) | 337 (!!hasWeapon[9]<<16) | (!!hasWeapon[10]<<24); 338 sum += (!!hasWeapon[11]) | (!!hasWeapon[12]<<8) | 339 (!!hasWeapon[13]<<16) | ((weaponMode[0]&255)<<24); 340 for (i = 0; i < 13; ++i) 341 sum += ((weaponMode[i]>>8)&0xFFFFFF) | ((weaponMode[i+1]&255)<<24); 342 sum += ((weaponMode[13]>>8)&0xFFFFFF) | ((weaponOrder[0][0]&255)<<24); 343 for (i = 0; i < 13; ++i) 344 sum += ((weaponOrder[0][i]>>8)&0xFFFFFF) | ((weaponOrder[0][i+1]&255)<<24); 345 sum += ((weaponOrder[0][13]>>8)&0xFFFFFF) | ((weaponOrder[1][0]&255)<<24); 346 for (i = 0; i < 13; ++i) 347 sum += ((weaponOrder[1][i]>>8)&0xFFFFFF) | ((weaponOrder[1][i+1]&255)<<24); 348 sum += ((weaponOrder[1][13]>>8)&0xFFFFFF) | ((ammoCount[0]&255)<<24); 349 for (i = 0; i < 11; ++i) 350 sum += ((ammoCount[i]>>8)&0xFFFFFF) | ((ammoCount[i+1]&255)<<24); 351 sum += ((ammoCount[11]>>8)&0xFFFFFF) | ((!!qavLoop)<<24); 352 sum += fuseTime&0xFFFFFFFF; 353 sum += throwTime&0xFFFFFFFF; 354 sum += throwPower&0xFFFFFFFF; 355 sum += aim.dx&0xFFFFFFFF; 356 sum += aim.dy&0xFFFFFFFF; 357 sum += aim.dz&0xFFFFFFFF; 358 sum += relAim.dx&0xFFFFFFFF; 359 sum += relAim.dy&0xFFFFFFFF; 360 sum += relAim.dz&0xFFFFFFFF; 361 sum += aimTarget&0xFFFFFFFF; 362 sum += aimTargetsCount&0xFFFFFFFF; 363 for (i = 0; i < 16; i += 2) 364 sum += (aimTargets[i]&65535) | ((aimTargets[i+1]&65535) << 16); 365 sum += deathTime&0xFFFFFFFF; 366 for (i = 0; i < 49; ++i) 367 sum += pwUpTime[i]&0xFFFFFFFF; 368 sum += fragCount&0xFFFFFFFF; 369 for (i = 0; i < 8; ++i) 370 sum += fragInfo[i]&0xFFFFFFFF; 371 sum += teamId&0xFFFFFFFF; 372 sum += fraggerId&0xFFFFFFFF; 373 sum += underwaterTime&0xFFFFFFFF; 374 sum += bloodTime&0xFFFFFFFF; 375 sum += gooTime&0xFFFFFFFF; 376 sum += wetTime&0xFFFFFFFF; 377 sum += bubbleTime&0xFFFFFFFF; 378 sum += at306&0xFFFFFFFF; 379 sum += restTime&0xFFFFFFFF; 380 sum += kickPower&0xFFFFFFFF; 381 sum += laughCount&0xFFFFFFFF; 382 sum += spin&0xFFFFFFFF; 383 sum += (!!godMode) | ((!!fallScream)<<8) | 384 ((!!cantJump)<<16) | ((packItemTime&255)<<24); 385 sum += ((packItemTime>>8)&0xFFFFFF) | ((packItemId&255)<<24); 386 sum += ((packItemId>>8)&0xFFFFFF) | ((!!packSlots[0].isActive)<<24); 387 sum += packSlots[0].curAmount&0xFFFFFFFF; 388 sum += (!!packSlots[1].isActive) | ((packSlots[1].curAmount&0xFFFFFF)<<8); 389 sum += ((packSlots[1].curAmount>>24)&255) | 390 ((!!packSlots[2].isActive)<<8) | ((packSlots[2].curAmount&65535)<<16); 391 sum += ((packSlots[2].curAmount>>16)&65535) | 392 ((!!packSlots[3].isActive)<<16) | ((packSlots[3].curAmount&255)<<8); 393 sum += ((packSlots[3].curAmount>>8)&0xFFFFFF) | 394 ((!!packSlots[4].isActive)<<24); 395 sum += packSlots[4].curAmount&0xFFFFFFFF; 396 for (i = 0; i < 3; ++i) 397 sum += armor[i]&0xFFFFFFFF; 398 sum += voodooTarget&0xFFFFFFFF; 399 sum += voodooTargets&0xFFFFFFFF; 400 sum += voodooVar1&0xFFFFFFFF; 401 sum += vodooVar2&0xFFFFFFFF; 402 sum += flickerEffect&0xFFFFFFFF; 403 sum += tiltEffect&0xFFFFFFFF; 404 sum += visibility&0xFFFFFFFF; 405 sum += painEffect&0xFFFFFFFF; 406 sum += blindEffect&0xFFFFFFFF; 407 sum += chokeEffect&0xFFFFFFFF; 408 sum += handTime&0xFFFFFFFF; 409 sum += (!!hand) | ((pickupEffect&0xFFFFFF)<<8); 410 sum += ((pickupEffect>>24)&255) | ((flashEffect&0xFFFFFF)<<8); 411 sum += ((flashEffect>>24)&255) | ((quakeEffect&0xFFFFFF)<<8); 412 return sum; 413 } 414 415 int powerupCheck(PLAYER *pPlayer, int nPowerUp) 416 { 417 dassert(pPlayer != NULL); 418 dassert(nPowerUp >= 0 && nPowerUp < kMaxPowerUps); 419 if (gNoTarget && (nPowerUp == kPwUpShadowCloak)) // no target cheat 420 return gPowerUpInfo[kPwUpShadowCloak].bonusTime; 421 int nPack = powerupToPackItem(nPowerUp); 422 if (nPack >= 0 && !packItemActive(pPlayer, nPack)) 423 return 0; 424 return pPlayer->pwUpTime[nPowerUp]; 425 } 426 427 char powerupAkimboWeapons(int nWeapon) 428 { 429 switch (nWeapon) 430 { 431 case kWeaponFlare: 432 case kWeaponShotgun: 433 case kWeaponTommy: 434 case kWeaponNapalm: 435 case kWeaponTesla: 436 return 1; 437 default: 438 break; 439 } 440 return 0; 441 } 442 443 char powerupActivate(PLAYER *pPlayer, int nPowerUp) 444 { 445 if (powerupCheck(pPlayer, nPowerUp) > 0 && gPowerUpInfo[nPowerUp].pickupOnce) 446 return 0; 447 const int nPwUpTime = pPlayer->pwUpTime[nPowerUp]; 448 if (!nPwUpTime) { 449 int bonusTime = gPowerUpInfo[nPowerUp].bonusTime; 450 if ((nPowerUp == kPwUpTwoGuns) && gGameOptions.bQuadDamagePowerup && !VanillaMode()) // if picked up quad damage 451 bonusTime = kTicRate*22; // set to 22 seconds 452 pPlayer->pwUpTime[nPowerUp] = bonusTime; 453 } 454 int nPack = powerupToPackItem(nPowerUp); 455 if (nPack >= 0) 456 pPlayer->packSlots[nPack].isActive = 1; 457 458 switch (nPowerUp + kItemBase) { 459 #ifdef NOONE_EXTENSIONS 460 case kItemModernMapLevel: 461 if (gModernMap) gFullMap = true; 462 break; 463 case kItemShroomShrink: 464 if (!gModernMap) break; 465 else if (isGrown(pPlayer->pSprite)) playerDeactivateShrooms(pPlayer); 466 else playerSizeShrink(pPlayer, 2); 467 break; 468 case kItemShroomGrow: 469 if (!gModernMap) break; 470 else if (isShrinked(pPlayer->pSprite)) playerDeactivateShrooms(pPlayer); 471 else { 472 playerSizeGrow(pPlayer, 2); 473 if (powerupCheck(&gPlayer[pPlayer->pSprite->type - kDudePlayer1], kPwUpShadowCloak) > 0) { 474 powerupDeactivate(pPlayer, kPwUpShadowCloak); 475 pPlayer->pwUpTime[kPwUpShadowCloak] = 0; 476 } 477 478 if (ceilIsTooLow(pPlayer->pSprite)) 479 actDamageSprite(pPlayer->pSprite->index, pPlayer->pSprite, kDamageExplode, 65535); 480 } 481 break; 482 #endif 483 case kItemFeatherFall: 484 case kItemJumpBoots: 485 pPlayer->damageControl[kDamageFall]++; 486 break; 487 case kItemReflectShots: // reflective shots 488 if (pPlayer == gMe && gGameOptions.nGameType == kGameTypeSinglePlayer) 489 sfxSetReverb2(1); 490 break; 491 case kItemDeathMask: 492 for (int i = 0; i < kDamageMax; i++) 493 pPlayer->damageControl[i]++; 494 break; 495 case kItemDivingSuit: // diving suit 496 pPlayer->damageControl[kDamageDrown]++; 497 if (pPlayer == gMe && gGameOptions.nGameType == kGameTypeSinglePlayer) 498 sfxSetReverb(1); 499 break; 500 case kItemGasMask: 501 pPlayer->damageControl[kDamageDrown]++; 502 break; 503 case kItemArmorAsbest: 504 pPlayer->damageControl[kDamageBurn]++; 505 break; 506 case kItemTwoGuns: 507 if (!VanillaMode()) 508 { 509 if (gGameOptions.bQuadDamagePowerup) // if quad damage is active, do not switch weapon 510 { 511 if (!nPwUpTime) // play quad damage starting sfx if quad damage is not already active 512 { 513 if (pPlayer == gMe) 514 sndStartSample("NOTBLOOD0", 128, -1); 515 else 516 sfxPlay3DSoundCP(pPlayer->pSprite, 776, -1, 0, 0, 192, "NOTBLOOD0"); 517 } 518 return 1; 519 } 520 if (!powerupAkimboWeapons(pPlayer->curWeapon)) // if weapon doesn't have a akimbo state, don't raise weapon 521 break; 522 } 523 pPlayer->input.newWeapon = pPlayer->curWeapon; 524 WeaponRaise(pPlayer); 525 break; 526 } 527 sfxPlay3DSound(pPlayer->pSprite, 776, -1, 0); 528 return 1; 529 } 530 531 void powerupDeactivate(PLAYER *pPlayer, int nPowerUp) 532 { 533 int nPack = powerupToPackItem(nPowerUp); 534 if (nPack >= 0) 535 pPlayer->packSlots[nPack].isActive = 0; 536 537 switch (nPowerUp + kItemBase) { 538 #ifdef NOONE_EXTENSIONS 539 case kItemShroomShrink: 540 if (gModernMap) { 541 playerSizeReset(pPlayer); 542 if (ceilIsTooLow(pPlayer->pSprite)) 543 actDamageSprite(pPlayer->pSprite->index, pPlayer->pSprite, kDamageExplode, 65535); 544 } 545 break; 546 case kItemShroomGrow: 547 if (gModernMap) playerSizeReset(pPlayer); 548 break; 549 #endif 550 case kItemFeatherFall: 551 case kItemJumpBoots: 552 pPlayer->damageControl[kDamageFall]--; 553 break; 554 case kItemDeathMask: 555 for (int i = 0; i < kDamageMax; i++) 556 pPlayer->damageControl[i]--; 557 break; 558 case kItemDivingSuit: 559 pPlayer->damageControl[kDamageDrown]--; 560 if ((pPlayer == gMe) && (VanillaMode() || !powerupCheck(pPlayer, kPwUpReflectShots))) 561 sfxSetReverb(0); 562 break; 563 case kItemReflectShots: 564 if ((pPlayer == gMe) && (VanillaMode() || !packItemActive(pPlayer, kPackDivingSuit))) 565 sfxSetReverb(0); 566 break; 567 case kItemGasMask: 568 pPlayer->damageControl[kDamageDrown]--; 569 break; 570 case kItemArmorAsbest: 571 pPlayer->damageControl[kDamageBurn]--; 572 break; 573 case kItemTwoGuns: 574 if (!VanillaMode()) 575 { 576 if (gGameOptions.bQuadDamagePowerup) // if quad damage is active, do not switch weapon 577 break; 578 if (!powerupAkimboWeapons(pPlayer->curWeapon)) // if weapon doesn't have a akimbo state, don't raise weapon 579 break; 580 } 581 pPlayer->input.newWeapon = pPlayer->curWeapon; 582 WeaponRaise(pPlayer); 583 break; 584 } 585 } 586 587 void powerupSetState(PLAYER *pPlayer, int nPowerUp, char bState) 588 { 589 if (!bState) 590 powerupActivate(pPlayer, nPowerUp); 591 else 592 powerupDeactivate(pPlayer, nPowerUp); 593 } 594 595 void powerupProcess(PLAYER *pPlayer) 596 { 597 pPlayer->packItemTime = ClipLow(pPlayer->packItemTime-kTicsPerFrame, 0); 598 for (int i = kMaxPowerUps-1; i >= 0; i--) 599 { 600 int nPack = powerupToPackItem(i); 601 if (nPack >= 0) 602 { 603 if (pPlayer->packSlots[nPack].isActive) 604 { 605 pPlayer->pwUpTime[i] = ClipLow(pPlayer->pwUpTime[i]-kTicsPerFrame, 0); 606 if (pPlayer->pwUpTime[i]) 607 pPlayer->packSlots[nPack].curAmount = (100*pPlayer->pwUpTime[i])/gPowerUpInfo[i].bonusTime; 608 else 609 { 610 powerupDeactivate(pPlayer, i); 611 if (pPlayer->packItemId == nPack) 612 pPlayer->packItemId = 0; 613 } 614 } 615 } 616 else if (pPlayer->pwUpTime[i] > 0) 617 { 618 if ((i == kPwUpTwoGuns) && gMatrixMode && !VanillaMode()) 619 continue; 620 if (!VanillaMode() && gGameOptions.bQuadDamagePowerup && (i == kPwUpTwoGuns) && (pPlayer->pwUpTime[i] == ((int)(2.987f * kTicsPerSec) * kTicsPerFrame))) // if quad damage is ending 621 { 622 if (pPlayer == gMe) // play quad damage ending sfx 623 sndStartSample("NOTBLOOD1", 128, -1); 624 else 625 sfxPlay3DSoundCP(pPlayer->pSprite, 776, -1, 0, 0, 192, "NOTBLOOD1"); 626 } 627 pPlayer->pwUpTime[i] = ClipLow(pPlayer->pwUpTime[i]-kTicsPerFrame, 0); 628 if (!pPlayer->pwUpTime[i]) 629 powerupDeactivate(pPlayer, i); 630 } 631 } 632 } 633 634 void powerupClear(PLAYER *pPlayer) 635 { 636 if (!VanillaMode() && (pPlayer == gMe)) // turn off reverb sound effects 637 { 638 if (packItemActive(pPlayer, kPackDivingSuit) || powerupCheck(pPlayer, kPwUpReflectShots)) // if diving suit/reflective shots powerup is active, turn off reverb effect 639 sfxSetReverb(0); 640 } 641 for (int i = 0; i < kMaxPowerUps; i++) 642 { 643 pPlayer->pwUpTime[i] = 0; 644 } 645 } 646 647 void powerupInit(void) 648 { 649 } 650 651 int packItemToPowerup(int nPack) 652 { 653 int nPowerUp = -1; 654 switch (nPack) { 655 case kPackMedKit: 656 break; 657 case kPackDivingSuit: 658 nPowerUp = kPwUpDivingSuit; 659 break; 660 case kPackCrystalBall: 661 nPowerUp = kPwUpCrystalBall; 662 break; 663 case kPackBeastVision: 664 nPowerUp = kPwUpBeastVision; 665 break; 666 case kPackJumpBoots: 667 nPowerUp = kPwUpJumpBoots; 668 break; 669 default: 670 ThrowError("Unhandled pack item %d", nPack); 671 break; 672 } 673 return nPowerUp; 674 } 675 676 int powerupToPackItem(int nPowerUp) 677 { 678 switch (nPowerUp) { 679 case kPwUpDivingSuit: 680 return kPackDivingSuit; 681 case kPwUpCrystalBall: 682 return kPackCrystalBall; 683 case kPwUpBeastVision: 684 return kPackBeastVision; 685 case kPwUpJumpBoots: 686 return kPackJumpBoots; 687 } 688 return -1; 689 } 690 691 char packAddItem(PLAYER *pPlayer, unsigned int nPack) 692 { 693 if (nPack < kPackMax) 694 { 695 if (pPlayer->packSlots[nPack].curAmount >= 100) 696 return 0; 697 pPlayer->packSlots[nPack].curAmount = 100; 698 int nPowerUp = packItemToPowerup(nPack); 699 if (nPowerUp >= 0) 700 pPlayer->pwUpTime[nPowerUp] = gPowerUpInfo[nPowerUp].bonusTime; 701 if (pPlayer->packItemId == -1) 702 pPlayer->packItemId = nPack; 703 if (!pPlayer->packSlots[pPlayer->packItemId].curAmount) 704 pPlayer->packItemId = nPack; 705 } 706 else 707 ThrowError("Unhandled pack item %d", nPack); 708 return 1; 709 } 710 711 int packCheckItem(PLAYER *pPlayer, int nPack) 712 { 713 return pPlayer->packSlots[nPack].curAmount; 714 } 715 716 char packItemActive(PLAYER *pPlayer, int nPack) 717 { 718 return pPlayer->packSlots[nPack].isActive; 719 } 720 721 void packUseItem(PLAYER *pPlayer, int nPack) 722 { 723 char bActivate = 0; 724 int nPowerUp = -1; 725 if (pPlayer->packSlots[nPack].curAmount > 0) 726 { 727 switch (nPack) 728 { 729 case kPackMedKit: 730 { 731 XSPRITE *pXSprite = pPlayer->pXSprite; 732 unsigned int health = pXSprite->health>>4; 733 if (health < 100) 734 { 735 int heal = ClipHigh(100-health, pPlayer->packSlots[0].curAmount); 736 actHealDude(pXSprite, heal, 100); 737 pPlayer->packSlots[kPackMedKit].curAmount -= heal; 738 } 739 break; 740 } 741 case kPackDivingSuit: 742 bActivate = 1; 743 nPowerUp = kPwUpDivingSuit; 744 break; 745 case kPackCrystalBall: 746 bActivate = 1; 747 nPowerUp = kPwUpCrystalBall; 748 break; 749 case kPackBeastVision: 750 bActivate = 1; 751 nPowerUp = kPwUpBeastVision; 752 break; 753 case kPackJumpBoots: 754 bActivate = 1; 755 nPowerUp = kPwUpJumpBoots; 756 break; 757 default: 758 ThrowError("Unhandled pack item %d", nPack); 759 return; 760 } 761 if ((gPackSlotSwitch || (gGameOptions.nGameType != kGameTypeSinglePlayer)) && !packItemActive(pPlayer, nPack) && (nPack != kPackDivingSuit) && !VanillaMode()) // switch current slot item to activated item (ignore diving suit) 762 pPlayer->packItemId = nPack; 763 } 764 pPlayer->packItemTime = 0; 765 if (bActivate) 766 powerupSetState(pPlayer, nPowerUp, pPlayer->packSlots[nPack].isActive); 767 } 768 769 void packPrevItem(PLAYER *pPlayer) 770 { 771 if (pPlayer->packItemTime > 0) 772 { 773 for (int nPrev = ClipLow(pPlayer->packItemId-1,kPackBase); nPrev >= kPackBase; nPrev--) 774 { 775 if (pPlayer->packSlots[nPrev].curAmount) 776 { 777 pPlayer->packItemId = nPrev; 778 break; 779 } 780 } 781 } 782 pPlayer->packItemTime = 600; 783 } 784 785 void packNextItem(PLAYER *pPlayer) 786 { 787 if (pPlayer->packItemTime > 0) 788 { 789 for (int nNext = ClipHigh(pPlayer->packItemId+1,kPackMax); nNext < kPackMax; nNext++) 790 { 791 if (pPlayer->packSlots[nNext].curAmount) 792 { 793 pPlayer->packItemId = nNext; 794 break; 795 } 796 } 797 } 798 pPlayer->packItemTime = 600; 799 } 800 801 char playerSeqPlaying(PLAYER * pPlayer, int nSeq) 802 { 803 int nCurSeq = seqGetID(3, pPlayer->pSprite->extra); 804 if (pPlayer->pDudeInfo->seqStartID+nSeq == nCurSeq && seqGetStatus(3,pPlayer->pSprite->extra) >= 0) 805 return 1; 806 return 0; 807 } 808 809 void playerSetRace(PLAYER *pPlayer, int nLifeMode) 810 { 811 dassert(nLifeMode >= kModeHuman && nLifeMode <= kModeHumanGrown); 812 DUDEINFO *pDudeInfo = pPlayer->pDudeInfo; 813 *pDudeInfo = gPlayerTemplate[nLifeMode]; 814 pPlayer->lifeMode = nLifeMode; 815 816 // By NoOne: don't forget to change clipdist for grow and shrink modes 817 pPlayer->pSprite->clipdist = pDudeInfo->clipdist; 818 819 const int nSkill = !(gGameOptions.uNetGameFlags&kNetGameFlagSkillIssue) ? gProfile[pPlayer->nPlayer].skill : gGameOptions.nDifficulty; 820 for (int i = 0; i < kDamageMax; i++) 821 pDudeInfo->curDamage[i] = mulscale8(Handicap[nSkill], pDudeInfo->startDamage[i]); 822 } 823 824 void playerSetGodMode(PLAYER *pPlayer, char bGodMode) 825 { 826 if (bGodMode) 827 { 828 for (int i = 0; i < kDamageMax; i++) 829 pPlayer->damageControl[i]++; 830 } 831 else 832 { 833 for (int i = 0; i < kDamageMax; i++) 834 pPlayer->damageControl[i]--; 835 } 836 pPlayer->godMode = bGodMode; 837 } 838 839 void playerResetInertia(PLAYER *pPlayer) 840 { 841 POSTURE *pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture]; 842 pPlayer->zView = pPlayer->pSprite->z-pPosture->eyeAboveZ; 843 pPlayer->zWeapon = pPlayer->pSprite->z-pPosture->weaponAboveZ; 844 viewBackupView(pPlayer->nPlayer); 845 } 846 847 void playerCorrectInertia(PLAYER* pPlayer, vec3_t const *oldpos) 848 { 849 pPlayer->zView += pPlayer->pSprite->z-oldpos->z; 850 pPlayer->zWeapon += pPlayer->pSprite->z-oldpos->z; 851 viewCorrectViewOffsets(pPlayer->nPlayer, oldpos); 852 } 853 854 void playerResetPowerUps(PLAYER* pPlayer) 855 { 856 for (int i = 0; i < kMaxPowerUps; i++) { 857 if (!VanillaMode() && (i == kPwUpJumpBoots || i == kPwUpDivingSuit || i == kPwUpCrystalBall || i == kPwUpBeastVision)) 858 continue; 859 pPlayer->pwUpTime[i] = 0; 860 } 861 if (!VanillaMode() && (pPlayer == gView)) // reset delirium tilt view variables 862 { 863 gScreenTiltO = gScreenTilt = 0; 864 deliriumTurnO = deliriumTurn = 0; 865 deliriumPitchO = deliriumPitch = 0; 866 } 867 } 868 869 void playerSpawnProtection(PLAYER* pPlayer, int nTicks) 870 { 871 for (int i = 0; i < kDamageMax; i++) // set invul state to damage types 872 pPlayer->damageControl[i]++; 873 pPlayer->pwUpTime[kPwUpDeathMask] = nTicks; // set spawn protection duration 874 } 875 876 void playerSpawnWeapon(PLAYER* pPlayer, int nSpawnWeapon) 877 { 878 if (nSpawnWeapon == 13) // random weapon 879 { 880 nSpawnWeapon = Random(kWeaponRemoteTNT-kWeaponFlare+1)+kWeaponFlare; // give random weapon (between flare gun to remote tnt) 881 } 882 else if (nSpawnWeapon == 14) // all weapons 883 { 884 for (int i = 0; i < kWeaponMax; i++) 885 pPlayer->hasWeapon[i] = 1; 886 for (int i = 0; i < 12; i++) 887 pPlayer->ammoCount[i] = gAmmoInfo[i].max; 888 pPlayer->input.newWeapon = Random(kWeaponRemoteTNT-kWeaponFlare+1)+kWeaponFlare; // switch to random weapon on spawn (between flare gun to remote tnt) 889 return; 890 } 891 892 int nWeaponType = 0; 893 switch (nSpawnWeapon) 894 { 895 case kWeaponFlare: 896 pPlayer->hasWeapon[kWeaponFlare] = 1; 897 pPlayer->input.newWeapon = kWeaponFlare; 898 nWeaponType = gWeaponItemData[kItemWeaponFlarePistol-kItemWeaponBase].ammoType; 899 pPlayer->ammoCount[nWeaponType] = 8; 900 break; 901 case kWeaponShotgun: 902 pPlayer->hasWeapon[kWeaponShotgun] = 1; 903 pPlayer->input.newWeapon = kWeaponShotgun; 904 nWeaponType = gWeaponItemData[kItemWeaponSawedoff-kItemWeaponBase].ammoType; 905 pPlayer->ammoCount[nWeaponType] = 10; 906 break; 907 case kWeaponTommy: 908 pPlayer->hasWeapon[kWeaponTommy] = 1; 909 pPlayer->input.newWeapon = kWeaponTommy; 910 nWeaponType = gWeaponItemData[kItemWeaponTommygun-kItemWeaponBase].ammoType; 911 pPlayer->ammoCount[nWeaponType] = 50; 912 break; 913 case kWeaponNapalm: 914 pPlayer->hasWeapon[kWeaponNapalm] = 1; 915 pPlayer->input.newWeapon = kWeaponNapalm; 916 nWeaponType = gWeaponItemData[kItemWeaponNapalmLauncher-kItemWeaponBase].ammoType; 917 pPlayer->ammoCount[nWeaponType] = 5; 918 break; 919 case kWeaponTNT: 920 pPlayer->hasWeapon[kWeaponTNT] = 1; 921 pPlayer->input.newWeapon = kWeaponTNT; 922 nWeaponType = gWeaponItemData[kItemWeaponTNT-kItemWeaponBase].ammoType; 923 pPlayer->ammoCount[nWeaponType] = 5; 924 break; 925 case kWeaponSprayCan: 926 pPlayer->hasWeapon[kWeaponSprayCan] = 1; 927 pPlayer->input.newWeapon = kWeaponSprayCan; 928 nWeaponType = gWeaponItemData[kItemWeaponSprayCan-kItemWeaponBase].ammoType; 929 pPlayer->ammoCount[nWeaponType] = 300; 930 break; 931 case kWeaponTesla: 932 pPlayer->hasWeapon[kWeaponTesla] = 1; 933 pPlayer->input.newWeapon = kWeaponTesla; 934 nWeaponType = gWeaponItemData[kItemWeaponTeslaCannon-kItemWeaponBase].ammoType; 935 pPlayer->ammoCount[nWeaponType] = 25; 936 break; 937 case kWeaponLifeLeech: 938 pPlayer->hasWeapon[kWeaponLifeLeech] = 1; 939 pPlayer->input.newWeapon = kWeaponLifeLeech; 940 nWeaponType = gWeaponItemData[kItemWeaponLifeLeech-kItemWeaponBase].ammoType; 941 pPlayer->ammoCount[nWeaponType] = 25; 942 break; 943 case kWeaponVoodoo: 944 pPlayer->hasWeapon[kWeaponVoodoo] = 1; 945 pPlayer->input.newWeapon = kWeaponVoodoo; 946 nWeaponType = gWeaponItemData[kItemWeaponVoodooDoll-kItemWeaponBase].ammoType; 947 pPlayer->ammoCount[nWeaponType] = 25; 948 break; 949 case kWeaponProxyTNT: 950 pPlayer->hasWeapon[kWeaponProxyTNT] = 1; 951 pPlayer->input.newWeapon = kWeaponProxyTNT; 952 nWeaponType = 10; 953 pPlayer->ammoCount[nWeaponType] = 3; 954 break; 955 case kWeaponRemoteTNT: 956 pPlayer->hasWeapon[kWeaponRemoteTNT] = 1; 957 pPlayer->input.newWeapon = kWeaponRemoteTNT; 958 nWeaponType = 11; 959 pPlayer->ammoCount[nWeaponType] = 5; 960 break; 961 default: // invalid weapon, this should never be reached 962 break; 963 } 964 } 965 966 struct RESTOREITEMINFO { 967 int hasWeapon[kWeaponMax]; 968 int ammoCount[12]; 969 int packCurAmount[kPackMax]; 970 int pwUpTime[4]; 971 int armorCur[3]; 972 int packId; 973 }; 974 975 static RESTOREITEMINFO restoreInfo[kMaxPlayers]; 976 977 inline void playerBackupItems(PLAYER* pPlayer) 978 { 979 if ((gGameOptions.nGameType != kGameTypeCoop) || !gGameOptions.bItemWeaponSettings) // don't backup item/weapon info for non-coop mode/if restore items is off 980 return; 981 982 RESTOREITEMINFO *pRestoreInfo = &restoreInfo[pPlayer->nPlayer]; 983 for (int i = 0; i < kWeaponMax; i++) 984 pRestoreInfo->hasWeapon[i] = pPlayer->hasWeapon[i]; 985 for (int i = 0; i < 12; i++) 986 pRestoreInfo->ammoCount[i] = pPlayer->ammoCount[i]; 987 for (int i = 0; i < kPackMax; i++) 988 pRestoreInfo->packCurAmount[i] = pPlayer->packSlots[i].curAmount; 989 pRestoreInfo->pwUpTime[0] = pPlayer->pwUpTime[kPwUpDivingSuit]; 990 pRestoreInfo->pwUpTime[1] = pPlayer->pwUpTime[kPwUpCrystalBall]; 991 pRestoreInfo->pwUpTime[2] = pPlayer->pwUpTime[kPwUpBeastVision]; 992 pRestoreInfo->pwUpTime[3] = pPlayer->pwUpTime[kPwUpJumpBoots]; 993 pRestoreInfo->packId = pPlayer->packItemId; 994 for (int i = 0; i < 3; i++) 995 pRestoreInfo->armorCur[i] = pPlayer->armor[i]; 996 } 997 998 inline void playerRestoreItems(PLAYER* pPlayer) 999 { 1000 if ((gGameOptions.nGameType != kGameTypeCoop) || !gGameOptions.bItemWeaponSettings) // don't restore item/weapon info for non-coop mode/if restore items is off 1001 return; 1002 1003 RESTOREITEMINFO *pRestoreInfo = &restoreInfo[pPlayer->nPlayer]; 1004 for (int i = 0; i < kWeaponMax; i++) 1005 pPlayer->hasWeapon[i] = pRestoreInfo->hasWeapon[i]; 1006 for (int i = 0; i < 12; i++) 1007 pPlayer->ammoCount[i] = pRestoreInfo->ammoCount[i]; 1008 for (int i = 0; i < kPackMax; i++) 1009 pPlayer->packSlots[i].curAmount = pRestoreInfo->packCurAmount[i]; 1010 pPlayer->pwUpTime[kPwUpDivingSuit] = pRestoreInfo->pwUpTime[0]; 1011 pPlayer->pwUpTime[kPwUpCrystalBall] = pRestoreInfo->pwUpTime[1]; 1012 pPlayer->pwUpTime[kPwUpBeastVision] = pRestoreInfo->pwUpTime[2]; 1013 pPlayer->pwUpTime[kPwUpJumpBoots] = pRestoreInfo->pwUpTime[3]; 1014 pPlayer->packItemId = pRestoreInfo->packId; 1015 for (int i = 0; i < 3; i++) 1016 pPlayer->armor[i] = pRestoreInfo->armorCur[i]; 1017 } 1018 1019 void playerResetPosture(PLAYER* pPlayer) { 1020 memcpy(pPlayer->pPosture, gPostureDefaults, sizeof(gPostureDefaults)); 1021 if (!VanillaMode()) { 1022 pPlayer->bobPhase = 0; 1023 pPlayer->bobAmp = 0; 1024 pPlayer->swayAmp = 0; 1025 pPlayer->bobHeight = 0; 1026 pPlayer->bobWidth = 0; 1027 pPlayer->swayHeight = 0; 1028 pPlayer->swayWidth = 0; 1029 } 1030 if (pPlayer == gMe) // only reset crouch toggle state if resetting our own posture 1031 gCrouchToggleState = 0; // reset crouch toggle state 1032 } 1033 1034 static void playerResetTeamId(int nPlayer, int bNewLevel) 1035 { 1036 char buffer[80]; 1037 PLAYER* pPlayer = &gPlayer[nPlayer]; 1038 const int nOldTeamId = pPlayer->teamId; 1039 pPlayer->teamId = nPlayer; 1040 1041 if (gGameOptions.nGameType == kGameTypeTeams) 1042 { 1043 if (gGameOptions.bAutoTeams || (gProfile[nPlayer].nTeamPreference == 0)) // game is set to auto teams/player team preference is set to none 1044 pPlayer->teamId = nPlayer&1; 1045 else 1046 pPlayer->teamId = gProfile[nPlayer].nTeamPreference-1; // set to player team preference 1047 1048 if (!bNewLevel && (pPlayer->teamId != nOldTeamId)) // if player changed teams during mid-match, reset co-op camera and print message 1049 { 1050 gViewIndex = myconnectindex; 1051 gView = &gPlayer[myconnectindex]; 1052 1053 const int nPalPlayer = gColorMsg && !VanillaMode() ? playerColorPalMessage(nOldTeamId) : 0; 1054 const int nPalTeam = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 1055 sprintf(buffer, "\r%s\r switched to \r%s\r", gProfile[nPlayer].name, (pPlayer->teamId&1) == 1 ? "Red Team" : "Blue Team"); 1056 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPalPlayer, nPalTeam); 1057 } 1058 } 1059 if ((gGameOptions.nGameType != kGameTypeTeams) && (gProfile[nPlayer].nColorPreference > 0) && !(gGameOptions.uNetGameFlags&kNetGameFlagVanillaColors)) 1060 pPlayer->teamIdPal = gProfile[nPlayer].nColorPreference-1; 1061 else 1062 pPlayer->teamIdPal = pPlayer->teamId&3; 1063 } 1064 1065 const int nZoneRandList[kMaxPlayers][kMaxPlayers] = { 1066 {0, 7, 6, 5, 4, 3, 2, 1}, 1067 {1, 2, 3, 4, 5, 6, 7, 0}, 1068 {2, 0, 5, 3, 4, 1, 6, 7}, 1069 {3, 5, 6, 0, 4, 1, 7, 2}, 1070 {4, 7, 2, 6, 3, 5, 0, 1}, 1071 {5, 3, 2, 6, 7, 1, 4, 0}, 1072 {6, 5, 3, 2, 1, 0, 7, 4}, 1073 {7, 4, 1, 3, 2, 5, 0, 6} 1074 }; 1075 1076 void playerStart(int nPlayer, int bNewLevel) 1077 { 1078 PLAYER* pPlayer = &gPlayer[nPlayer]; 1079 GINPUT* pInput = &pPlayer->input; 1080 ZONE* pStartZone = NULL; 1081 1082 // this is used to safely update profiles while in a network multiplayer session, for example... 1083 // if a player updated their autoaim settings while facing an enemy, it would cause a game desync thanks to the autoaim target changing for local player before the gProfile update packet has been sent to the other clients 1084 // by tunneling all mid-session gProfile updates to gProfileNet it'll allow all clients to update the current player's settings at the same tick, which is on spawn (this ensures everybody stays synced) 1085 if ((numplayers > 1) || (gGameOptions.nGameType != kGameTypeSinglePlayer)) 1086 gProfile[nPlayer] = gProfileNet[nPlayer]; 1087 1088 playerResetTeamId(nPlayer, bNewLevel); 1089 if ((pPlayer == gMe) && (gGameOptions.uNetGameFlags&kNetGameFlagSpectatingAllow)) // force player out of spectating mode after respawning 1090 { 1091 gViewIndex = myconnectindex; 1092 gView = &gPlayer[myconnectindex]; 1093 gNetNotifySpectating = false; 1094 } 1095 1096 // normal start position 1097 if (gGameOptions.nGameType <= kGameTypeCoop) 1098 pStartZone = &gStartZone[nPlayer]; 1099 1100 #ifdef NOONE_EXTENSIONS 1101 // let's check if there is positions of teams is specified 1102 // if no, pick position randomly, just like it works in vanilla. 1103 else if (gModernMap && gGameOptions.nGameType == kGameTypeTeams && gTeamsSpawnUsed == true) { 1104 int maxRetries = 5; 1105 while (maxRetries-- > 0) { 1106 if (pPlayer->teamId == 0) pStartZone = &gStartZoneTeam1[Random(3)]; 1107 else pStartZone = &gStartZoneTeam2[Random(3)]; 1108 1109 if (maxRetries != 0) { 1110 // check if there is no spawned player in selected zone 1111 for (int i = headspritesect[pStartZone->sectnum]; i >= 0; i = nextspritesect[i]) { 1112 spritetype* pSprite = &sprite[i]; 1113 if (pStartZone->x == pSprite->x && pStartZone->y == pSprite->y && IsPlayerSprite(pSprite)) { 1114 pStartZone = NULL; 1115 break; 1116 } 1117 } 1118 } 1119 1120 if (pStartZone != NULL) 1121 break; 1122 } 1123 1124 } 1125 #endif 1126 else { 1127 int zoneId = Random(kMaxPlayers); 1128 if ((gGameOptions.nGameType >= kGameTypeBloodBath) && (gGameOptions.uNetGameFlags&kNetGameFlagSpawnSmart)) { // search for a safe random spawn for bloodbath/teams mode 1129 const int nSearchList = zoneId; 1130 for (int nZone = 0; nZone < kMaxPlayers; nZone++) { 1131 pStartZone = &gStartZone[nZoneRandList[nSearchList][nZone]]; 1132 char bSpawnTooClose = false; 1133 for (int i = connecthead; i >= 0; i = connectpoint2[i]) { // check every connected player 1134 if (!gPlayer[i].pSprite || !gPlayer[i].pXSprite) // invalid player, skip 1135 continue; 1136 if (!sectRangeIsFine(gPlayer[i].pSprite->sectnum)) // invalid sector, skip 1137 continue; 1138 const char bActiveEnemy = (gPlayer[i].pXSprite->health > 0) && !IsTargetTeammate(pPlayer, gPlayer[i].pSprite); 1139 if ((i != nPlayer) && !bActiveEnemy) // only check our current location or that of an alive/enemy player, otherwise skip 1140 continue; 1141 const int nDist = approxDist3D(pStartZone->x-gPlayer[i].pSprite->x, pStartZone->y-gPlayer[i].pSprite->y, pStartZone->z-gPlayer[i].pSprite->z); 1142 if (nDist < 32*10) // if within 10 meters of each other 1143 { 1144 bSpawnTooClose = true; 1145 break; 1146 } 1147 const vec3_t startpos = {pStartZone->x, pStartZone->y, pStartZone->z}; // get start/enemy position (and offset by 1 meter from floor) 1148 const vec3_t enemypos = {gPlayer[i].pSprite->x, gPlayer[i].pSprite->y, gPlayer[i].zView}; 1149 if (cansee(startpos.x, startpos.y, startpos.z, pStartZone->sectnum, enemypos.x, enemypos.y, enemypos.z, gPlayer[i].pSprite->sectnum)) // this spawn is in viewable range of another player/self, stop checking rest of players 1150 { 1151 if (nDist < 32*20) // if within 20 meters of each other 1152 { 1153 bSpawnTooClose = true; 1154 break; 1155 } 1156 } 1157 } 1158 if (!bSpawnTooClose) // if we found a safe spawn point, quit searching 1159 { 1160 zoneId = nZoneRandList[nSearchList][nZone]; 1161 break; 1162 } 1163 } 1164 } 1165 else if ((gGameOptions.nGameType >= kGameTypeBloodBath) && (gGameOptions.uNetGameFlags&kNetGameFlagSpawnDist)) { // get farthest spawn location 1166 int nZoneDist[kMaxPlayers] = {0}; 1167 for (int nZone = 0; nZone < kMaxPlayers; nZone++) { 1168 pStartZone = &gStartZone[nZone]; 1169 for (int i = connecthead; i >= 0; i = connectpoint2[i]) { // check every connected player 1170 if (!gPlayer[i].pSprite || !gPlayer[i].pXSprite) // invalid player, skip 1171 continue; 1172 if (!sectRangeIsFine(gPlayer[i].pSprite->sectnum)) // invalid sector, skip 1173 continue; 1174 const char bActiveEnemy = (gPlayer[i].pXSprite->health > 0) && !IsTargetTeammate(pPlayer, gPlayer[i].pSprite); 1175 if ((i != nPlayer) && !bActiveEnemy) // only check our current location or that of an alive/enemy player, otherwise skip 1176 continue; 1177 const int nDist = approxDist(pStartZone->x-gPlayer[i].pSprite->x, pStartZone->y-gPlayer[i].pSprite->y); 1178 if (nDist > nZoneDist[nZone]) 1179 nZoneDist[nZone] = nDist; 1180 } 1181 } 1182 for (int nZone = 0, nDist = 0; nZone < kMaxPlayers; nZone++) { 1183 if (nDist < nZoneDist[nZone]) { 1184 nDist = nZoneDist[nZone]; 1185 zoneId = nZone; 1186 } 1187 } 1188 } 1189 pStartZone = &gStartZone[zoneId]; 1190 } 1191 1192 if (!VanillaMode()) 1193 sfxKillSpriteSounds(pPlayer->pSprite); 1194 1195 spritetype *pSprite = actSpawnSprite(pStartZone->sectnum, pStartZone->x, pStartZone->y, pStartZone->z, 6, 1); 1196 dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites); 1197 XSPRITE *pXSprite = &xsprite[pSprite->extra]; 1198 pPlayer->pSprite = pSprite; 1199 pPlayer->pXSprite = pXSprite; 1200 pPlayer->nSprite = pSprite->index; 1201 DUDEINFO *pDudeInfo = &dudeInfo[kDudePlayer1 + nPlayer - kDudeBase]; 1202 pPlayer->pDudeInfo = pDudeInfo; 1203 playerSetRace(pPlayer, kModeHuman); 1204 playerResetPosture(pPlayer); 1205 seqSpawn(pDudeInfo->seqStartID, 3, pSprite->extra, -1); 1206 if (pPlayer == gMe) 1207 SetBitString(show2dsprite, pSprite->index); 1208 int top, bottom; 1209 GetSpriteExtents(pSprite, &top, &bottom); 1210 pSprite->z -= bottom - pSprite->z; 1211 if (!VanillaMode() && (gGameOptions.nGameType == kGameTypeTeams) && !(gGameOptions.uNetGameFlags&kNetGameFlagNoTeamColors) && (gGameOptions.uNetGameFlags&kNetGameFlagCalebOnly || !gProfile[pPlayer->nPlayer].nModel)) 1212 pSprite->pal = playerColorPalSprite(pPlayer->teamIdPal); 1213 else 1214 pSprite->pal = playerColorPalDefault(pPlayer->teamIdPal); 1215 pPlayer->angold = pSprite->ang = pStartZone->ang; 1216 pPlayer->q16ang = fix16_from_int(pSprite->ang); 1217 pSprite->type = kDudePlayer1+nPlayer; 1218 pSprite->clipdist = pDudeInfo->clipdist; 1219 pSprite->flags = 15; 1220 pXSprite->burnTime = 0; 1221 pXSprite->burnSource = -1; 1222 pPlayer->pXSprite->health = pDudeInfo->startHealth<<4; 1223 pPlayer->pSprite->cstat &= (unsigned short)~32768; 1224 pPlayer->bloodlust = 0; 1225 pPlayer->q16horiz = 0; 1226 pPlayer->q16slopehoriz = 0; 1227 pPlayer->q16look = 0; 1228 pPlayer->slope = 0; 1229 pPlayer->fraggerId = -1; 1230 pPlayer->underwaterTime = 1200; 1231 pPlayer->bloodTime = 0; 1232 pPlayer->gooTime = 0; 1233 pPlayer->wetTime = 0; 1234 pPlayer->bubbleTime = 0; 1235 pPlayer->at306 = 0; 1236 pPlayer->restTime = 0; 1237 pPlayer->kickPower = 0; 1238 pPlayer->laughCount = 0; 1239 pPlayer->spin = 0; 1240 pPlayer->posture = kPostureStand; 1241 pPlayer->voodooTarget = -1; 1242 pPlayer->voodooTargets = 0; 1243 pPlayer->voodooVar1 = 0; 1244 pPlayer->vodooVar2 = 0; 1245 pPlayer->invulTime = 0; 1246 playerResetInertia(pPlayer); 1247 pPlayer->zWeaponVel = 0; 1248 pPlayer->relAim.dx = 0x4000; 1249 pPlayer->relAim.dy = 0; 1250 pPlayer->relAim.dz = 0; 1251 pPlayer->aimTarget = -1; 1252 pPlayer->zViewVel = pPlayer->zWeaponVel; 1253 if (!(gGameOptions.nGameType == kGameTypeCoop && gGameOptions.nKeySettings && !bNewLevel)) 1254 { 1255 for (int i = 0; i < 8; i++) 1256 pPlayer->hasKey[i] = gGameOptions.nGameType >= kGameTypeBloodBath; 1257 } 1258 pPlayer->hasFlag = 0; 1259 for (int i = 0; i < 8; i++) 1260 pPlayer->used2[i] = -1; 1261 for (int i = 0; i < kDamageMax; i++) 1262 pPlayer->damageControl[i] = 0; 1263 if (pPlayer->godMode) 1264 playerSetGodMode(pPlayer, 1); 1265 gInfiniteAmmo = 0; 1266 gLifeleechRnd = 0; 1267 gAlphaPitchfork = 0; 1268 gSonicMode = 0; 1269 gNukeMode = 0; 1270 gMatrixMode = 0; 1271 gFullMap = 0; 1272 pPlayer->throwPower = 0; 1273 pPlayer->tauntTime = 0; 1274 pPlayer->deathTime = 0; 1275 pPlayer->nextWeapon = kWeaponNone; 1276 pPlayer->lastWeapon = kWeaponPitchfork; 1277 xvel[pSprite->index] = yvel[pSprite->index] = zvel[pSprite->index] = 0; 1278 pInput->q16turn = 0; 1279 pInput->keyFlags.word = 0; 1280 pInput->forward = 0; 1281 pInput->strafe = 0; 1282 pInput->q16mlook = 0; 1283 pInput->buttonFlags.byte = 0; 1284 pInput->useFlags.byte = 0; 1285 pPlayer->flickerEffect = 0; 1286 pPlayer->quakeEffect = 0; 1287 pPlayer->tiltEffect = 0; 1288 pPlayer->visibility = 0; 1289 pPlayer->painEffect = 0; 1290 pPlayer->blindEffect = 0; 1291 pPlayer->chokeEffect = 0; 1292 pPlayer->handTime = 0; 1293 pPlayer->weaponTimer = 0; 1294 pPlayer->weaponState = 0; 1295 pPlayer->weaponQav = -1; 1296 #ifdef NOONE_EXTENSIONS 1297 playerQavSceneReset(pPlayer); // reset qav scene 1298 1299 // assign or update player's sprite index for conditions 1300 if (gModernMap) 1301 { 1302 for (int nSprite = headspritestat[kStatModernPlayerLinker]; nSprite >= 0; nSprite = nextspritestat[nSprite]) 1303 { 1304 XSPRITE* pXCtrl = &xsprite[sprite[nSprite].extra]; 1305 if (!pXCtrl->data1 || pXCtrl->data1 == pPlayer->nPlayer + 1) 1306 { 1307 int nSpriteOld = pXCtrl->sysData1; 1308 trPlayerCtrlLink(pXCtrl, pPlayer, (nSpriteOld < 0) ? true : false); 1309 if (nSpriteOld > 0) 1310 conditionsUpdateIndex(OBJ_SPRITE, nSpriteOld, pXCtrl->sysData1); 1311 } 1312 } 1313 } 1314 1315 #endif 1316 pPlayer->hand = 0; 1317 pPlayer->nWaterPal = 0; 1318 playerResetPowerUps(pPlayer); 1319 const char bSpectatorPlayer = (gGameOptions.uNetGameFlags&kNetGameFlagSpectatingAllow) && !strncmp(gProfile[nPlayer].name, "spectator", MAXPLAYERNAME); 1320 if ((gGameOptions.nGameType != kGameTypeSinglePlayer) && (gGameOptions.nSpawnProtection > 0) && !bSpectatorPlayer) 1321 playerSpawnProtection(pPlayer, gGameOptions.nSpawnProtection*kTicRate); 1322 1323 if (pPlayer == gMe) 1324 { 1325 viewInitializePrediction(); 1326 gViewLook = pPlayer->q16look; 1327 gViewAngle = pPlayer->q16ang; 1328 gViewMap.x = pPlayer->pSprite->x; 1329 gViewMap.y = pPlayer->pSprite->y; 1330 gViewMap.angle = pPlayer->pSprite->ang; 1331 if (!VanillaMode()) 1332 { 1333 sfxResetListener(); // player is listener, update ear position/reset ear velocity so audio pitch of surrounding sfx does not freak out when respawning player 1334 sfxUpdateSpritePos(pPlayer->pSprite); // update any assigned sfx to new player position 1335 } 1336 } 1337 if (IsUnderwaterSector(pSprite->sectnum)) 1338 { 1339 pPlayer->posture = kPostureSwim; 1340 pPlayer->pXSprite->medium = kMediumWater; 1341 } 1342 gMultiKillsFrags[nPlayer] = 0; 1343 gMultiKillsTicks[nPlayer] = 0; 1344 if (bNewLevel || ((nPlayer == gPlayerLastVictim) && (pPlayer == gMe))) 1345 playerResetKillMsg(); 1346 if (bNewLevel || (nPlayer == gAnnounceKillingSpreePlayer)) 1347 playerResetAnnounceKillingSpree(); 1348 if (bNewLevel) 1349 memset(gDominationCount, 0, sizeof(gDominationCount)); 1350 if (bNewLevel) 1351 playerBackupItems(pPlayer); 1352 if (bSpectatorPlayer) 1353 { 1354 int bakPlayerScores[kMaxPlayers]; 1355 int bakFragCount = pPlayer->fragCount; 1356 memcpy(bakPlayerScores, gPlayerScores, sizeof(bakPlayerScores)); 1357 actDamageSprite(pPlayer->pSprite->index, pSprite, kDamageFall, 500<<4); 1358 memcpy(gPlayerScores, bakPlayerScores, sizeof(bakPlayerScores)); 1359 pPlayer->fragCount = bakFragCount; 1360 } 1361 } 1362 1363 void playerReset(PLAYER *pPlayer) 1364 { 1365 static int dword_136400[] = { 1366 3, 4, 2, 8, 9, 10, 7, 1, 1, 1, 1, 1, 1, 1 1367 }; 1368 static int dword_136438[] = { 1369 3, 4, 2, 8, 9, 10, 7, 1, 1, 1, 1, 1, 1, 1 1370 }; 1371 dassert(pPlayer != NULL); 1372 for (int i = 0; i < kWeaponMax; i++) 1373 { 1374 pPlayer->hasWeapon[i] = gInfiniteAmmo; 1375 pPlayer->weaponMode[i] = 0; 1376 } 1377 pPlayer->hasWeapon[kWeaponPitchfork] = 1; 1378 pPlayer->curWeapon = kWeaponNone; 1379 pPlayer->qavCallback = -1; 1380 pPlayer->input.newWeapon = kWeaponPitchfork; 1381 pPlayer->lastWeapon = kWeaponPitchfork; 1382 for (int i = 0; i < kWeaponMax; i++) 1383 { 1384 pPlayer->weaponOrder[0][i] = dword_136400[i]; 1385 pPlayer->weaponOrder[1][i] = dword_136438[i]; 1386 } 1387 for (int i = 0; i < 12; i++) 1388 { 1389 if (gInfiniteAmmo) 1390 pPlayer->ammoCount[i] = gAmmoInfo[i].max; 1391 else 1392 pPlayer->ammoCount[i] = 0; 1393 } 1394 for (int i = 0; i < 3; i++) 1395 pPlayer->armor[i] = 0; 1396 pPlayer->weaponTimer = 0; 1397 pPlayer->weaponState = 0; 1398 pPlayer->weaponQav = -1; 1399 pPlayer->qavLoop = 0; 1400 pPlayer->packItemId = -1; 1401 1402 if ((gGameOptions.nGameType != kGameTypeSinglePlayer) && (gGameOptions.nSpawnWeapon > 0)) 1403 playerSpawnWeapon(pPlayer, gGameOptions.nSpawnWeapon+kWeaponPitchfork); 1404 1405 for (int i = 0; i < kPackMax; i++) { 1406 pPlayer->packSlots[i].isActive = 0; 1407 pPlayer->packSlots[i].curAmount = 0; 1408 } 1409 #ifdef NOONE_EXTENSIONS 1410 playerQavSceneReset(pPlayer); 1411 #endif 1412 // reset posture (mainly required for resetting movement speed and jump height) 1413 playerResetPosture(pPlayer); 1414 } 1415 1416 void playerResetScores(int nPlayer) 1417 { 1418 PLAYER *pPlayer = &gPlayer[nPlayer]; 1419 pPlayer->fragCount = 0; 1420 memset(pPlayer->fragInfo, 0, sizeof(pPlayer->fragInfo)); 1421 memset(gPlayerScores, 0, sizeof(gPlayerScores)); 1422 memset((void *)gPlayerScoreTicks, 0, sizeof(gPlayerScoreTicks)); 1423 } 1424 1425 void playerInit(int nPlayer, unsigned int a2) 1426 { 1427 PLAYER *pPlayer = &gPlayer[nPlayer]; 1428 if (!(a2&1)) 1429 memset((void *)pPlayer, 0, sizeof(PLAYER)); 1430 pPlayer->nPlayer = nPlayer; 1431 playerResetTeamId(nPlayer, 1); 1432 playerResetScores(nPlayer); 1433 1434 if (!(a2&1)) 1435 playerReset(pPlayer); 1436 } 1437 1438 char findDroppedLeech(PLAYER *a1, spritetype *a2) 1439 { 1440 for (int nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) 1441 { 1442 if (a2 && a2->index == nSprite) 1443 continue; 1444 spritetype *pSprite = &sprite[nSprite]; 1445 if (pSprite->type == kThingDroppedLifeLeech && actOwnerIdToSpriteId(pSprite->owner) == a1->nSprite) 1446 return 1; 1447 } 1448 return 0; 1449 } 1450 1451 char PickupItem(PLAYER *pPlayer, spritetype *pItem) { 1452 1453 spritetype *pSprite = pPlayer->pSprite; XSPRITE *pXSprite = pPlayer->pXSprite; 1454 char buffer[80]; int pickupSnd = 775; int nType = pItem->type - kItemBase; 1455 1456 switch (pItem->type) { 1457 case kItemShadowCloak: 1458 #ifdef NOONE_EXTENSIONS 1459 if (isGrown(pPlayer->pSprite) || !powerupActivate(pPlayer, nType)) return false; 1460 #else 1461 if (!powerupActivate(pPlayer, nType)) return false; 1462 #endif 1463 break; 1464 #ifdef NOONE_EXTENSIONS 1465 case kItemShroomShrink: 1466 case kItemShroomGrow: 1467 1468 if (gModernMap) { 1469 switch (pItem->type) { 1470 case kItemShroomShrink: 1471 if (isShrinked(pSprite)) return false; 1472 break; 1473 case kItemShroomGrow: 1474 if (isGrown(pSprite)) return false; 1475 break; 1476 } 1477 1478 powerupActivate(pPlayer, nType); 1479 } 1480 1481 break; 1482 #endif 1483 case kItemFlagABase: 1484 case kItemFlagBBase: { 1485 if (gGameOptions.nGameType != kGameTypeTeams || pItem->extra <= 0 || gPlayerRoundEnding) return 0; 1486 XSPRITE * pXItem = &xsprite[pItem->extra]; 1487 const int nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 1488 if (pItem->type == kItemFlagABase) { 1489 if (pPlayer->teamId == 1) { 1490 if ((pPlayer->hasFlag & 1) == 0 && pXItem->state) { 1491 pPlayer->hasFlag |= 1; 1492 pPlayer->used2[0] = pItem->index; 1493 trTriggerSprite(pItem->index, pXItem, kCmdOff, pPlayer->nSprite); 1494 sprintf(buffer, "\r%s\r stole \rBlue Flag\r", gProfile[pPlayer->nPlayer].name); 1495 sndStartSample(8007, 255, 2, 0); 1496 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagBluePal); 1497 } 1498 } 1499 1500 if (pPlayer->teamId == 0) { 1501 1502 if ((pPlayer->hasFlag & 1) != 0 && !pXItem->state) { 1503 pPlayer->hasFlag &= ~1; 1504 pPlayer->used2[0] = -1; 1505 trTriggerSprite(pItem->index, pXItem, kCmdOn, pPlayer->nSprite); 1506 sprintf(buffer, "\r%s\r returned \rBlue Flag\r", gProfile[pPlayer->nPlayer].name); 1507 sndStartSample(8003, 255, 2, 0); 1508 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagBluePal); 1509 } 1510 1511 if ((pPlayer->hasFlag & 2) != 0 && pXItem->state) { 1512 pPlayer->hasFlag &= ~2; 1513 pPlayer->used2[1] = -1; 1514 gPlayerScores[pPlayer->teamId] += 10; 1515 gPlayerScoreTicks[pPlayer->teamId] += 240; 1516 evSend(0, 0, 81, kCmdOn, pPlayer->nSprite); 1517 sprintf(buffer, "\r%s\r captured \rRed Flag\r!", gProfile[pPlayer->nPlayer].name); 1518 sndStartSample(8001, 255, 2, 0); 1519 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagRedPal); 1520 if (gGameOptions.uNetGameFlags&kNetGameFlagScoresLimitMask) 1521 playerProcessRoundCheck(pPlayer); 1522 #if 0 1523 if (dword_28E3D4 == 3 && myconnectindex == connecthead) 1524 { 1525 sprintf(buffer, "frag A killed B\n"); 1526 netBroadcastFrag(buffer); 1527 } 1528 #endif 1529 } 1530 } 1531 1532 } 1533 else if (pItem->type == kItemFlagBBase) { 1534 1535 if (pPlayer->teamId == 0) { 1536 if ((pPlayer->hasFlag & 2) == 0 && pXItem->state) { 1537 pPlayer->hasFlag |= 2; 1538 pPlayer->used2[1] = pItem->index; 1539 trTriggerSprite(pItem->index, pXItem, kCmdOff, pPlayer->nSprite); 1540 sprintf(buffer, "\r%s\r stole \rRed Flag\r", gProfile[pPlayer->nPlayer].name); 1541 sndStartSample(8006, 255, 2, 0); 1542 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagRedPal); 1543 } 1544 } 1545 1546 if (pPlayer->teamId == 1) { 1547 if ((pPlayer->hasFlag & 2) != 0 && !pXItem->state) 1548 { 1549 pPlayer->hasFlag &= ~2; 1550 pPlayer->used2[1] = -1; 1551 trTriggerSprite(pItem->index, pXItem, kCmdOn, pPlayer->nSprite); 1552 sprintf(buffer, "\r%s\r returned \rRed Flag\r", gProfile[pPlayer->nPlayer].name); 1553 sndStartSample(8002, 255, 2, 0); 1554 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagRedPal); 1555 } 1556 if ((pPlayer->hasFlag & 1) != 0 && pXItem->state) 1557 { 1558 pPlayer->hasFlag &= ~1; 1559 pPlayer->used2[0] = -1; 1560 gPlayerScores[pPlayer->teamId] += 10; 1561 gPlayerScoreTicks[pPlayer->teamId] += 240; 1562 evSend(0, 0, 80, kCmdOn, pPlayer->nSprite); 1563 sprintf(buffer, "\r%s\r captured \rBlue Flag\r!", gProfile[pPlayer->nPlayer].name); 1564 sndStartSample(8000, 255, 2, 0); 1565 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagBluePal); 1566 if (gGameOptions.uNetGameFlags&kNetGameFlagScoresLimitMask) 1567 playerProcessRoundCheck(pPlayer); 1568 #if 0 1569 if (dword_28E3D4 == 3 && myconnectindex == connecthead) 1570 { 1571 sprintf(buffer, "frag B killed A\n"); 1572 netBroadcastFrag(buffer); 1573 } 1574 #endif 1575 } 1576 } 1577 } 1578 } 1579 return 0; 1580 case kItemFlagA: { 1581 if (gGameOptions.nGameType != kGameTypeTeams) return 0; 1582 evKill(pItem->index, 3, kCallbackReturnFlag); 1583 gBlueFlagDropped = false; 1584 pPlayer->hasFlag |= 1; 1585 pPlayer->used2[0] = pItem->owner; 1586 const bool enemyTeam = (pPlayer->teamId&1) == 1; 1587 const int nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 1588 if (enemyTeam) 1589 { 1590 sprintf(buffer, "\r%s\r stole \rBlue Flag\r", gProfile[pPlayer->nPlayer].name); 1591 sndStartSample(8007, 255, 2, 0); 1592 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagBluePal); 1593 } 1594 break; 1595 } 1596 case kItemFlagB: { 1597 if (gGameOptions.nGameType != kGameTypeTeams) return 0; 1598 evKill(pItem->index, 3, kCallbackReturnFlag); 1599 gRedFlagDropped = false; 1600 pPlayer->hasFlag |= 2; 1601 pPlayer->used2[1] = pItem->owner; 1602 const bool enemyTeam = (pPlayer->teamId&1) == 0; 1603 const int nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 1604 if (enemyTeam) 1605 { 1606 sprintf(buffer, "\r%s\r stole \rRed Flag\r", gProfile[pPlayer->nPlayer].name); 1607 sndStartSample(8006, 255, 2, 0); 1608 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagRedPal); 1609 } 1610 break; 1611 } 1612 case kItemArmorBasic: 1613 case kItemArmorBody: 1614 case kItemArmorFire: 1615 case kItemArmorSpirit: 1616 case kItemArmorSuper: { 1617 ARMORDATA *pArmorData = &armorData[pItem->type - kItemArmorBasic]; bool pickedUp = false; 1618 if (pPlayer->armor[1] < pArmorData->atc) { 1619 pPlayer->armor[1] = ClipHigh(pPlayer->armor[1]+pArmorData->at8, pArmorData->atc); 1620 pickedUp = true; 1621 } 1622 1623 if (pPlayer->armor[0] < pArmorData->at4) { 1624 pPlayer->armor[0] = ClipHigh(pPlayer->armor[0]+pArmorData->at0, pArmorData->at4); 1625 pickedUp = true; 1626 } 1627 1628 if (pPlayer->armor[2] < pArmorData->at14) { 1629 pPlayer->armor[2] = ClipHigh(pPlayer->armor[2]+pArmorData->at10, pArmorData->at14); 1630 pickedUp = true; 1631 } 1632 1633 if (!pickedUp) return 0; 1634 pickupSnd = 779; 1635 break; 1636 } 1637 case kItemCrystalBall: 1638 if (gGameOptions.nGameType == kGameTypeSinglePlayer || !packAddItem(pPlayer, gItemData[nType].packSlot)) return 0; 1639 break; 1640 case kItemKeySkull: 1641 case kItemKeyEye: 1642 case kItemKeyFire: 1643 case kItemKeyDagger: 1644 case kItemKeySpider: 1645 case kItemKeyMoon: 1646 case kItemKeyKey7: 1647 if (pPlayer->hasKey[pItem->type-99]) return 0; 1648 pPlayer->hasKey[pItem->type-99] = 1; 1649 pickupSnd = 781; 1650 if ((gAutosave > 1) && (gGameOptions.nGameType == kGameTypeSinglePlayer) && !gDemo.bPlaying && !gDemo.bRecording) // if autosave is on and not currently in multiplayer/demo playback, autosave on key pickup 1651 gDoQuickSave = 3; 1652 if ((gGameOptions.nGameType == kGameTypeCoop) && (gGameOptions.nKeySettings == 2)) { // if co-op and global key collection is on, also give key to all players 1653 for (int i = connecthead; i >= 0; i = connectpoint2[i]) { 1654 gPlayer[i].hasKey[pItem->type-99] = 1; 1655 } 1656 if (pPlayer != gMe) { // display message if network player collected key 1657 sprintf(buffer, "\r%s\r picked up %s", gProfile[pPlayer->nPlayer].name, gItemText[pItem->type - kItemBase]); 1658 const int nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 1659 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_PICKUP, nPal); 1660 } 1661 } 1662 break; 1663 case kItemHealthMedPouch: 1664 case kItemHealthLifeEssense: 1665 case kItemHealthLifeSeed: 1666 case kItemHealthRedPotion: { 1667 int addPower = gPowerUpInfo[nType].bonusTime; 1668 #ifdef NOONE_EXTENSIONS 1669 // allow custom amount for item 1670 if (gModernMap && sprite[pItem->index].extra >= 0 && xsprite[sprite[pItem->index].extra].data1 > 0) 1671 addPower = xsprite[sprite[pItem->index].extra].data1; 1672 #endif 1673 1674 if (!actHealDude(pXSprite, addPower, gPowerUpInfo[nType].maxTime)) return 0; 1675 return 1; 1676 } 1677 case kItemHealthDoctorBag: 1678 case kItemJumpBoots: 1679 case kItemDivingSuit: 1680 case kItemBeastVision: 1681 if (!packAddItem(pPlayer, gItemData[nType].packSlot)) return 0; 1682 break; 1683 default: 1684 if (pPlayer->pwUpTime[nType] && !gPowerUpInfo[nType].pickupOnce && (gGameOptions.nGameType <= kGameTypeCoop) && !VanillaMode()) // if powerup is already active, reset time (only for co-op/single-player - Mario star logic) 1685 pPlayer->pwUpTime[nType] = 0; 1686 if (!powerupActivate(pPlayer, nType)) return 0; 1687 return 1; 1688 } 1689 1690 sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, pickupSnd, pSprite->sectnum); 1691 return 1; 1692 } 1693 1694 char PickupAmmo(PLAYER* pPlayer, spritetype* pAmmo) { 1695 AMMOITEMDATA* pAmmoItemData = &gAmmoItemData[pAmmo->type - kItemAmmoBase]; 1696 int nAmmoType = pAmmoItemData->type; 1697 const int nAmmoOld = pPlayer->ammoCount[nAmmoType]; 1698 1699 if (pPlayer->ammoCount[nAmmoType] >= gAmmoInfo[nAmmoType].max) return 0; 1700 #ifdef NOONE_EXTENSIONS 1701 else if (gModernMap && pAmmo->extra >= 0 && xsprite[pAmmo->extra].data1 > 0) // allow custom amount for item 1702 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + xsprite[pAmmo->extra].data1, gAmmoInfo[nAmmoType].max); 1703 #endif 1704 else 1705 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType]+pAmmoItemData->count, gAmmoInfo[nAmmoType].max); 1706 1707 if (pAmmoItemData->weaponType) pPlayer->hasWeapon[pAmmoItemData->weaponType] = 1; 1708 sfxPlay3DSound(pPlayer->pSprite, 782, -1, 0); 1709 if (gGameOptions.nAmmoScale && !VanillaMode()) 1710 { 1711 const int nAmmoDiff = pPlayer->ammoCount[nAmmoType] - nAmmoOld; 1712 switch (gGameOptions.nAmmoScale) 1713 { 1714 case 3: // 0.25x 1715 pPlayer->ammoCount[nAmmoType] = ClipLow(pPlayer->ammoCount[nAmmoType] - ((nAmmoDiff>>1)+(nAmmoDiff>>2)), 0); 1716 break; 1717 case 4: // 0.5x 1718 pPlayer->ammoCount[nAmmoType] = ClipLow(pPlayer->ammoCount[nAmmoType] - (nAmmoDiff>>1), 0); 1719 break; 1720 case 5: // 0.75x 1721 pPlayer->ammoCount[nAmmoType] = ClipLow(pPlayer->ammoCount[nAmmoType] - ((nAmmoDiff>>1)>>1), 0); 1722 break; 1723 case 1: // 1.25x 1724 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + ((nAmmoDiff>>1)>>1), gAmmoInfo[nAmmoType].max); 1725 break; 1726 case 2: // 1.5x 1727 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + (nAmmoDiff>>1), gAmmoInfo[nAmmoType].max); 1728 break; 1729 } 1730 } 1731 return 1; 1732 } 1733 1734 char PickupWeapon(PLAYER *pPlayer, spritetype *pWeapon) { 1735 WEAPONITEMDATA *pWeaponItemData = &gWeaponItemData[pWeapon->type - kItemWeaponBase]; 1736 int nWeaponType = pWeaponItemData->type; 1737 int nAmmoType = pWeaponItemData->ammoType; 1738 if (!pPlayer->hasWeapon[nWeaponType] || gGameOptions.nWeaponSettings == 2 || gGameOptions.nWeaponSettings == 3) { 1739 if ((pWeapon->type == kItemWeaponLifeLeech) && (gGameOptions.nGameType >= kGameTypeBloodBath) && findDroppedLeech(pPlayer, NULL)) 1740 return 0; 1741 pPlayer->hasWeapon[nWeaponType] = 1; 1742 if (nAmmoType == -1) return 0; 1743 // allow to set custom ammo count for weapon pickups 1744 #ifdef NOONE_EXTENSIONS 1745 else if (gModernMap && pWeapon->extra >= 0 && xsprite[pWeapon->extra].data1 > 0) 1746 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + xsprite[pWeapon->extra].data1, gAmmoInfo[nAmmoType].max); 1747 #endif 1748 else 1749 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pWeaponItemData->count, gAmmoInfo[nAmmoType].max); 1750 1751 int nNewWeapon = WeaponUpgrade(pPlayer, nWeaponType); 1752 if (nNewWeapon != pPlayer->curWeapon) { 1753 pPlayer->weaponState = 0; 1754 pPlayer->nextWeapon = nNewWeapon; 1755 } 1756 sfxPlay3DSound(pPlayer->pSprite, 777, -1, 0); 1757 return 1; 1758 } 1759 1760 if (!actGetRespawnTime(pWeapon) || nAmmoType == -1 || pPlayer->ammoCount[nAmmoType] >= gAmmoInfo[nAmmoType].max) return 0; 1761 #ifdef NOONE_EXTENSIONS 1762 else if (gModernMap && pWeapon->extra >= 0 && xsprite[pWeapon->extra].data1 > 0) 1763 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + xsprite[pWeapon->extra].data1, gAmmoInfo[nAmmoType].max); 1764 #endif 1765 else 1766 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType]+pWeaponItemData->count, gAmmoInfo[nAmmoType].max); 1767 1768 sfxPlay3DSound(pPlayer->pSprite, 777, -1, 0); 1769 return 1; 1770 } 1771 1772 void PickUp(PLAYER *pPlayer, spritetype *pSprite) 1773 { 1774 char buffer[256], pickedUp = 0, showMsg = 1, showEff = 1; 1775 int nType = pSprite->type, customMsg = -1; 1776 1777 #ifdef NOONE_EXTENSIONS 1778 if (gModernMap) // allow custom INI message instead "Picked up" 1779 { 1780 XSPRITE* pXSprite = (pSprite->extra >= 0) ? &xsprite[pSprite->extra] : NULL; 1781 if (pXSprite != NULL && pXSprite->txID != 3 && pXSprite->lockMsg > 0) 1782 customMsg = pXSprite->lockMsg; 1783 } 1784 1785 if (IsUserItem(nType)) 1786 { 1787 ITEM* pItem = userItemGet(nType); 1788 if (pItem && (pickedUp = userItemPickup(pPlayer, pSprite, pItem)) != 0) 1789 { 1790 if (customMsg < 0) 1791 { 1792 showMsg = !(pItem->flags & kFlagItemNoMessage); 1793 1794 if (showMsg) 1795 { 1796 if (!pItem->message) sprintf(buffer, "Picked up %s", pItem->name); 1797 else strcpy(buffer, pItem->message); 1798 } 1799 } 1800 1801 showEff = (!(pItem->flags & kFlagItemNoEffect) && !pItem->numeffects); 1802 } 1803 } 1804 else 1805 #endif 1806 if (nType >= kItemBase && nType <= kItemMax) { 1807 pickedUp = PickupItem(pPlayer, pSprite); 1808 if (pickedUp && customMsg == -1) { 1809 const char *pItemText = gItemText[nType - kItemBase]; 1810 if ((nType == kItemTwoGuns) && gGameOptions.bQuadDamagePowerup && !VanillaMode()) // replace guns akimbo pickup text 1811 pItemText = "Tome of Quad"; 1812 sprintf(buffer, "Picked up %s", pItemText); 1813 } 1814 1815 } else if (nType >= kItemAmmoBase && nType < kItemAmmoMax) { 1816 pickedUp = PickupAmmo(pPlayer, pSprite); 1817 if (pickedUp && customMsg == -1) sprintf(buffer, "Picked up %s", gAmmoText[nType - kItemAmmoBase]); 1818 1819 } else if (nType >= kItemWeaponBase && nType < kItemWeaponMax) { 1820 pickedUp = PickupWeapon(pPlayer, pSprite); 1821 if (pickedUp && customMsg == -1) sprintf(buffer, "Picked up %s", gWeaponText[nType - kItemWeaponBase]); 1822 } 1823 1824 if (!pickedUp) return; 1825 else if (pSprite->extra > 0) { 1826 XSPRITE *pXSprite = &xsprite[pSprite->extra]; 1827 if (pXSprite->Pickup) 1828 trTriggerSprite(pSprite->index, pXSprite, kCmdSpritePickup, pPlayer->nSprite); 1829 } 1830 1831 if (!actCheckRespawn(pSprite)) 1832 actPostSprite(pSprite->index, kStatFree); 1833 1834 if (showEff) 1835 pPlayer->pickupEffect = 30; 1836 1837 if (pPlayer == gMe && showMsg) 1838 { 1839 if (customMsg > 0) trTextOver(customMsg - 1); 1840 else if ((nType == kItemShroomDelirium) && (pSprite->cstat&CSTAT_SPRITE_INVISIBLE) && !VanillaMode()) return; // do not print text for invisible delirium shrooms 1841 else viewSetMessage(buffer, 0, MESSAGE_PRIORITY_PICKUP); 1842 } 1843 } 1844 1845 void CheckPickUp(PLAYER *pPlayer) 1846 { 1847 spritetype *pSprite = pPlayer->pSprite; 1848 int x = pSprite->x; 1849 int y = pSprite->y; 1850 int z = pSprite->z; 1851 int nSector = pSprite->sectnum; 1852 int nNextSprite; 1853 for (int nSprite = headspritestat[kStatItem]; nSprite >= 0; nSprite = nNextSprite) { 1854 spritetype *pItem = &sprite[nSprite]; 1855 nNextSprite = nextspritestat[nSprite]; 1856 if (pItem->flags&32) 1857 continue; 1858 int dx = klabs(x-pItem->x)>>4; 1859 if (dx > 48) 1860 continue; 1861 int dy = klabs(y-pItem->y)>>4; 1862 if (dy > 48) 1863 continue; 1864 int top, bottom; 1865 GetSpriteExtents(pSprite, &top, &bottom); 1866 int vb = 0; 1867 if (pItem->z < top) 1868 vb = (top-pItem->z)>>8; 1869 else if (pItem->z > bottom) 1870 vb = (pItem->z-bottom)>>8; 1871 if (vb > 32) 1872 continue; 1873 if (approxDist(dx,dy) > 48) 1874 continue; 1875 GetSpriteExtents(pItem, &top, &bottom); 1876 if (cansee(x, y, z, nSector, pItem->x, pItem->y, pItem->z, pItem->sectnum) 1877 || cansee(x, y, z, nSector, pItem->x, pItem->y, top, pItem->sectnum) 1878 || cansee(x, y, z, nSector, pItem->x, pItem->y, bottom, pItem->sectnum)) 1879 PickUp(pPlayer, pItem); 1880 } 1881 } 1882 1883 int ActionScan(PLAYER *pPlayer, int *a2, int *a3) 1884 { 1885 *a2 = 0; 1886 *a3 = 0; 1887 spritetype *pSprite = pPlayer->pSprite; 1888 int x = Cos(pSprite->ang)>>16; 1889 int y = Sin(pSprite->ang)>>16; 1890 int z = pPlayer->slope; 1891 int hit = HitScan(pSprite, pPlayer->zView, x, y, z, 0x10000040, 128); 1892 int hitDist = approxDist(pSprite->x-gHitInfo.hitx, pSprite->y-gHitInfo.hity)>>4; 1893 if (hitDist < 64) 1894 { 1895 switch (hit) 1896 { 1897 case 3: 1898 *a2 = gHitInfo.hitsprite; 1899 *a3 = sprite[*a2].extra; 1900 if (*a3 > 0 && sprite[*a2].statnum == kStatThing) 1901 { 1902 spritetype *pSprite = &sprite[*a2]; 1903 XSPRITE *pXSprite = &xsprite[*a3]; 1904 if (pSprite->type == kThingDroppedLifeLeech) 1905 { 1906 if ((gGameOptions.nGameType >= kGameTypeBloodBath) && findDroppedLeech(pPlayer, pSprite)) 1907 return -1; 1908 pXSprite->data4 = pPlayer->nPlayer; 1909 pXSprite->isTriggered = 0; 1910 } 1911 } 1912 if (*a3 > 0 && xsprite[*a3].Push) 1913 return 3; 1914 if (sprite[*a2].statnum == kStatDude) 1915 { 1916 spritetype *pSprite = &sprite[*a2]; 1917 XSPRITE *pXSprite = &xsprite[*a3]; 1918 int nMass = getDudeInfo(pSprite->type)->mass; 1919 if (nMass) 1920 { 1921 int t2 = divscale8(0xccccc, nMass); 1922 xvel[*a2] += mulscale16(x, t2); 1923 yvel[*a2] += mulscale16(y, t2); 1924 zvel[*a2] += mulscale16(z, t2); 1925 } 1926 if (pXSprite->Push && !pXSprite->state && !pXSprite->isTriggered) 1927 trTriggerSprite(*a2, pXSprite, kCmdSpritePush, pPlayer->nSprite); 1928 } 1929 break; 1930 case 0: 1931 case 4: 1932 *a2 = gHitInfo.hitwall; 1933 *a3 = wall[*a2].extra; 1934 if (*a3 > 0 && xwall[*a3].triggerPush) 1935 return 0; 1936 if (wall[*a2].nextsector >= 0) 1937 { 1938 *a2 = wall[*a2].nextsector; 1939 *a3 = sector[*a2].extra; 1940 if (*a3 > 0 && xsector[*a3].Wallpush) 1941 return 6; 1942 } 1943 break; 1944 case 1: 1945 case 2: 1946 *a2 = gHitInfo.hitsect; 1947 *a3 = sector[*a2].extra; 1948 if (*a3 > 0 && xsector[*a3].Push) 1949 return 6; 1950 break; 1951 } 1952 } 1953 *a2 = pSprite->sectnum; 1954 *a3 = sector[*a2].extra; 1955 if (*a3 > 0 && xsector[*a3].Push) 1956 return 6; 1957 return -1; 1958 } 1959 1960 inline void playerDropHand(PLAYER *pPlayer) 1961 { 1962 spritetype *pSprite2 = actSpawnDude(pPlayer->pSprite, kDudeHand, pPlayer->pSprite->clipdist<<1, 0); 1963 pSprite2->ang = (pPlayer->pSprite->ang+1024)&2047; 1964 int nSprite = pPlayer->pSprite->index; 1965 int x = Cos(pPlayer->pSprite->ang)>>16; 1966 int y = Sin(pPlayer->pSprite->ang)>>16; 1967 xvel[pSprite2->index] = xvel[nSprite] + mulscale14(0x155555, x); 1968 yvel[pSprite2->index] = yvel[nSprite] + mulscale14(0x155555, y); 1969 zvel[pSprite2->index] = zvel[nSprite]; 1970 pPlayer->hand = 0; 1971 } 1972 1973 void ProcessInput(PLAYER *pPlayer) 1974 { 1975 spritetype *pSprite = pPlayer->pSprite; 1976 XSPRITE *pXSprite = pPlayer->pXSprite; 1977 int nSprite = pPlayer->nSprite; 1978 POSTURE *pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture]; 1979 GINPUT *pInput = &pPlayer->input; 1980 1981 if (pPlayer == gMe && numplayers == 1) 1982 { 1983 gViewAngleAdjust = 0.f; 1984 gViewLookRecenter = false; 1985 gViewLookAdjust = 0.f; 1986 } 1987 1988 pPlayer->isRunning = gProfile[pPlayer->nPlayer].nWeaponHBobbing == 2 ? pInput->syncFlags.run : 0; // v1.0x weapon swaying 1989 if (pInput->buttonFlags.byte || pInput->forward || pInput->strafe || pInput->q16turn) 1990 pPlayer->restTime = 0; 1991 else if (pPlayer->restTime >= 0) 1992 pPlayer->restTime += kTicsPerFrame; 1993 if (VanillaMode() || pXSprite->health == 0) 1994 WeaponProcess(pPlayer); 1995 if (pXSprite->health == 0) 1996 { 1997 char bSeqStat = playerSeqPlaying(pPlayer, 16); 1998 if (pPlayer->fraggerId != -1) 1999 { 2000 pPlayer->angold = pSprite->ang = getangle(sprite[pPlayer->fraggerId].x - pSprite->x, sprite[pPlayer->fraggerId].y - pSprite->y); 2001 pPlayer->q16ang = fix16_from_int(pSprite->ang); 2002 } 2003 pPlayer->deathTime += kTicsPerFrame; 2004 if (!bSeqStat) 2005 { 2006 if (VanillaMode()) 2007 pPlayer->q16horiz = fix16_from_int(mulscale16(0x8000-(Cos(ClipHigh(pPlayer->deathTime*8, 1024))>>15), 120)); 2008 else 2009 pPlayer->q16horiz = mulscale16(0x8000-(Cos(ClipHigh(pPlayer->deathTime*8, 1024))>>15), F16(120)); 2010 } 2011 if (pPlayer->curWeapon) 2012 pInput->newWeapon = pPlayer->curWeapon; 2013 if (pInput->keyFlags.action || pInput->keyFlags.useItem) 2014 { 2015 char bAllowRespawn = 1; 2016 const char bSpectator = (gGameOptions.uNetGameFlags&kNetGameFlagSpectatingAllow) && !strncmp(gProfile[pPlayer->nPlayer].name, "spectator", MAXPLAYERNAME); 2017 if ((gGameOptions.nGameType == kGameTypeCoop) && (gGameOptions.uNetGameFlags&kNetGameFlagScoresLimitMask)) 2018 bAllowRespawn = gPlayerCoopLives[pPlayer->nPlayer] < gPlayerRoundScoreLimit; 2019 if (bSeqStat) 2020 { 2021 if (pPlayer->deathTime > 360) 2022 seqSpawn(pPlayer->pDudeInfo->seqStartID+14, 3, pPlayer->pSprite->extra, nPlayerSurviveClient); 2023 } 2024 else if (!gDemo.bPlaying && (seqGetStatus(3, pPlayer->pSprite->extra) < 0) && bAllowRespawn && !bSpectator) 2025 { 2026 if (pPlayer->pSprite) 2027 { 2028 if ((gGameOptions.nGameType != kGameTypeSinglePlayer) && !(gGameOptions.uNetGameFlags&kNetGameFlagCalebOnly) && gProfile[pPlayer->nPlayer].nModel && !VanillaMode()) // set to unused thing, so it can be easily replaced with cultist tile 2029 { 2030 pPlayer->pSprite->inittype = pPlayer->pSprite->type; 2031 pPlayer->pSprite->type = kThingVoodooHead; 2032 } 2033 else 2034 pPlayer->pSprite->type = kThingBloodChunks; 2035 } 2036 actPostSprite(pPlayer->nSprite, kStatThing); 2037 seqSpawn(pPlayer->pDudeInfo->seqStartID+15, 3, pPlayer->pSprite->extra, -1); 2038 playerReset(pPlayer); 2039 playerRestoreItems(pPlayer); 2040 if ((gGameOptions.nGameType == kGameTypeSinglePlayer) && (numplayers == 1)) // if single-player 2041 { 2042 if (gDemo.bRecording) 2043 gDemo.Close(); 2044 pInput->keyFlags.restart = 1; 2045 if (gRestoreLastSave && pInput->keyFlags.action) 2046 return; // return so ProcessFrame() can load last save if action was pressed 2047 } 2048 else 2049 playerStart(pPlayer->nPlayer); 2050 } 2051 else if (!gDemo.bPlaying && (seqGetStatus(3, pPlayer->pSprite->extra) < 0) && !bAllowRespawn && !bSpectator) // all players are dead, restart level 2052 { 2053 char bAllPlayersDead = 1; 2054 for (int i = connecthead; i >= 0 && bAllPlayersDead; i = connectpoint2[i]) 2055 { 2056 if (gPlayerCoopLives[i] < gPlayerRoundScoreLimit) 2057 bAllPlayersDead = 0; 2058 } 2059 if (bAllPlayersDead) // trigger level restart 2060 { 2061 levelRestart(); 2062 gPacketStartGame.episodeId = gGameOptions.nEpisode; // copy level info to packet struct so StartLevel() won't put us back to the start of episode 2063 gPacketStartGame.levelId = gGameOptions.nLevel; 2064 } 2065 else if (pPlayer == gMe) // switch to next player if attempting to respawn while there are still players alive 2066 BUTTONSET(gamefunc_See_Coop_View, 1); 2067 } 2068 pInput->keyFlags.useItem = 0; 2069 pInput->keyFlags.action = 0; 2070 } 2071 return; 2072 } 2073 if (pPlayer->posture == kPostureSwim || gFlyMode) 2074 { 2075 int x = Cos(pSprite->ang); 2076 int y = Sin(pSprite->ang); 2077 if (pInput->forward) 2078 { 2079 int forward = pInput->forward; 2080 if (forward > 0) 2081 forward = mulscale8(pPosture->frontAccel, forward); 2082 else 2083 forward = mulscale8(pPosture->backAccel, forward); 2084 xvel[nSprite] += mulscale30(forward, x); 2085 yvel[nSprite] += mulscale30(forward, y); 2086 } 2087 if (pInput->strafe) 2088 { 2089 int strafe = pInput->strafe; 2090 strafe = mulscale8(pPosture->sideAccel, strafe); 2091 xvel[nSprite] += mulscale30(strafe, y); 2092 yvel[nSprite] -= mulscale30(strafe, x); 2093 } 2094 } 2095 else if (pXSprite->height < 256) 2096 { 2097 int speed = 0x10000; 2098 if (pXSprite->height > 0) 2099 speed -= divscale16(pXSprite->height, 256); 2100 int x = Cos(pSprite->ang); 2101 int y = Sin(pSprite->ang); 2102 if (pInput->forward) 2103 { 2104 int forward = pInput->forward; 2105 if (forward > 0) 2106 forward = mulscale8(pPosture->frontAccel, forward); 2107 else 2108 forward = mulscale8(pPosture->backAccel, forward); 2109 if (pXSprite->height) 2110 forward = mulscale16(forward, speed); 2111 xvel[nSprite] += mulscale30(forward, x); 2112 yvel[nSprite] += mulscale30(forward, y); 2113 } 2114 if (pInput->strafe) 2115 { 2116 int strafe = pInput->strafe; 2117 strafe = mulscale8(pPosture->sideAccel, strafe); 2118 if (pXSprite->height) 2119 strafe = mulscale16(strafe, speed); 2120 xvel[nSprite] += mulscale30(strafe, y); 2121 yvel[nSprite] -= mulscale30(strafe, x); 2122 } 2123 } 2124 if (pInput->q16turn) 2125 { 2126 if (VanillaMode()) 2127 pPlayer->q16ang = ((pPlayer->q16ang&0x7ff0000)+(pInput->q16turn&0x7ff0000))&0x7ffffff; 2128 else 2129 pPlayer->q16ang = (pPlayer->q16ang+pInput->q16turn)&0x7ffffff; 2130 } 2131 if (pInput->keyFlags.spin180) 2132 { 2133 if (!pPlayer->spin) 2134 pPlayer->spin = -kAng180; 2135 pInput->keyFlags.spin180 = 0; 2136 } 2137 if (pPlayer->spin < 0) 2138 { 2139 const int speed = (pPlayer->posture == kPostureSwim) ? 64 : 128; 2140 pPlayer->spin = min(pPlayer->spin+speed, 0); 2141 pPlayer->q16ang += fix16_from_int(speed); 2142 if (pPlayer == gMe && numplayers == 1) 2143 gViewAngleAdjust += float(ClipHigh(-pPlayer->spin, speed)); // don't overturn when nearing end of spin 2144 } 2145 if (pPlayer == gMe && numplayers == 1) 2146 { 2147 int nDeltaAngle = pSprite->ang - pPlayer->angold; 2148 if (nDeltaAngle > kAng180) // handle unsigned overflow 2149 nDeltaAngle += -kAng360; 2150 else if (nDeltaAngle < -kAng180) 2151 nDeltaAngle += kAng360; 2152 gViewAngleAdjust += float(nDeltaAngle); 2153 } 2154 pPlayer->q16ang = (pPlayer->q16ang+fix16_from_int(pSprite->ang-pPlayer->angold))&0x7ffffff; 2155 pPlayer->angold = pSprite->ang = fix16_to_int(pPlayer->q16ang); 2156 if (!pInput->buttonFlags.jump) 2157 pPlayer->cantJump = 0; 2158 2159 switch (pPlayer->posture) { 2160 case kPostureSwim: 2161 if (pInput->buttonFlags.jump) 2162 zvel[nSprite] -= pPosture->normalJumpZ;//0x5b05; 2163 if (pInput->buttonFlags.crouch) 2164 zvel[nSprite] += pPosture->normalJumpZ;//0x5b05; 2165 break; 2166 case kPostureCrouch: 2167 if (!pInput->buttonFlags.crouch) 2168 pPlayer->posture = kPostureStand; 2169 break; 2170 default: 2171 if (gFlyMode) 2172 break; 2173 if (!pPlayer->cantJump && pInput->buttonFlags.jump && pXSprite->height == 0) { 2174 #ifdef NOONE_EXTENSIONS 2175 if ((packItemActive(pPlayer, kPackJumpBoots) && pPosture->pwupJumpZ != 0) || pPosture->normalJumpZ != 0) 2176 #endif 2177 sfxPlay3DSound(pSprite, 700, 0, 0); 2178 2179 if (packItemActive(pPlayer, kPackJumpBoots)) zvel[nSprite] = pPosture->pwupJumpZ; //-0x175555; 2180 else zvel[nSprite] = pPosture->normalJumpZ; //-0xbaaaa; 2181 pPlayer->cantJump = 1; 2182 } 2183 2184 if (pInput->buttonFlags.crouch) 2185 pPlayer->posture = kPostureCrouch; 2186 break; 2187 } 2188 if (pInput->keyFlags.action) 2189 { 2190 int a2, a3; 2191 int hit = ActionScan(pPlayer, &a2, &a3); 2192 switch (hit) 2193 { 2194 case 6: 2195 if (a3 > 0 && a3 < kMaxXSectors) 2196 { 2197 XSECTOR *pXSector = &xsector[a3]; 2198 int key = pXSector->Key; 2199 if (pXSector->locked && pPlayer == gMe) 2200 { 2201 viewSetMessage("It's locked"); 2202 sndStartSample(3062, 255, 2, 0); 2203 } 2204 if (!key || pPlayer->hasKey[key]) 2205 trTriggerSector(a2, pXSector, kCmdSpritePush, pPlayer->nSprite); 2206 else if (pPlayer == gMe) 2207 { 2208 viewSetMessage("That requires a key."); 2209 sndStartSample(3063, 255, 2, 0); 2210 } 2211 } 2212 break; 2213 case 0: 2214 { 2215 XWALL *pXWall = &xwall[a3]; 2216 int key = pXWall->key; 2217 if (pXWall->locked && pPlayer == gMe) 2218 { 2219 viewSetMessage("It's locked"); 2220 sndStartSample(3062, 255, 2, 0); 2221 } 2222 if (!key || pPlayer->hasKey[key]) 2223 { 2224 trTriggerWall(a2, pXWall, kCmdWallPush, pPlayer->nSprite); 2225 } 2226 else if (pPlayer == gMe) 2227 { 2228 viewSetMessage("That requires a key."); 2229 sndStartSample(3063, 255, 2, 0); 2230 } 2231 break; 2232 } 2233 case 3: 2234 { 2235 XSPRITE *pXSprite = &xsprite[a3]; 2236 int key = pXSprite->key; 2237 if (pXSprite->locked && pPlayer == gMe && pXSprite->lockMsg) 2238 trTextOver(pXSprite->lockMsg); 2239 if (!key || pPlayer->hasKey[key]) 2240 trTriggerSprite(a2, pXSprite, kCmdSpritePush, pPlayer->nSprite); 2241 else if (pPlayer == gMe) 2242 { 2243 viewSetMessage("That requires a key."); 2244 sndStartSample(3063, 255, 2, 0); 2245 } 2246 break; 2247 } 2248 } 2249 if (pPlayer->handTime > 0) 2250 pPlayer->handTime = ClipLow(pPlayer->handTime-kTicsPerFrame*(6-gGameOptions.nDifficulty), 0); 2251 if (pPlayer->handTime <= 0 && pPlayer->hand) // if hand enemy successfully thrown off 2252 playerDropHand(pPlayer); 2253 pInput->keyFlags.action = 0; 2254 } 2255 if (VanillaMode(true)) 2256 { 2257 if (pInput->keyFlags.lookCenter && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown) 2258 { 2259 if (pPlayer->q16look < 0) 2260 pPlayer->q16look = fix16_min(pPlayer->q16look+F16(4), F16(0)); 2261 if (pPlayer->q16look > 0) 2262 pPlayer->q16look = fix16_max(pPlayer->q16look-F16(4), F16(0)); 2263 if (!pPlayer->q16look) 2264 pInput->keyFlags.lookCenter = 0; 2265 } 2266 else 2267 { 2268 if (pInput->buttonFlags.lookUp) 2269 pPlayer->q16look = fix16_min(pPlayer->q16look+F16(4), F16(60)); 2270 if (pInput->buttonFlags.lookDown) 2271 pPlayer->q16look = fix16_max(pPlayer->q16look-F16(4), F16(-60)); 2272 } 2273 pPlayer->q16look = fix16_clamp(pPlayer->q16look+pInput->q16mlook, F16(-60), F16(60)); 2274 if (pPlayer->q16look > 0) 2275 pPlayer->q16horiz = fix16_from_int(mulscale30(120, Sin(fix16_to_int(pPlayer->q16look)<<3))); 2276 else if (pPlayer->q16look < 0) 2277 pPlayer->q16horiz = fix16_from_int(mulscale30(180, Sin(fix16_to_int(pPlayer->q16look)<<3))); 2278 else 2279 pPlayer->q16horiz = 0; 2280 } 2281 else 2282 { 2283 CONSTEXPR int upAngle = 289; 2284 CONSTEXPR int downAngle = -347; 2285 CONSTEXPR double lookStepUp = 4.0*upAngle/60.0; 2286 CONSTEXPR double lookStepDown = -4.0*downAngle/60.0; 2287 if (pInput->keyFlags.lookCenter && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown) 2288 { 2289 if (pPlayer->q16look < 0) 2290 pPlayer->q16look = fix16_min(pPlayer->q16look+F16(lookStepDown), F16(0)); 2291 if (pPlayer->q16look > 0) 2292 pPlayer->q16look = fix16_max(pPlayer->q16look-F16(lookStepUp), F16(0)); 2293 if (!pPlayer->q16look) 2294 pInput->keyFlags.lookCenter = 0; 2295 } 2296 else 2297 { 2298 if (pInput->buttonFlags.lookUp) 2299 pPlayer->q16look = fix16_min(pPlayer->q16look+F16(lookStepUp), F16(upAngle)); 2300 if (pInput->buttonFlags.lookDown) 2301 pPlayer->q16look = fix16_max(pPlayer->q16look-F16(lookStepDown), F16(downAngle)); 2302 } 2303 if (pPlayer == gMe && numplayers == 1) 2304 { 2305 if (pInput->buttonFlags.lookUp) 2306 { 2307 gViewLookAdjust += float(lookStepUp); 2308 } 2309 if (pInput->buttonFlags.lookDown) 2310 { 2311 gViewLookAdjust -= float(lookStepDown); 2312 } 2313 gViewLookRecenter = pInput->keyFlags.lookCenter && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown; 2314 } 2315 pPlayer->q16look = fix16_clamp(pPlayer->q16look+(pInput->q16mlook<<3), F16(downAngle), F16(upAngle)); 2316 pPlayer->q16horiz = fix16_from_float(100.f*tanf(fix16_to_float(pPlayer->q16look)*fPI/1024.f)); 2317 } 2318 int nSector = pSprite->sectnum; 2319 int florhit = gSpriteHit[pSprite->extra].florhit & 0xc000; 2320 char va; 2321 if (pXSprite->height < 16 && (florhit == 0x4000 || florhit == 0)) 2322 va = 1; 2323 else 2324 va = 0; 2325 if (va && (sector[nSector].floorstat&2)) 2326 { 2327 int z1 = getflorzofslope(nSector, pSprite->x, pSprite->y); 2328 int x2 = pSprite->x+mulscale30(64, Cos(pSprite->ang)); 2329 int y2 = pSprite->y+mulscale30(64, Sin(pSprite->ang)); 2330 short nSector2 = nSector; 2331 updatesector(x2, y2, &nSector2); 2332 if (nSector2 == nSector) 2333 { 2334 int z2 = getflorzofslope(nSector2, x2, y2); 2335 pPlayer->q16slopehoriz = interpolate(pPlayer->q16slopehoriz, fix16_from_int(z1-z2)>>3, 0x4000, 1); 2336 } 2337 } 2338 else 2339 { 2340 pPlayer->q16slopehoriz = interpolate(pPlayer->q16slopehoriz, F16(0), 0x4000, 1); 2341 if (klabs(pPlayer->q16slopehoriz) < 4) 2342 pPlayer->q16slopehoriz = 0; 2343 } 2344 pPlayer->slope = (-fix16_to_int(pPlayer->q16horiz))<<7; 2345 if (!VanillaMode()) 2346 WeaponProcess(pPlayer); 2347 if (pInput->keyFlags.prevItem) 2348 { 2349 pInput->keyFlags.prevItem = 0; 2350 packPrevItem(pPlayer); 2351 } 2352 if (pInput->keyFlags.nextItem) 2353 { 2354 pInput->keyFlags.nextItem = 0; 2355 packNextItem(pPlayer); 2356 } 2357 if (pInput->keyFlags.useItem) 2358 { 2359 pInput->keyFlags.useItem = 0; 2360 if (pPlayer->packSlots[pPlayer->packItemId].curAmount > 0) 2361 packUseItem(pPlayer, pPlayer->packItemId); 2362 } 2363 if (pInput->useFlags.useBeastVision) 2364 { 2365 pInput->useFlags.useBeastVision = 0; 2366 if (pPlayer->packSlots[kPackBeastVision].curAmount > 0) 2367 packUseItem(pPlayer, kPackBeastVision); 2368 } 2369 if (pInput->useFlags.useCrystalBall) 2370 { 2371 pInput->useFlags.useCrystalBall = 0; 2372 if (pPlayer->packSlots[kPackCrystalBall].curAmount > 0) 2373 packUseItem(pPlayer, kPackCrystalBall); 2374 } 2375 if (pInput->useFlags.useJumpBoots) 2376 { 2377 pInput->useFlags.useJumpBoots = 0; 2378 if (pPlayer->packSlots[kPackJumpBoots].curAmount > 0) 2379 packUseItem(pPlayer, kPackJumpBoots); 2380 } 2381 if (pInput->useFlags.useMedKit) 2382 { 2383 pInput->useFlags.useMedKit = 0; 2384 if (pPlayer->packSlots[kPackMedKit].curAmount > 0) 2385 packUseItem(pPlayer, kPackMedKit); 2386 } 2387 if (pInput->keyFlags.holsterWeapon) 2388 { 2389 pInput->keyFlags.holsterWeapon = 0; 2390 if (gGameOptions.uNetGameFlags&kNetGameFlagNoHolstering) 2391 { 2392 if (VanillaMode() || (pPlayer == gMe)) 2393 viewSetMessage("Holstering is off in this match"); 2394 } 2395 else if (pPlayer->curWeapon) 2396 { 2397 WeaponLower(pPlayer); 2398 if (VanillaMode() || (pPlayer == gMe)) 2399 viewSetMessage("Holstering weapon"); 2400 } 2401 } 2402 CheckPickUp(pPlayer); 2403 } 2404 2405 void playerProcess(PLAYER *pPlayer) 2406 { 2407 spritetype *pSprite = pPlayer->pSprite; 2408 int nSprite = pPlayer->nSprite; 2409 int nXSprite = pSprite->extra; 2410 XSPRITE *pXSprite = pPlayer->pXSprite; 2411 POSTURE* pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture]; 2412 powerupProcess(pPlayer); 2413 int top, bottom; 2414 GetSpriteExtents(pSprite, &top, &bottom); 2415 int dzb = (bottom-pSprite->z)/4; 2416 int dzt = (pSprite->z-top)/4; 2417 int dw = pSprite->clipdist<<2; 2418 if (!gNoClip) 2419 { 2420 short nSector = pSprite->sectnum; 2421 if (pushmove_old(&pSprite->x, &pSprite->y, &pSprite->z, &nSector, dw, dzt, dzb, CLIPMASK0) == -1) 2422 actDamageSprite(nSprite, pSprite, kDamageFall, 500<<4); 2423 if (pSprite->sectnum != nSector) 2424 { 2425 if (nSector == -1) 2426 { 2427 nSector = pSprite->sectnum; 2428 actDamageSprite(nSprite, pSprite, kDamageFall, 500<<4); 2429 } 2430 dassert(nSector >= 0 && nSector < kMaxSectors); 2431 ChangeSpriteSect(nSprite, nSector); 2432 } 2433 } 2434 ProcessInput(pPlayer); 2435 int nSpeed = approxDist(xvel[nSprite], yvel[nSprite]); 2436 if (pPlayer == gMe) 2437 gPlayerSpeed = nSpeed; 2438 pPlayer->zViewVel = interpolate(pPlayer->zViewVel, zvel[nSprite], 0x7000, 1); 2439 int dz = pPlayer->pSprite->z-pPosture->eyeAboveZ-pPlayer->zView; 2440 if (dz > 0) 2441 pPlayer->zViewVel += mulscale16(dz<<8, 0xa000); 2442 else 2443 pPlayer->zViewVel += mulscale16(dz<<8, 0x1800); 2444 pPlayer->zView += pPlayer->zViewVel>>8; 2445 pPlayer->zWeaponVel = interpolate(pPlayer->zWeaponVel, zvel[nSprite], 0x5000, 1); 2446 dz = pPlayer->pSprite->z-pPosture->weaponAboveZ-pPlayer->zWeapon; 2447 if (dz > 0) 2448 pPlayer->zWeaponVel += mulscale16(dz<<8, 0x8000); 2449 else 2450 pPlayer->zWeaponVel += mulscale16(dz<<8, 0xc00); 2451 pPlayer->zWeapon += pPlayer->zWeaponVel>>8; 2452 pPlayer->bobPhase = ClipLow(pPlayer->bobPhase-kTicsPerFrame, 0); 2453 nSpeed >>= 16; 2454 if (pPlayer->posture == kPostureSwim) 2455 { 2456 pPlayer->bobAmp = (pPlayer->bobAmp+17)&2047; 2457 pPlayer->swayAmp = (pPlayer->swayAmp+17)&2047; 2458 pPlayer->bobHeight = mulscale30(pPosture->bobV*10, Sin(pPlayer->bobAmp*2)); 2459 pPlayer->bobWidth = mulscale30(pPosture->bobH*pPlayer->bobPhase, Sin(pPlayer->bobAmp-256)); 2460 pPlayer->swayHeight = mulscale30(pPosture->swayV*pPlayer->bobPhase, Sin(pPlayer->swayAmp*2)); 2461 pPlayer->swayWidth = mulscale30(pPosture->swayH*pPlayer->bobPhase, Sin(pPlayer->swayAmp-0x155)); 2462 } 2463 else 2464 { 2465 if (pXSprite->height < 256) 2466 { 2467 pPlayer->bobAmp = (pPlayer->bobAmp+pPosture->pace[pPlayer->isRunning]*4) & 2047; 2468 pPlayer->swayAmp = (pPlayer->swayAmp+(pPosture->pace[pPlayer->isRunning]*4)/2) & 2047; 2469 const int clampPhase = pPlayer->isRunning ? 60 : 30; 2470 if (pPlayer->bobPhase < clampPhase) 2471 pPlayer->bobPhase = ClipHigh(pPlayer->bobPhase+nSpeed, clampPhase); 2472 } 2473 pPlayer->bobHeight = mulscale30(pPosture->bobV*pPlayer->bobPhase, Sin(pPlayer->bobAmp*2)); 2474 pPlayer->bobWidth = mulscale30(pPosture->bobH*pPlayer->bobPhase, Sin(pPlayer->bobAmp-256)); 2475 pPlayer->swayHeight = mulscale30(pPosture->swayV*pPlayer->bobPhase, Sin(pPlayer->swayAmp*2)); 2476 pPlayer->swayWidth = mulscale30(pPosture->swayH*pPlayer->bobPhase, Sin(pPlayer->swayAmp-0x155)); 2477 } 2478 pPlayer->flickerEffect = 0; 2479 pPlayer->quakeEffect = ClipLow(pPlayer->quakeEffect-kTicsPerFrame, 0); 2480 pPlayer->tiltEffect = ClipLow(pPlayer->tiltEffect-kTicsPerFrame, 0); 2481 pPlayer->visibility = ClipLow(pPlayer->visibility-kTicsPerFrame, 0); 2482 pPlayer->painEffect = ClipLow(pPlayer->painEffect-kTicsPerFrame, 0); 2483 pPlayer->blindEffect = ClipLow(pPlayer->blindEffect-kTicsPerFrame, 0); 2484 pPlayer->pickupEffect = ClipLow(pPlayer->pickupEffect-kTicsPerFrame, 0); 2485 if (!pXSprite->health) 2486 { 2487 if (pPlayer->hand && EnemiesNBlood() && gGameOptions.nGameType != kGameTypeSinglePlayer && !VanillaMode()) 2488 playerDropHand(pPlayer); 2489 else if (!VanillaMode() || pPlayer == gMe) 2490 pPlayer->hand = 0; 2491 return; 2492 } 2493 pPlayer->isUnderwater = 0; 2494 if (pPlayer->posture == kPostureSwim) 2495 { 2496 pPlayer->isUnderwater = 1; 2497 int nSector = pSprite->sectnum; 2498 int nLink = gLowerLink[nSector]; 2499 if (nLink > 0 && (sprite[nLink].type == kMarkerLowGoo || sprite[nLink].type == kMarkerLowWater)) 2500 { 2501 if (getceilzofslope(nSector, pSprite->x, pSprite->y) > pPlayer->zView) 2502 pPlayer->isUnderwater = 0; 2503 } 2504 } 2505 if (!pPlayer->isUnderwater) 2506 { 2507 pPlayer->underwaterTime = 1200; 2508 pPlayer->chokeEffect = 0; 2509 if (packItemActive(pPlayer, kPackDivingSuit)) 2510 packUseItem(pPlayer, kPackDivingSuit); 2511 } 2512 int nType = kDudePlayer1-kDudeBase; 2513 switch (pPlayer->posture) 2514 { 2515 case kPostureSwim: 2516 seqSpawn(dudeInfo[nType].seqStartID+9, 3, nXSprite, -1); 2517 break; 2518 case kPostureCrouch: 2519 seqSpawn(dudeInfo[nType].seqStartID+10, 3, nXSprite, -1); 2520 break; 2521 default: 2522 if (!nSpeed) 2523 seqSpawn(dudeInfo[nType].seqStartID, 3, nXSprite, -1); 2524 else 2525 seqSpawn(dudeInfo[nType].seqStartID+8, 3, nXSprite, -1); 2526 break; 2527 } 2528 if (gGameOptions.uNetGameFlags&kNetGameFlagTimeLimitMask) // check every tick 2529 playerProcessRoundCheck(NULL); 2530 } 2531 2532 spritetype *playerFireMissile(PLAYER *pPlayer, int a2, int a3, int a4, int a5, int a6) 2533 { 2534 int zAimOffset = pPlayer->zWeapon-pPlayer->pSprite->z; 2535 if (WeaponsNotBlood() && !VanillaMode()) // adjust z height offset for pitch angle 2536 { 2537 CONSTEXPR int upAngle = 289; 2538 CONSTEXPR int downAngle = -347; 2539 zAimOffset -= fix16_clamp(pPlayer->q16look, F16(downAngle), F16(upAngle))>>13; 2540 } 2541 return actFireMissile(pPlayer->pSprite, a2, zAimOffset, a3, a4, a5, a6); 2542 } 2543 2544 spritetype * playerFireThing(PLAYER *pPlayer, int a2, int a3, int thingType, int a5) 2545 { 2546 dassert(thingType >= kThingBase && thingType < kThingMax); 2547 return actFireThing(pPlayer->pSprite, a2, pPlayer->zWeapon-pPlayer->pSprite->z, pPlayer->slope+a3, thingType, a5); 2548 } 2549 2550 void playerFrag(PLAYER *pKiller, PLAYER *pVictim) 2551 { 2552 dassert(pKiller != NULL); 2553 dassert(pVictim != NULL); 2554 2555 char buffer[128] = ""; 2556 int nKiller = pKiller->pSprite->type-kDudePlayer1; 2557 dassert(nKiller >= 0 && nKiller < kMaxPlayers); 2558 int nVictim = pVictim->pSprite->type-kDudePlayer1; 2559 dassert(nVictim >= 0 && nVictim < kMaxPlayers); 2560 const ClockTicks kKillingSpreeTime = kTicRate * 5; // five seconds window for kill sprees 2561 const char bKillingSpreeStopped = !VanillaMode() && (gGameOptions.nGameType >= kGameTypeBloodBath) && gMultiKill && (gMultiKillsFrags[nVictim] >= 5) && ((gFrameClock - gMultiKillsTicks[nVictim]) < kKillingSpreeTime); 2562 if (myconnectindex == connecthead) 2563 { 2564 sprintf(buffer, "frag %d killed %d\n", pKiller->nPlayer+1, pVictim->nPlayer+1); 2565 netBroadcastFrag(buffer); 2566 buffer[0] = 0; 2567 } 2568 if (nVictim == gAnnounceKillingSpreePlayer) // if victim is currently being announced for their kill spree, end it 2569 playerResetAnnounceKillingSpree(); 2570 if (nKiller == nVictim) 2571 { 2572 pKiller->fraggerId = -1; 2573 if (VanillaMode() || gGameOptions.nGameType != kGameTypeCoop) 2574 { 2575 pKiller->fragCount--; 2576 pKiller->fragInfo[nKiller]--; 2577 gMultiKillsFrags[nKiller] = 0; // reset multi kill counter 2578 } 2579 if (gGameOptions.nGameType == kGameTypeTeams) 2580 gPlayerScores[pKiller->teamId]--; 2581 int nMessage = Random(5); 2582 if (!gKillObituary) // always use generic suicide message instead of obituary 2583 nMessage = 4; 2584 const int nSound = !bKillingSpreeStopped ? gSuicide[nMessage].nSound : gKillingSpreeSuicide.nSound; 2585 const char* pzMessage = !bKillingSpreeStopped ? gSuicide[nMessage].pzMessage : gKillingSpreeSuicide.pzMessage; 2586 if (gMe->handTime <= 0) 2587 { 2588 if (!VanillaMode() && (gGameOptions.nGameType != kGameTypeSinglePlayer)) // use unused suicide messages for multiplayer 2589 sprintf(buffer, pzMessage, gProfile[nKiller].name); 2590 else if (pKiller == gMe) 2591 sprintf(buffer, "You killed yourself!"); 2592 if ((gGameOptions.nGameType != kGameTypeSinglePlayer) && (nSound >= 0) && (pKiller == gMe) && gKillObituary) 2593 sndStartSample(nSound, 255, 2, 0); 2594 } 2595 } 2596 else 2597 { 2598 char bKilledEnemy = 1; 2599 if (VanillaMode() || gGameOptions.nGameType != kGameTypeCoop) 2600 { 2601 pKiller->fragCount++; 2602 pKiller->fragInfo[nVictim]++; 2603 gMultiKillsFrags[nVictim] = 0; 2604 if (gKillMsg) // calculate kill message notification 2605 { 2606 if (pVictim == gMe) // if other player killed current player 2607 { 2608 playerResetKillMsg(); 2609 gPlayerLastKiller = nKiller; 2610 gPlayerLastVictim = nVictim; 2611 gPlayerKillMsgTicks = kTicRate * 5.5; 2612 } 2613 else if (pKiller == gMe) // if current player killed other player 2614 { 2615 playerResetKillMsg(); 2616 gPlayerLastVictim = nVictim; 2617 gPlayerKillMsgTicks = kTicRate * 2.5; 2618 } 2619 } 2620 } 2621 if (gGameOptions.nGameType == kGameTypeTeams) 2622 { 2623 if (pKiller->teamId == pVictim->teamId) // teammate was killed 2624 { 2625 gPlayerScores[pKiller->teamId]--; 2626 bKilledEnemy = 0; 2627 } 2628 else 2629 { 2630 gPlayerScores[pKiller->teamId]++; 2631 gPlayerScoreTicks[pKiller->teamId]+=120; 2632 } 2633 } 2634 if (!VanillaMode() && (gGameOptions.nGameType >= kGameTypeBloodBath) && bKilledEnemy) // calculate multi kill/killing spree for bloodbath/teams mode (ignore friendly fire) 2635 { 2636 const char bKillerAlive = pKiller->pXSprite->health > 0; 2637 const char bKillerSpreeActive = (gFrameClock - gMultiKillsTicks[nKiller]) < kKillingSpreeTime; 2638 if (bKillerSpreeActive && bKillerAlive) // if killed enemy within multi kill time window, reward point 2639 { 2640 gMultiKillsFrags[nKiller]++; 2641 if (gMultiKill && (pKiller != gMe) && ((gMultiKillsFrags[nKiller] % 5) == 0)) // announce killing spree every 5 kills 2642 { 2643 if ((gMultiKill == 2) && (gAnnounceKillingSpreeTicks == 0)) // only play sfx if multi kill alert setting is on, and if there isn't a multi kill alert already active 2644 sndStartSample("NOTBLOOD2", 128, -1, 22050); 2645 gAnnounceKillingSpreePlayer = nKiller; 2646 gAnnounceKillingSpreeTicks = kTicRate * 3; 2647 } 2648 } 2649 else 2650 { 2651 gMultiKillsFrags[nKiller] = 1; // reset multi kill to 1 (outside of time window) 2652 } 2653 gMultiKillsTicks[nKiller] = gFrameClock; 2654 } 2655 int nMessage = Random(25); 2656 if (!gKillObituary) // always use generic kill message instead of obituary 2657 nMessage = 10; 2658 const int nSound = !bKillingSpreeStopped ? gVictory[nMessage].nSound : gKillingSpreeFrag.nSound; 2659 const char* pzMessage = !bKillingSpreeStopped ? gVictory[nMessage].pzMessage : gKillingSpreeFrag.pzMessage; 2660 sprintf(buffer, pzMessage, gProfile[nKiller].name, gProfile[nVictim].name); 2661 if ((gGameOptions.nGameType != kGameTypeSinglePlayer) && (nSound >= 0) && (pKiller == gMe) && gKillObituary) 2662 sndStartSample(nSound, 255, 2, 0); 2663 } 2664 int nPal1 = 0, nPal2 = 0; 2665 if (gColorMsg && !VanillaMode()) // tint names within message string 2666 { 2667 nPal1 = playerColorPalMessage(pKiller->teamId); 2668 nPal2 = playerColorPalMessage(pVictim->teamId); 2669 } 2670 if ((buffer[0] != '\0') || VanillaMode()) 2671 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal1, nPal2); 2672 if (!VanillaMode() && (gGameOptions.nGameType >= kGameTypeBloodBath) && (nKiller != nVictim)) 2673 { 2674 if ((gGameOptions.nGameType != kGameTypeTeams) || (pKiller->teamId != pVictim->teamId)) // not a teammate 2675 { 2676 const int kDominationCount = 4; 2677 if (gDominationCount[nKiller][nVictim] < kDominationCount) // we haven't dominated this player yet 2678 { 2679 gDominationCount[nKiller][nVictim]++; 2680 if (gMultiKill && gDominationCount[nVictim][nKiller] >= kDominationCount) // we've gotten revenge, announce it 2681 { 2682 sprintf(buffer, "\r%s\r got REVENGE on \r%s\r!", gProfile[nKiller].name, gProfile[nVictim].name); 2683 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal1, nPal2); 2684 } 2685 } 2686 else if (gDominationCount[nKiller][nVictim] == kDominationCount) // only announce our domination once 2687 { 2688 gDominationCount[nKiller][nVictim]++; 2689 if (gMultiKill) 2690 { 2691 sprintf(buffer, "\r%s\r is DOMINATING \r%s\r!", gProfile[nKiller].name, gProfile[nVictim].name); 2692 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal1, nPal2); 2693 } 2694 } 2695 gDominationCount[nVictim][nKiller] = 0; // reset victim's count for killer 2696 } 2697 } 2698 } 2699 2700 void FragPlayer(PLAYER *pPlayer, int nSprite) 2701 { 2702 spritetype *pSprite = NULL; 2703 if (nSprite >= 0) 2704 pSprite = &sprite[nSprite]; 2705 if (pSprite && IsPlayerSprite(pSprite) && !gPlayerRoundEnding) 2706 { 2707 PLAYER *pKiller = &gPlayer[pSprite->type - kDudePlayer1]; 2708 playerFrag(pKiller, pPlayer); 2709 int nTeam1 = pKiller->teamId&1; 2710 int nTeam2 = pPlayer->teamId&1; 2711 if (nTeam1 == 0) 2712 { 2713 if (nTeam1 == nTeam2) 2714 evSend(0, 0, 15, kCmdToggle, pPlayer->nSprite); 2715 else 2716 evSend(0, 0, 16, kCmdToggle, pPlayer->nSprite); 2717 } 2718 else 2719 { 2720 if (nTeam1 == nTeam2) 2721 evSend(0, 0, 16, kCmdToggle, pPlayer->nSprite); 2722 else 2723 evSend(0, 0, 15, kCmdToggle, pPlayer->nSprite); 2724 } 2725 if ((pKiller == gMe) && (pKiller != pPlayer) && gSoundDingKill) // if we killed another player 2726 { 2727 static int nLastDinged = 0; 2728 if (klabs(nLastDinged - gLevelTime) > (kTicsPerSec>>2)) // only allow one ding every half second 2729 sndStartSample("NOTKILL", gSoundDingKillVol, -1, gSoundDingKillPitch); 2730 nLastDinged = gLevelTime; 2731 } 2732 } 2733 if (gGameOptions.uNetGameFlags&kNetGameFlagScoresLimitMask) 2734 playerProcessRoundCheck(pSprite && IsPlayerSprite(pSprite) ? &gPlayer[pSprite->type - kDudePlayer1] : pPlayer); 2735 } 2736 2737 void playerInitRoundCheck(void) 2738 { 2739 gPlayerRoundScoreLimit = gPlayerRoundTimeLimit = gPlayerRoundEnding = 0; 2740 gPlayerRoundLimitAnnounce = -1; 2741 if (gGameOptions.uNetGameFlags&kNetGameFlagScoresLimitMask) 2742 gPlayerRoundScoreLimit = (gGameOptions.uNetGameFlags&kNetGameFlagScoresLimitMask)>>kNetGameFlagScoresLimitBase; 2743 if (gGameOptions.uNetGameFlags&kNetGameFlagTimeLimitMask) // convert to minutes 2744 gPlayerRoundTimeLimit = ((gGameOptions.uNetGameFlags&kNetGameFlagTimeLimitMask)>>kNetGameFlagTimeLimitBase)*kTicsPerSec*60; 2745 memset(gPlayerCoopLives, 0, sizeof(gPlayerCoopLives)); 2746 } 2747 2748 void playerProcessRoundCheck(PLAYER *pPlayer) 2749 { 2750 if (gGameOptions.nGameType <= kGameTypeCoop) // frag limits do not apply here, return 2751 return; 2752 if (gPlayerRoundEnding) // we're already ending the round, don't trigger this again 2753 return; 2754 2755 if (!pPlayer) // if we're checking time limit only 2756 { 2757 if (gLevelTime <= gPlayerRoundTimeLimit) // if time limit has not been reached 2758 { 2759 const char *pzTimeMessage[7] = {"20 minutes left", "10 minutes left", "5 minutes left", "2 minutes left", "1 minute left", "30 seconds left", "10 seconds left"}; 2760 char nMessage; 2761 switch (gPlayerRoundTimeLimit-gLevelTime-1) 2762 { 2763 case kTicsPerSec*60*20: 2764 nMessage = 0; 2765 break; 2766 case kTicsPerSec*60*10: 2767 nMessage = 1; 2768 break; 2769 case kTicsPerSec*60*5: 2770 nMessage = 2; 2771 break; 2772 case kTicsPerSec*60*2: 2773 nMessage = 3; 2774 break; 2775 case kTicsPerSec*60: 2776 nMessage = 4; 2777 break; 2778 case kTicsPerSec*30: 2779 nMessage = 5; 2780 break; 2781 case kTicsPerSec*10: 2782 nMessage = 6; 2783 sndStartSample("NOTBLOOD3", 64, -1, 8259); // countdown alarm 2784 break; 2785 default: 2786 return; 2787 } 2788 if (nMessage == gPlayerRoundLimitAnnounce) // avoid triggering this multiple times per tick 2789 return; 2790 viewSetMessage(pzTimeMessage[nMessage], 8, MESSAGE_PRIORITY_NORMAL); 2791 gPlayerRoundLimitAnnounce = nMessage; 2792 return; 2793 } 2794 } 2795 2796 int nWinner = 0, nWinners = 0; 2797 int nScore[kMaxPlayers], nScoreMax = INT_MIN; 2798 for (int i = 0; i < kMaxPlayers; i++) 2799 nScore[i] = INT_MIN; 2800 2801 if (gGameOptions.nGameType == kGameTypeBloodBath) // count scores and check for ties 2802 { 2803 for (int p = connecthead; p >= 0; p = connectpoint2[p]) 2804 { 2805 nScore[p] = gPlayer[p].fragCount; 2806 if (nScoreMax < nScore[p]) 2807 nScoreMax = nScore[p], nWinner = p; 2808 } 2809 for (int p = connecthead; p >= 0; p = connectpoint2[p]) 2810 { 2811 if (nScoreMax == nScore[p]) 2812 nWinners++; 2813 } 2814 } 2815 else if (gGameOptions.nGameType == kGameTypeTeams) 2816 { 2817 for (int i = 0; i < 2; i++) 2818 { 2819 nScore[i] = gPlayerScores[i]; 2820 if (nScoreMax < nScore[i]) 2821 nScoreMax = nScore[i], nWinner = i; 2822 } 2823 for (int i = 0; i < 2; i++) 2824 { 2825 if (nScoreMax == nScore[i]) 2826 nWinners++; 2827 } 2828 } 2829 2830 char buffer[80] = "Ending round..."; 2831 int nPal = 0; 2832 if (pPlayer && (nScoreMax < gPlayerRoundScoreLimit)) // if frag/score limit has not been reached (and not checking time limit) 2833 { 2834 const int nPlayerScore = gGameOptions.nGameType == kGameTypeBloodBath ? pPlayer->fragCount : gPlayerScores[pPlayer->teamId]; 2835 const int nScoreRemaining = gPlayerRoundScoreLimit-nPlayerScore; 2836 if (nPlayerScore < 0) 2837 return; 2838 if (nScoreRemaining == 1 && pPlayer != gMe) // announce player's last point to all players 2839 { 2840 if (gGameOptions.nGameType == kGameTypeBloodBath) 2841 sprintf(buffer, "\r%s\r is on their last frag!", gProfile[pPlayer->nPlayer].name); 2842 else 2843 sprintf(buffer, "\r%s\r is on their last score!", pPlayer->teamId ? "Red Team" : "Blue Team"); 2844 nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 2845 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal); 2846 } 2847 else if (pPlayer == gMe) // only report current player's progress 2848 { 2849 switch (nScoreRemaining) 2850 { 2851 case 50: 2852 case 25: 2853 case 10: 2854 case 5: 2855 case 1: 2856 sprintf(buffer, "%d %s%s left", nScoreRemaining, gGameOptions.nGameType == kGameTypeBloodBath ? "frag" : "point", nScoreRemaining > 1 ? "s" : ""); 2857 viewSetMessage(buffer, 8, MESSAGE_PRIORITY_NORMAL); 2858 break; 2859 } 2860 } 2861 return; 2862 } 2863 2864 if (nWinners > 1) // if there is more than one winner, count as tie 2865 { 2866 if (gGameOptions.nGameType == kGameTypeTeams) 2867 sprintf(buffer, "Both teams tied score!"); 2868 else 2869 sprintf(buffer, "It's a tie of %d!", nWinners); 2870 } 2871 else if (gGameOptions.nGameType == kGameTypeBloodBath) 2872 { 2873 sprintf(buffer, "\r%s\r is the winner!", gProfile[nWinner].name); 2874 nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(gPlayer[nWinner].teamId) : 0; 2875 } 2876 else if (gGameOptions.nGameType == kGameTypeTeams) 2877 { 2878 sprintf(buffer, "\r%s\r is the winner!", nWinner ? "Red Team" : "Blue Team"); 2879 nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(nWinner) : 0; 2880 } 2881 viewDrawWinner(buffer, nPal); 2882 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal); 2883 gPlayerRoundEnding = 1; 2884 gPacketStartGame.uNetGameFlags |= kNetGameFlagNoLevelExit; // prevent normal level exits from triggering 2885 } 2886 2887 int playerDamageArmor(PLAYER *pPlayer, DAMAGE_TYPE nType, int nDamage) 2888 { 2889 DAMAGEINFO *pDamageInfo = &damageInfo[nType]; 2890 int nArmorType = pDamageInfo->at0; 2891 if (nArmorType >= 0 && pPlayer->armor[nArmorType]) 2892 { 2893 #if 0 2894 int vbp = (nDamage*7)/8-nDamage/4; 2895 int v8 = pPlayer->at33e[nArmorType]; 2896 int t = nDamage/4 + vbp * v8 / 3200; 2897 v8 -= t; 2898 #endif 2899 int v8 = pPlayer->armor[nArmorType]; 2900 int t = scale(v8, 0, 3200, nDamage/4, (nDamage*7)/8); 2901 v8 -= t; 2902 nDamage -= t; 2903 pPlayer->armor[nArmorType] = ClipLow(v8, 0); 2904 } 2905 return nDamage; 2906 } 2907 2908 spritetype *playerDropFlag(PLAYER *pPlayer, int a2) 2909 { 2910 int nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 2911 char buffer[80]; 2912 spritetype *pSprite = NULL; 2913 switch (a2) 2914 { 2915 case kItemFlagA: 2916 pPlayer->hasFlag &= ~1; 2917 pSprite = actDropObject(pPlayer->pSprite, kItemFlagA); 2918 if (pSprite) 2919 pSprite->owner = pPlayer->used2[0]; 2920 gBlueFlagDropped = true; 2921 sprintf(buffer, "\r%s\r dropped \rBlue Flag\r", gProfile[pPlayer->nPlayer].name); 2922 sndStartSample(8005, 255, 2, 0); 2923 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagBluePal); 2924 break; 2925 case kItemFlagB: 2926 pPlayer->hasFlag &= ~2; 2927 pSprite = actDropObject(pPlayer->pSprite, kItemFlagB); 2928 if (pSprite) 2929 pSprite->owner = pPlayer->used2[1]; 2930 gRedFlagDropped = true; 2931 sprintf(buffer, "\r%s\r dropped \rRed Flag\r", gProfile[pPlayer->nPlayer].name); 2932 sndStartSample(8004, 255, 2, 0); 2933 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, kFlagRedPal); 2934 break; 2935 } 2936 return pSprite; 2937 } 2938 2939 #define kInvulSteps 8 2940 2941 const int invulTimers[kInvulSteps] = 2942 { 2943 int(kTicRate/6*6.0), // no health (1 full second) 2944 int(kTicRate/6*6.0), 2945 int(kTicRate/6*5.0), 2946 int(kTicRate/6*4.0), 2947 int(kTicRate/6*3.5), 2948 int(kTicRate/6*3.0), // full health (half a second) 2949 int(kTicRate/6*3.0), 2950 int(kTicRate/6*3.0) 2951 }; 2952 2953 int playerDamageSprite(int nSource, PLAYER *pPlayer, DAMAGE_TYPE nDamageType, int nDamage) 2954 { 2955 dassert(nSource < kMaxSprites); 2956 dassert(pPlayer != NULL); 2957 if (pPlayer->damageControl[nDamageType]) 2958 return 0; 2959 if (gGameOptions.nDamageInvul && !VanillaMode()) // if invul timer option is active 2960 { 2961 if ((nDamageType == kDamageBullet) || (nDamageType == kDamageSpirit) || (nDamageType == kDamageTesla)) // only apply invulnerability for bullet/spirit/tesla damage 2962 { 2963 const DUDEINFO *pDudeInfo = getDudeInfo(pPlayer->pSprite->type); 2964 const XSPRITE *pXSprite = pPlayer->pXSprite; 2965 const int nHealth = clamp(pXSprite->health / ((pDudeInfo->startHealth<<4)>>3), 0, kInvulSteps-1); // divide health into invul array range (0-7) 2966 const int nSkill = gGameOptions.nDamageInvul-1; 2967 const int nInvulTicks = ((invulTimers[nHealth]/4) * (nSkill+1))>>1; // scale invul ticks depending on current difficulty 2968 const char bInvulState = pPlayer->invulTime > (gFrameClock - nInvulTicks); 2969 if ((pPlayer->invulTime != gFrameClock) && bInvulState) // if invulnerability timer has not lapsed for difficulty, bypass damage calculation 2970 return 0; 2971 } 2972 if ((nDamageType != kDamageBurn) && (nDamageType != kDamageDrown)) // do not update the invul timer on burn or drown damage 2973 pPlayer->invulTime = (int)gFrameClock; 2974 } 2975 nDamage = playerDamageArmor(pPlayer, nDamageType, nDamage); 2976 pPlayer->painEffect = ClipHigh(pPlayer->painEffect+(nDamage>>3), 600); 2977 if (pPlayer == gMe) 2978 ctrlJoystickRumble(pPlayer->painEffect); 2979 2980 spritetype *pSprite = pPlayer->pSprite; 2981 XSPRITE *pXSprite = pPlayer->pXSprite; 2982 int nXSprite = pSprite->extra; 2983 int nXSector = sector[pSprite->sectnum].extra; 2984 DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type); 2985 int nDeathSeqID = -1; 2986 int nKneelingPlayer = -1; 2987 int nSprite = pSprite->index; 2988 char va = playerSeqPlaying(pPlayer, 16); 2989 if (!pXSprite->health) 2990 { 2991 if (va) 2992 { 2993 switch (nDamageType) 2994 { 2995 case kDamageSpirit: 2996 nDeathSeqID = 18; 2997 sfxPlay3DSound(pSprite, 716, 0, 0); 2998 break; 2999 case kDamageExplode: 3000 GibSprite(pSprite, GIBTYPE_7, NULL, NULL); 3001 GibSprite(pSprite, GIBTYPE_15, NULL, NULL); 3002 pPlayer->pSprite->cstat |= 32768; 3003 nDeathSeqID = 17; 3004 break; 3005 default: 3006 { 3007 int top, bottom; 3008 GetSpriteExtents(pSprite, &top, &bottom); 3009 CGibPosition gibPos(pSprite->x, pSprite->y, top); 3010 CGibVelocity gibVel(xvel[pSprite->index]>>1, yvel[pSprite->index]>>1, -0xccccc); 3011 GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel); 3012 GibSprite(pSprite, GIBTYPE_7, NULL, NULL); 3013 fxSpawnBlood(pSprite, nDamage<<4); 3014 fxSpawnBlood(pSprite, nDamage<<4); 3015 nDeathSeqID = 17; 3016 break; 3017 } 3018 } 3019 } 3020 } 3021 else 3022 { 3023 int nHealth = pXSprite->health-nDamage; 3024 pXSprite->health = ClipLow(nHealth, 0); 3025 if (pXSprite->health > 0 && pXSprite->health < 16) 3026 { 3027 nDamageType = kDamageBullet; 3028 pXSprite->health = 0; 3029 nHealth = -25; 3030 } 3031 if (pXSprite->health > 0) 3032 { 3033 DAMAGEINFO *pDamageInfo = &damageInfo[nDamageType]; 3034 int nSound; 3035 if ((gGameOptions.nGameType != kGameTypeSinglePlayer) && !(gGameOptions.uNetGameFlags&kNetGameFlagCalebOnly) && gProfile[pPlayer->nPlayer].nModel && !VanillaMode()) // use cultist hurt and burn sounds 3036 nSound = (nDamageType == kDamageBurn ? 1031 : 1013) + Random(2); 3037 else if (nDamage >= (10<<4)) 3038 nSound = pDamageInfo->at4[0]; 3039 else 3040 nSound = pDamageInfo->at4[Random(3)]; 3041 if (nDamageType == kDamageDrown && pXSprite->medium == kMediumWater && !pPlayer->hand) 3042 nSound = 714; 3043 sfxPlay3DSound(pSprite, nSound, 0, 6); 3044 return nDamage; 3045 } 3046 sfxKill3DSound(pPlayer->pSprite, -1, 441); 3047 if (gGameOptions.nGameType == kGameTypeTeams && pPlayer->hasFlag) { 3048 if (pPlayer->hasFlag&1) playerDropFlag(pPlayer, kItemFlagA); 3049 if (pPlayer->hasFlag&2) playerDropFlag(pPlayer, kItemFlagB); 3050 } 3051 pPlayer->deathTime = 0; 3052 pPlayer->qavLoop = 0; 3053 pPlayer->curWeapon = kWeaponNone; 3054 pPlayer->fraggerId = nSource; 3055 pPlayer->voodooTargets = 0; 3056 pPlayer->invulTime = 0; 3057 if (nDamageType == kDamageExplode && nDamage < (9<<4)) 3058 nDamageType = kDamageFall; 3059 switch (nDamageType) 3060 { 3061 case kDamageExplode: 3062 sfxPlay3DSound(pSprite, 717, 0, 0); 3063 GibSprite(pSprite, GIBTYPE_7, NULL, NULL); 3064 GibSprite(pSprite, GIBTYPE_15, NULL, NULL); 3065 pPlayer->pSprite->cstat |= 32768; 3066 nDeathSeqID = 2; 3067 break; 3068 case kDamageBurn: 3069 sfxPlay3DSound(pSprite, 718, 0, 0); 3070 nDeathSeqID = 3; 3071 break; 3072 case kDamageDrown: 3073 nDeathSeqID = 1; 3074 break; 3075 default: 3076 if (nHealth < -20 && ((gGameOptions.nGameType >= kGameTypeBloodBath && !(gGameOptions.uNetGameFlags&kNetGameFlagSecondWindToggle)) || (gGameOptions.nGameType == kGameTypeCoop && (gGameOptions.uNetGameFlags&kNetGameFlagSecondWindToggle))) && Chance(0x4000)) 3077 { 3078 DAMAGEINFO *pDamageInfo = &damageInfo[nDamageType]; 3079 sfxPlay3DSound(pSprite, pDamageInfo->at10[0], 0, 2); 3080 nDeathSeqID = 16; 3081 nKneelingPlayer = nPlayerKneelClient; 3082 powerupActivate(pPlayer, kPwUpDeliriumShroom); 3083 pXSprite->target = nSource; 3084 evPost(pSprite->index, 3, 15, kCallbackFinishHim); 3085 } 3086 else 3087 { 3088 if(!((gGameOptions.uNetGameFlags&kNetGameFlagSpectatingAllow) && !strncmp(gProfile[pPlayer->nPlayer].name, "spectator", MAXPLAYERNAME))) 3089 { 3090 const int nSound = (gGameOptions.nGameType != kGameTypeSinglePlayer) && !(gGameOptions.uNetGameFlags&kNetGameFlagCalebOnly) && gProfile[pPlayer->nPlayer].nModel && !VanillaMode() ? 1018 + Random(2) : 716; 3091 sfxPlay3DSound(pSprite, nSound, 0, 0); 3092 } 3093 nDeathSeqID = 1; 3094 } 3095 break; 3096 } 3097 } 3098 if (nDeathSeqID < 0) 3099 return nDamage; 3100 if (nDeathSeqID != 16) 3101 { 3102 playerBackupItems(pPlayer); 3103 powerupClear(pPlayer); 3104 if (nXSector > 0 && xsector[nXSector].Exit) 3105 trTriggerSector(pSprite->sectnum, &xsector[nXSector], kCmdSectorExit, nSource); 3106 pSprite->flags |= 7; 3107 for (int p = connecthead; p >= 0; p = connectpoint2[p]) 3108 { 3109 if (gPlayer[p].fraggerId == nSprite && gPlayer[p].deathTime > 0) 3110 gPlayer[p].fraggerId = -1; 3111 } 3112 FragPlayer(pPlayer, nSource); 3113 trTriggerSprite(nSprite, pXSprite, kCmdOff, nSource); 3114 3115 if ((gGameOptions.nGameType == kGameTypeCoop) && (gGameOptions.uNetGameFlags&kNetGameFlagScoresLimitMask) && (pPlayer->pXSprite->health <= 0) && !gDemo.bPlaying && !gDemo.bRecording) 3116 { 3117 gPlayerCoopLives[pPlayer->nPlayer]++; 3118 char buffer[80]; 3119 buffer[0] = '\0'; 3120 char bAllPlayersDead = 1; 3121 for (int i = connecthead; i >= 0 && bAllPlayersDead; i = connectpoint2[i]) 3122 { 3123 if (gPlayerCoopLives[i] < gPlayerRoundScoreLimit) 3124 bAllPlayersDead = 0; 3125 } 3126 const int nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 3127 if (gPlayerCoopLives[pPlayer->nPlayer] >= gPlayerRoundScoreLimit) 3128 sprintf(buffer, "\r%s\r is outta lives!", gProfile[pPlayer->nPlayer].name); 3129 else if (gPlayerRoundScoreLimit - 1 == gPlayerCoopLives[pPlayer->nPlayer]) 3130 sprintf(buffer, "\r%s\r is on their last life!", gProfile[pPlayer->nPlayer].name); 3131 else if (pPlayer == gMe) 3132 sprintf(buffer, "You have %d lives left!", gPlayerRoundScoreLimit - gPlayerCoopLives[pPlayer->nPlayer]); 3133 if (buffer[0] != '\0') 3134 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, 0); 3135 if (bAllPlayersDead) 3136 viewSetMessage("press \"use\" or \"enter\" to restart level"); 3137 } 3138 else if (gGameOptions.bPermaDeath && (gGameOptions.nGameType == kGameTypeSinglePlayer) && (numplayers == 1) && (pPlayer->pXSprite->health <= 0) && !gDemo.bPlaying && !gDemo.bRecording) 3139 viewSetMessage("game over. press \"use\" or \"enter\" to quit"); 3140 else if (gRestoreLastSave && (gGameOptions.nGameType == kGameTypeSinglePlayer) && (numplayers == 1) && (pPlayer->pXSprite->health <= 0) && !gDemo.bPlaying && !gDemo.bRecording) // if died in single-player and not playing demo 3141 { 3142 extern short gQuickLoadSlot; // from menu.h 3143 char bAutosavedInSession = gAutosaveInCurLevel; 3144 if (!bAutosavedInSession) // if player has not triggered autosave in current level, check if last manual save/load was in current level 3145 bAutosavedInSession = LoadSavedInCurrentSession(gQuickLoadSlot) || LoadSavedInCurrentSession(kLoadSaveSlotQuick); 3146 if (bAutosavedInSession) 3147 viewSetMessage("press \"use\" to load last save or press \"enter\" to restart level"); // string borrowed from bloodgdx (thank you M210) 3148 else 3149 viewSetMessage("press \"use\" or \"enter\" to restart level"); 3150 } 3151 #ifdef NOONE_EXTENSIONS 3152 // allow drop items and keys in multiplayer 3153 if (gModernMap && gGameOptions.nGameType != kGameTypeSinglePlayer && pPlayer->pXSprite->health <= 0) { 3154 3155 spritetype* pItem = NULL; 3156 if (pPlayer->pXSprite->dropMsg && (pItem = actDropItem(pPlayer->pSprite, pPlayer->pXSprite->dropMsg)) != NULL) 3157 evPost(pItem->index, OBJ_SPRITE, 500, kCallbackRemove); 3158 3159 if (pPlayer->pXSprite->key) { 3160 3161 int i; // if all players have this key, don't drop it 3162 for (i = connecthead; i >= 0; i = connectpoint2[i]) { 3163 if (!gPlayer[i].hasKey[pPlayer->pXSprite->key]) 3164 break; 3165 } 3166 3167 if (i == 0 && (pItem = actDropKey(pPlayer->pSprite, (pPlayer->pXSprite->key + kItemKeyBase) - 1)) != NULL) 3168 evPost(pItem->index, OBJ_SPRITE, 500, kCallbackRemove); 3169 3170 } 3171 3172 3173 } 3174 #endif 3175 3176 } 3177 dassert(gSysRes.Lookup(pDudeInfo->seqStartID + nDeathSeqID, "SEQ") != NULL); 3178 seqSpawn(pDudeInfo->seqStartID+nDeathSeqID, 3, nXSprite, nKneelingPlayer); 3179 return nDamage; 3180 } 3181 3182 int UseAmmo(PLAYER *pPlayer, int nAmmoType, int nDec) 3183 { 3184 if (gInfiniteAmmo) 3185 return 9999; 3186 if (nAmmoType == -1) 3187 return 9999; 3188 pPlayer->ammoCount[nAmmoType] = ClipLow(pPlayer->ammoCount[nAmmoType]-nDec, 0); 3189 return pPlayer->ammoCount[nAmmoType]; 3190 } 3191 3192 void voodooTarget(PLAYER *pPlayer) 3193 { 3194 int v4 = pPlayer->aim.dz; 3195 int dz = pPlayer->zWeapon-pPlayer->pSprite->z; 3196 if (UseAmmo(pPlayer, 9, 0) < 8) 3197 { 3198 pPlayer->voodooTargets = 0; 3199 return; 3200 } 3201 for (int i = 0; i < 4; i++) 3202 { 3203 int ang1 = (pPlayer->voodooVar1+pPlayer->vodooVar2)&2047; 3204 actFireVector(pPlayer->pSprite, 0, dz, Cos(ang1)>>16, Sin(ang1)>>16, v4, kVectorVoodoo10); 3205 int ang2 = (pPlayer->voodooVar1+2048-pPlayer->vodooVar2)&2047; 3206 actFireVector(pPlayer->pSprite, 0, dz, Cos(ang2)>>16, Sin(ang2)>>16, v4, kVectorVoodoo10); 3207 } 3208 pPlayer->voodooTargets = ClipLow(pPlayer->voodooTargets-1, 0); 3209 } 3210 3211 int playerEffectGet(PLAYER* pPlayer, int nEffect) 3212 { 3213 switch (nEffect) 3214 { 3215 case kPlayerEffectTilt: return pPlayer->tiltEffect; 3216 case kPlayerEffectPain: return pPlayer->painEffect; 3217 case kPlayerEffectBlind: return pPlayer->blindEffect; 3218 case kPlayerEffectPickup: return pPlayer->pickupEffect; 3219 case kPlayerEffectQuake: return pPlayer->quakeEffect; 3220 case kPlayerEffectBright: return pPlayer->visibility; 3221 case kPlayerEffectDelirium: return pPlayer->pwUpTime[kPwUpDeliriumShroom]; 3222 case kPlayerEffectFlicker: return pPlayer->flickerEffect; 3223 case kPlayerEffectFlash: return (pPlayer->flashEffect) ? pPlayer->visibility : 0; 3224 } 3225 3226 return 0; 3227 } 3228 3229 void playerEffectSet(PLAYER* pPlayer, int nEffect, int nTime) 3230 { 3231 switch (nEffect) 3232 { 3233 case kPlayerEffectTilt: pPlayer->tiltEffect = nTime; break; 3234 case kPlayerEffectPain: pPlayer->painEffect = nTime; break; 3235 case kPlayerEffectBlind: pPlayer->blindEffect = nTime; break; 3236 case kPlayerEffectPickup: pPlayer->pickupEffect = nTime; break; 3237 case kPlayerEffectQuake: pPlayer->quakeEffect = nTime; break; 3238 case kPlayerEffectBright: pPlayer->visibility = nTime; break; 3239 case kPlayerEffectDelirium: pPlayer->pwUpTime[kPwUpDeliriumShroom] = nTime; break; 3240 case kPlayerEffectFlicker: pPlayer->flickerEffect = nTime; break; 3241 case kPlayerEffectFlash: 3242 pPlayer->visibility = nTime; 3243 pPlayer->flashEffect = 1; 3244 break; 3245 } 3246 } 3247 3248 void playerLandingSound(PLAYER *pPlayer) 3249 { 3250 static int surfaceSound[] = { 3251 -1, 3252 600, 3253 601, 3254 602, 3255 603, 3256 604, 3257 605, 3258 605, 3259 605, 3260 600, 3261 605, 3262 605, 3263 605, 3264 604, 3265 603 3266 }; 3267 spritetype *pSprite = pPlayer->pSprite; 3268 SPRITEHIT *pHit = &gSpriteHit[pSprite->extra]; 3269 if (pHit->florhit) 3270 { 3271 if ((pPlayer == gMe) && gCenterViewOnDrop) 3272 gCenterViewOnDrop = 2; 3273 if (!gGameOptions.bFriendlyFire && IsTargetTeammate(pPlayer, &sprite[pHit->florhit & 0x3fff])) 3274 return; 3275 char nSurf = tileGetSurfType(pHit->florhit); 3276 if (nSurf) 3277 sfxPlay3DSound(pSprite, surfaceSound[nSurf], -1, 0); 3278 } 3279 } 3280 3281 void PlayerSurvive(int, int nXSprite) 3282 { 3283 char buffer[80]; 3284 XSPRITE *pXSprite = &xsprite[nXSprite]; 3285 int nSprite = pXSprite->reference; 3286 spritetype *pSprite = &sprite[nSprite]; 3287 actHealDude(pXSprite, 1, 2); 3288 if (gGameOptions.nGameType != kGameTypeSinglePlayer && numplayers > 1) 3289 { 3290 sfxPlay3DSound(pSprite, 3009, 0, 6); 3291 if (IsPlayerSprite(pSprite)) 3292 { 3293 PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1]; 3294 if (pPlayer == gMe) 3295 viewSetMessage("I LIVE...AGAIN!!"); 3296 else 3297 { 3298 int nPal = gColorMsg && !VanillaMode() ? playerColorPalMessage(pPlayer->teamId) : 0; 3299 sprintf(buffer, "\r%s\r lives again!", gProfile[pPlayer->nPlayer].name); 3300 viewSetMessageColor(buffer, 0, MESSAGE_PRIORITY_NORMAL, nPal, 0); 3301 } 3302 pPlayer->input.newWeapon = kWeaponPitchfork; 3303 } 3304 } 3305 } 3306 3307 void PlayerKneelsOver(int, int nXSprite) 3308 { 3309 XSPRITE *pXSprite = &xsprite[nXSprite]; 3310 for (int p = connecthead; p >= 0; p = connectpoint2[p]) 3311 { 3312 if (gPlayer[p].pXSprite == pXSprite) 3313 { 3314 PLAYER *pPlayer = &gPlayer[p]; 3315 playerDamageSprite(pPlayer->fraggerId, pPlayer, kDamageSpirit, 500<<4); 3316 return; 3317 } 3318 } 3319 } 3320 3321 void playerHandChoke(PLAYER *pPlayer) 3322 { 3323 int t = gGameOptions.nDifficulty+2; 3324 if (pPlayer->handTime < 64) 3325 pPlayer->handTime = ClipHigh(pPlayer->handTime+t, 64); 3326 if (pPlayer->handTime > (7-gGameOptions.nDifficulty)*5) 3327 pPlayer->blindEffect = ClipHigh(pPlayer->blindEffect+t*4, 128); 3328 } 3329 3330 class PlayerLoadSave : public LoadSave 3331 { 3332 public: 3333 virtual void Load(void); 3334 virtual void Save(void); 3335 }; 3336 3337 void PlayerLoadSave::Load(void) 3338 { 3339 3340 Read(gPlayerScores, sizeof(gPlayerScores)); 3341 Read(&gNetPlayers, sizeof(gNetPlayers)); 3342 Read(&gProfile, sizeof(gProfile)); 3343 Read(&gPlayer, sizeof(gPlayer)); 3344 #ifdef NOONE_EXTENSIONS 3345 Read(&gPlayerCtrl, sizeof(gPlayerCtrl)); 3346 #endif 3347 for (int i = 0; i < gNetPlayers; i++) { 3348 gPlayer[i].pSprite = &sprite[gPlayer[i].nSprite]; 3349 gPlayer[i].pXSprite = &xsprite[gPlayer[i].pSprite->extra]; 3350 gPlayer[i].pDudeInfo = &dudeInfo[gPlayer[i].pSprite->type-kDudeBase]; 3351 3352 #ifdef NOONE_EXTENSIONS 3353 // load qav scene 3354 if (gPlayer[i].sceneQav != -1) { 3355 if (gPlayerCtrl[i].qavScene.qavResrc == NULL) 3356 gPlayer[i].sceneQav = -1; 3357 else { 3358 QAV* pQav = playerQavSceneLoad(gPlayer[i].sceneQav); 3359 if (pQav) { 3360 gPlayerCtrl[i].qavScene.qavResrc = pQav; 3361 gPlayerCtrl[i].qavScene.qavResrc->Preload(); 3362 } else { 3363 gPlayer[i].sceneQav = -1; 3364 } 3365 } 3366 } 3367 #endif 3368 3369 } 3370 gCrouchToggleState = 0; // reset crouch toggle state 3371 } 3372 3373 void PlayerLoadSave::Save(void) 3374 { 3375 Write(gPlayerScores, sizeof(gPlayerScores)); 3376 Write(&gNetPlayers, sizeof(gNetPlayers)); 3377 Write(&gProfile, sizeof(gProfile)); 3378 Write(&gPlayer, sizeof(gPlayer)); 3379 3380 #ifdef NOONE_EXTENSIONS 3381 Write(&gPlayerCtrl, sizeof(gPlayerCtrl)); 3382 #endif 3383 } 3384 3385 static PlayerLoadSave *myLoadSave; 3386 3387 void PlayerLoadSaveConstruct(void) 3388 { 3389 myLoadSave = new PlayerLoadSave(); 3390 }