/ source / blood / src / player.cpp
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  }