· 6 years ago · Sep 05, 2019, 09:50 AM
1// Player code. Very far from Half-Life, but still hacky.
2#include "extdll.h"
3#include "util.h"
4#include "pm_shared.h"
5#include "cbase.h"
6#include "player.h"
7#include "studio.h"
8#include "animation.h"
9#include "weapons.h"
10#include "trains.h"
11#include "nodes.h"
12#include "sound.h"
13#include "soundent.h"
14#include "monsters.h"
15#include "maprules.h"
16#include "gamerules.h"
17#include "teamplay_gamerules.h"
18#include "ctf_gamerules.h"
19#include "hltv.h"
20#include "items.h"
21#include "globals.h"
22#include "shake.h"
23#include "decals.h"
24#include "explode.h"
25#include "client.h"
26
27// XDM3038c: renamed back to normal
28TYPEDESCRIPTION CBasePlayer::m_SaveData[] =
29{
30 //DEFINE_FIELD(CBasePlayer, random_seed, FIELD_UINT), // Sent by usercmd
31
32 DEFINE_FIELD(CBasePlayer, m_iTargetVolume, FIELD_INTEGER),
33 DEFINE_FIELD(CBasePlayer, m_iWeaponVolume, FIELD_INTEGER),
34 DEFINE_FIELD(CBasePlayer, m_iExtraSoundTypes, FIELD_UINT32),
35 DEFINE_FIELD(CBasePlayer, m_iWeaponFlash, FIELD_INTEGER),
36 //DEFINE_FIELD(CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME),
37
38 DEFINE_FIELD(CBasePlayer, m_flFlashLightTime, FIELD_TIME),
39 DEFINE_FIELD(CBasePlayer, m_fFlashBattery, FIELD_FLOAT),
40
41 DEFINE_FIELD(CBasePlayer, m_afButtonLast, FIELD_INTEGER),
42 DEFINE_FIELD(CBasePlayer, m_afButtonPressed, FIELD_INTEGER),
43 DEFINE_FIELD(CBasePlayer, m_afButtonReleased, FIELD_INTEGER),
44
45 //DEFINE_FIELD(CBasePlayer, m_hEntSndLast, FIELD_EHANDLE), // Don't restore - client needs reset
46 //DEFINE_FIELD(CBasePlayer, m_flSndRoomtype, FIELD_FLOAT), // Don't restore - client needs reset
47 //DEFINE_FIELD(CBasePlayer, m_flSndRange, FIELD_FLOAT), // Don't restore - client needs reset
48
49 DEFINE_FIELD(CBasePlayer, m_flSuitUpdate, FIELD_TIME),
50 DEFINE_ARRAY(CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST),
51 DEFINE_FIELD(CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER),
52 DEFINE_ARRAY(CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT),
53 DEFINE_ARRAY(CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT),
54 //DEFINE_FIELD(CBasePlayer, m_lastDamageAmount, FIELD_INTEGER),
55 DEFINE_FIELD(CBasePlayer, m_tbdPrev, FIELD_TIME),
56
57 DEFINE_FIELD(CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER),
58 DEFINE_FIELD(CBasePlayer, m_fInitHUD, FIELD_BOOLEAN),
59 //DEFINE_FIELD(CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER), // Only in multiplayer
60 //DEFINE_FIELD(CBasePlayer, m_iInitEntities, FIELD_INTEGER), // Don't restore - client needs reset
61 //DEFINE_FIELD(CBasePlayer, m_iInitEntity, FIELD_INTEGER), // Don't restore - client needs reset
62 //DEFINE_FIELD(CBasePlayer, m_flInitEntitiesTime, FIELD_TIME), // Don't restore - client needs reset
63
64 DEFINE_FIELD(CBasePlayer, m_iTrain, FIELD_INTEGER),
65 DEFINE_FIELD(CBasePlayer, m_pTrain, FIELD_EHANDLE),// XDM3035a
66 DEFINE_FIELD(CBasePlayer, m_pTank, FIELD_EHANDLE),
67
68 //DEFINE_FIELD(CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN), // Don't need to restore, debug
69 DEFINE_FIELD(CBasePlayer, m_fLongJump, FIELD_BOOLEAN),
70 //DEFINE_FIELD(CBasePlayer, m_fWaterCircleTime, FIELD_TIME),
71 //DEFINE_FIELD(CBasePlayer, m_fGaitAnimFinishTime, FIELD_TIME),
72 //DEFINE_FIELD(CBasePlayer, m_iUpdateTime, FIELD_INTEGER), // Don't need to restore
73 //DEFINE_FIELD(CBasePlayer, m_iClientBattery, FIELD_INTEGER), // Don't restore - client needs reset
74 DEFINE_FIELD(CBasePlayer, m_iHideHUD, FIELD_INTEGER),
75 //DEFINE_FIELD(CBasePlayer, m_iClientHideHUD, FIELD_INTEGER), // Don't restore - client needs reset
76
77 DEFINE_FIELD(CBasePlayer, m_pActiveItem, FIELD_CLASSPTR),
78 DEFINE_FIELD(CBasePlayer, m_pLastItem, FIELD_CLASSPTR),
79 DEFINE_FIELD(CBasePlayer, m_pNextItem, FIELD_CLASSPTR),// XDM
80
81 DEFINE_ARRAY(CBasePlayer, m_rgAmmo, FIELD_UINT32, MAX_AMMO_SLOTS),// XDM3039: FIELD_UINT32
82 //DEFINE_ARRAY(CBasePlayer, m_rgAmmoLast, FIELD_UINT32, MAX_AMMO_SLOTS),// Don't restore - client needs reset
83 DEFINE_ARRAY(CBasePlayer, m_rgpWeapons, FIELD_EHANDLE, PLAYER_INVENTORY_SIZE),// XDM3035b: ID=32
84 DEFINE_ARRAY(CBasePlayer, m_rgItems, FIELD_UINT32, MAX_ITEMS),
85
86 //DEFINE_FIELD(CBasePlayer, m_fInventoryChanged, FIELD_INTEGER), // Don't restore, client needs reset
87
88 //DEFINE_FIELD(CBasePlayer, m_vecAutoAim, FIELD_VECTOR), // Don't save/restore - recomputed
89 //DEFINE_FIELD(CBasePlayer, m_vecAutoAimPrev, FIELD_VECTOR), // Don't save/restore - recomputed
90 //DEFINE_FIELD(CBasePlayer, m_hAutoaimTarget, FIELD_EHANDLE), // Don't save/restore - recomputed
91 //DEFINE_FIELD(CBasePlayer, m_fOnTarget, FIELD_BOOLEAN), // Don't save/restore - recomputed
92
93 DEFINE_FIELD(CBasePlayer, m_iDeaths, FIELD_UINT32),// XDM3038c
94 //DEFINE_FIELD(CBasePlayer, m_iRespawnFrames, FIELD_INTEGER), // Don't restore, client needs reset
95 //DEFINE_FIELD(CBasePlayer, m_fDiedTime, FIELD_FLOAT), // Only in multiplayer
96 //DEFINE_FIELD(CBasePlayer, m_fNextSuicideTime, FIELD_TIME), // Don't need to restore
97
98 //DEFINE_FIELD(CBasePlayer, m_flNextObserverInput, FIELD_TIME), // Don't need to restore
99 //DEFINE_FIELD(CBasePlayer, m_iObserverLastMode, FIELD_INTEGER), // Don't need to restore
100 //DEFINE_FIELD(CBasePlayer, m_hObserverTarget, FIELD_EHANDLE), // Don't need to restore
101
102 DEFINE_FIELD(CBasePlayer, m_hCarryingObject, FIELD_EHANDLE),// XDM3039
103
104 //DEFINE_FIELD(CBasePlayer, m_iGeigerRange, FIELD_FLOAT), // Don't restore - reset in Precache()
105 //DEFINE_FIELD(CBasePlayer, m_iGeigerRangePrev, FIELD_FLOAT), // Don't restore - reset in Precache()
106 //DEFINE_FIELD(CBasePlayer, m_flgeigerDelay, FIELD_FLOAT), // Don't restore - reset in Precache()
107
108 DEFINE_FIELD(CBasePlayer, m_idrowndmg, FIELD_INTEGER),
109 DEFINE_FIELD(CBasePlayer, m_idrownrestored, FIELD_INTEGER),
110
111 DEFINE_FIELD(CBasePlayer, m_flIgnoreLadderStopTime, FIELD_TIME),
112 DEFINE_FIELD(CBasePlayer, m_flNextDecalTime, FIELD_TIME),
113 //DEFINE_FIELD(CBasePlayer, m_flNextChatTime, FIELD_TIME),
114 DEFINE_FIELD(CBasePlayer, m_flThrowNDDTime, FIELD_TIME),
115 //DEFINE_FIELD(CBasePlayer, m_flLastSpawnTime, FIELD_TIME),
116
117 DEFINE_FIELD(CBasePlayer, m_vecLastSpawnOrigin, FIELD_VECTOR),
118 DEFINE_FIELD(CBasePlayer, m_vecLastSpawnAngles, FIELD_VECTOR),
119
120 //DEFINE_FIELD(CBasePlayer, m_iLastSpawnFlags, FIELD_INTEGER), // Only in multiplayer
121 //DEFINE_FIELD(CBasePlayer, m_iNumTeamChanges, FIELD_INTEGER), // Only in multiplayer
122 //DEFINE_FIELD(CBasePlayer, m_iScoreCombo, FIELD_INTEGER), // Only in multiplayer
123 //DEFINE_FIELD(CBasePlayer, m_iLastScoreAward, FIELD_INTEGER), // Only in multiplayer
124 //DEFINE_FIELD(CBasePlayer, m_fNextScoreTime, FIELD_TIME), // Only in multiplayer
125 DEFINE_ARRAY(CBasePlayer, m_hszCheckPoints, FIELD_EHANDLE, MAX_CHECKPOINTS),// ?
126 DEFINE_ARRAY(CBasePlayer, m_Stats, FIELD_INTEGER, STAT_NUMELEMENTS), // ?
127
128 DEFINE_ARRAY(CBasePlayer, m_szCurrentMusicTrack, FIELD_CHARACTER, MAX_ENTITY_STRING_LENGTH),
129 DEFINE_FIELD(CBasePlayer, m_iCurrentMusicTrackCommand, FIELD_INTEGER),
130 DEFINE_FIELD(CBasePlayer, m_iCurrentMusicTrackStartTime, FIELD_TIME),
131 //DEFINE_FIELD(CBasePlayer, m_iCurrentMusicTrackCommandIssued, FIELD_SHORT),// Don't save/restore - recomputed
132
133 DEFINE_FIELD(CBasePlayer, m_vMemOrigin, FIELD_VECTOR),
134 DEFINE_FIELD(CBasePlayer, m_vMemAngles, FIELD_VECTOR),
135 //DEFINE_FIELD(CBasePlayer, m_PickTrace, FIELD_),// cannot save TraceResult because of pointer inside! Also, don't need to save
136 //DEFINE_FIELD(CBasePlayer, m_hPickEntity, FIELD_CLASSPTR),
137 DEFINE_FIELD(CBasePlayer, m_CoordsRemembered, FIELD_BOOLEAN),
138
139 DEFINE_FIELD(CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER),// TODO: FIELD_UINT32 ???
140 //DEFINE_FIELD(CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER), // Don't restore, depends on server message after spawning and only matters in multiplayer
141
142 DEFINE_FIELD(CBasePlayer, m_iUserRights, FIELD_UINT32),// XDM3038c
143 //DEFINE_FIELD(CBasePlayer, m_pClientActiveItem, FIELD_CLASSPTR), // Don't restore - client needs reset
144
145 //DEFINE_FIELD(CBasePlayer, m_iSequenceDeepIdle, FIELD_INTEGER), // Don't restore - reset in Precache()
146 DEFINE_ARRAY(CBasePlayer, m_szAnimExtention, FIELD_CHARACTER, 32),
147
148 //DEFINE_FIELD(CBasePlayer, m_flNextSBarUpdateTime, FIELD_TIME),
149 //DEFINE_FIELD(CBasePlayer, m_flStatusBarDisappearDelay, FIELD_TIME),
150 //DEFINE_ARRAY(CBasePlayer, m_StatusBarString, FIELD_CHARACTER, SBAR_STRING_SIZE),
151 //DEFINE_ARRAY(CBasePlayer, m_iStatusBarValues, FIELD_SHORT, SBAR_NUMVALUES),
152
153 //DEFINE_FIELD(CBasePlayer, m_iHUDDistortUpdate, FIELD_SHORT), // Don't restore - client needs reset
154 DEFINE_FIELD(CBasePlayer, m_iHUDDistortMode, FIELD_SHORT),
155 DEFINE_FIELD(CBasePlayer, m_iHUDDistortValue, FIELD_SHORT),
156};
157
158LINK_ENTITY_TO_CLASS(player, CBasePlayer);
159
160
161//-----------------------------------------------------------------------------
162// Purpose: ID's player as such.
163// Warning: May be called before Spawn()!
164// Output : int
165//-----------------------------------------------------------------------------
166int CBasePlayer::Classify(void)
167{
168 return m_iClass?m_iClass:CLASS_PLAYER;// XDM3037
169}
170
171//-----------------------------------------------------------------------------
172// Purpose: Spawn
173// Warning: Not called on level change!
174// Callers: ClientPutInServer()
175// Input : restore - true if restoring from a saved game with FCAP_MUST_SPAWN
176//-----------------------------------------------------------------------------
177void CBasePlayer::Spawn(byte restore)
178{
179 if (restore == FALSE)
180 {
181 size_t i = 0;
182 for (i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
183 m_rgpWeapons[i] = nullptr;
184
185 for (i = 0; i < MAX_ITEMS; ++i)// XDM3034
186 m_rgItems[i] = 0;
187
188 m_rgItems[ITEM_ANTIDOTE] = DEFAULT_ANTIDOTE_GIVE;
189 m_rgItems[ITEM_FLARE] = DEFAULT_FLARE_GIVE;
190
191 m_flBurnTime = 0.0f;// XDM3037
192 m_hTBDAttacker = nullptr;// XDM3037
193
194 m_fFrozen = FALSE;// XDM3035
195 m_voicePitch = PITCH_NORM;
196
197 m_afButtonLast = 0;// XDM3038a
198 m_afButtonPressed = 0;// XDM3038a
199 m_afButtonReleased = 0;// XDM3038a
200 m_fInitHUD = TRUE;// XDM3038c: only init if not restoring or changing map
201
202 //if (m_iSpawnState <= SPAWNSTATE_CONNECTED)// XDM3038c: player is conecting, start playing default map list
203 if (!FBitSet(m_iGameFlags, EGF_SPAWNED|EGF_RESPAWNED))
204 {
205 m_szCurrentMusicTrack[0] = '\0';// play map list
206 m_iCurrentMusicTrackStartTime = gpGlobals->time + 3.5;// should be enough to let the world override command before it gets sent
207 m_iCurrentMusicTrackCommand = MPSVCMD_PLAYFILELOOP;
208 m_iCurrentMusicTrackCommandIssued = 0;
209 }
210
211 m_iHUDDistortMode = 0;// XDM3038
212 m_iHUDDistortValue = 0;// XDM3038
213
214 UTIL_SetView(edict(), edict());// XDM3035b: restore after possible SET_VIEW (like trigger_camera on previous level)
215
216 pev->velocity.Clear();// XDM3038c
217 pev->basevelocity.Clear();// XDM3038c
218 pev->angles.Clear();// XDM3038c
219 pev->avelocity.Clear();// XDM3038c
220 pev->scale = 1.0;// XDM3038a
221 pev->weapons = 0;// XDM3038a
222 pev->button = 0;// XDM3038a
223 //FAIL! pev->groupinfo = (1 << entindex());// XDM3038c: to disable collisions with this player, set entity's groupinfo to this player index and UTIL_SetGroupTrace(, GROUP_OP_AND). DO NOT use just entindex because of bitwise operators
224
225 if (g_pWorld)// XDM3038c
226 {
227 if (FBitSet(g_pWorld->pev->spawnflags, SF_WORLD_STARTSUIT))
228 pev->weapons |= (1<<WEAPON_SUIT);
229 }
230 }
231 Spawn();
232 //FlashlightUpdate(FlashlightIsOn());
233}
234
235//-----------------------------------------------------------------------------
236// Purpose: Spawn, called by Spawn(bool restore)
237// Note : Arrange variables in cache-friendly manner
238//-----------------------------------------------------------------------------
239void CBasePlayer::Spawn(void)
240{
241 DBG_PLR_PRINT("CBasePlayer(%d)::Spawn()\n", entindex());
242 SetClassName("player");
243 ASSERT(edict()->serialnumber != 0);// XDM3038c: !!!
244 pev->targetname = pev->classname;// XDM3037: TESTME: is this ok?
245 pev->max_health = g_pGameRules?g_pGameRules->GetPlayerMaxHealth():MAX_PLAYER_HEALTH;//100.0f;
246 if (IsMultiplayer())// XDM3038a: setup initial values
247 {
248 pev->health = min(MAX_ABS_PLAYER_HEALTH, mp_playerstarthealth.value>0?mp_playerstarthealth.value:pev->max_health);
249 pev->armorvalue = min(MAX_ABS_PLAYER_ARMOR, mp_playerstartarmor.value);
250 }
251 else
252 {
253 if (pev->health <= 0.0f)// player can't spawn dead
254 pev->health = pev->max_health;
255
256 //pev->armorvalue = 0;
257 }
258 pev->deadflag = DEAD_NO;
259 pev->owner = nullptr;// XDM3035
260 SetBits(pev->flags, FL_CLIENT);
261 pev->dmg_take = 0.0f;
262 pev->dmg_save = 0.0f;
263 pev->maxspeed = g_psv_maxspeed->value;// XDM3035c
264 pev->fov = 0.0f;// for client side this sould mean "reset to default"
265
266 m_flFieldOfView = 0.5f;// some monsters use this to determine whether or not the player is looking at them.
267 m_bitsDamageType = 0;
268 m_iScoreAward = gSkillData.plrScore;// XDM3038c: for CoOp, used for friendly fire penalty
269
270 m_afPhysicsFlags = 0;
271 m_fLongJump = FALSE;// no longjump module.
272 m_flLastSpawnTime = gpGlobals->time;
273
274 //if (m_iSpawnState <= SPAWNSTATE_CONNECTED)// XDM3038: is this reliable enough?
275 if (!FBitSet(m_iGameFlags, EGF_SPAWNED|EGF_RESPAWNED))
276 CheckPointsClear();
277
278 bool startfailed = false;
279
280 if (!IsBot() && g_pCvarDeveloper->value > 0.0f && g_psv_cheats && g_psv_cheats->value > 0.0f)// XDM3039: no bots!
281 RightsAssign(USER_RIGHTS_DEVELOPER);// XDM3038c
282
283 if (g_pGameRules)// XDM3038a: early initialization
284 {
285 if (!g_pGameRules->PlayerSpawnStart(this))// allowed to spawn? assign team, required to use spawn spot later
286 startfailed = true;
287
288 if (!startfailed)
289 {
290 if (!g_pGameRules->PlayerUseSpawnSpot(this, false))// team must be set by now!
291 {
292 ClientPrint(pev, HUD_PRINTCENTER, "#PL_NOSSTARTSPOT");
293 startfailed = true;
294 }
295 }
296
297 if (startfailed)
298 {
299 g_pGameRules->PlayerUseSpawnSpot(this, true);// try using spectator spot
300 if (g_pGameRules->IsMultiplayer())// continue anyway in SP
301 {
302 ObserverStart(pev->origin, pev->angles, OBS_ROAMING, nullptr);
303 goto spawn_end;
304 }
305 }
306 }
307 RightsAssign(USER_RIGHTS_PLAYER);// XDM3038c: not for spectators
308
309 pev->movetype = MOVETYPE_WALK;
310 pev->solid = SOLID_SLIDEBOX;
311 pev->takedamage = DAMAGE_AIM;
312 pev->dmg = 2;// initial water damage
313 pev->air_finished = gpGlobals->time + 12.0f;
314
315 {
316 char *infobuffer = GET_INFO_KEY_BUFFER(edict());
317 if (pev->skin == 0 && infobuffer)
318 pev->skin = atoi(GET_INFO_KEY_VALUE(infobuffer, "skin"));// XDM
319 }
320
321 // XDM3037: players aren't created by DispatchSpawn(), so handle these separately
322 if (g_pWorld)// HACK: this global multiplier should be used by physics code, but we don't have access to entity physics
323 {
324 pev->gravity = g_pWorld->pev->gravity;
325 pev->friction = g_pWorld->pev->friction;// XDM3035c: use map global multipliers
326 }
327 pev->rendermode = kRenderNormal;// XDM3035
328 pev->renderamt = 255;// XDM3037a
329 pev->rendercolor.Set(255,255,255);// XDM3037a
330 pev->renderfx = kRenderFxNone;// XDM
331
332 m_bitsHUDDamage = -1;
333 if (FBitSet(m_iGameFlags, EGF_DIED))
334 SetBits(m_iGameFlags, EGF_RESPAWNED);// XDM3038c
335
336 ClearBits(m_iGameFlags, EGF_DROPPEDITEMS);// XDM3038c
337 ENGINE_SETPHYSKV(edict(), PHYSKEY_LONGJUMP, "0");
338 ENGINE_SETPHYSKV(edict(), PHYSKEY_DEFAULT, "1");
339 ENGINE_SETPHYSKV(edict(), PHYSKEY_IGNORELADDER, "0");// XDM3037: ignore ladder: off
340 //XDM3037a m_iClientFOV = -1; // make sure fov reset is sent
341 m_flNextDecalTime = 0;// let this player decal as soon as he spawns.
342 m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations are recieved by all clients
343 //m_flTimeStepSound = 0;
344 //m_iStepLeft = 0;
345 m_Activity = ACT_RESET;// XDM3037a
346 m_IdealActivity = ACT_IDLE;// XDM3037a
347 m_LastHitGroup = HITGROUP_GENERIC;// XDM3037
348 m_afCapability = bits_CAP_DUCK | bits_CAP_JUMP | bits_CAP_STRAFE | bits_CAP_SWIM | bits_CAP_CLIMB | bits_CAP_USE | bits_CAP_HEAR | bits_CAP_AUTO_DOORS | bits_CAP_OPEN_DOORS | bits_CAP_TURN_HEAD;
349
350 if (m_bloodColor == 0)
351 m_bloodColor = BLOOD_COLOR_RED;
352
353 if (m_iGibCount == 0)
354 m_iGibCount = GIB_COUNT_HUMAN;// XDM
355
356 m_afMemory = MEMORY_CLEAR;// XDM3037a: forget everything, like bits_MEMORY_KILLED
357 m_flNextAttack = UTIL_WeaponTimeBase();
358 m_flFallVelocity = 0;// dont let uninitialized value here hurt the player
359
360 m_flFlashLightTime = gpGlobals->time + FLASHLIGHT_UPDATE_INTERVAL;// force first message
361 m_fFlashBattery = MAX_FLASHLIGHT_CHARGE;//-1; XDM3037: TESTME?
362 m_fDiedTime = 0.0f;// XDM3035
363 m_flIgnoreLadderStopTime = 0.0f;// XDM3037
364 m_flNextChatTime = gpGlobals->time;
365 m_flThrowNDDTime = 0.0f;// XDM3035
366 m_pClientActiveItem = nullptr;
367 m_iHUDDistortUpdate = 1;// XDM3038
368
369 CBaseAnimating::Spawn();// XDM3039 Precache();
370
371 /*if (sv_modelhitboxes.value > 0.0f)// XDM3035: UNDONE: use custom hitboxes it not afraid of cheaters. Error no precache
372 {
373 char *bufmdl = GET_INFO_KEY_VALUE(infobuffer, "model");
374 char modelpath[32];
375 _snprintf(modelpath, 32, "models/player/%s/%s.mdl", bufmdl, bufmdl);
376 SET_MODEL(edict(), modelpath);
377 }
378 else*/
379 //SET_MODEL(edict(), "models/player.mdl");
380 //SET_MODEL(edict(), STRING(pev->model));// XDM3038a
381
382 g_iModelIndexPlayer= pev->modelindex;
383 pev->sequence = LookupActivity(m_IdealActivity);
384
385 m_HackedGunPos.Set(0,0,VEC_VIEW);// XDM3037: probably doesn't change a thing anyway
386
387 // XDM3038c: m_fInitHUD = TRUE;
388 //m_pTrain = nullptr;// XDM3035
389 m_pTank = nullptr;
390 m_fNoPlayerSound = FALSE;// normal sound behavior.
391 m_iClientBattery = -1;
392 m_iClientHideHUD = -1; // force this to be recalculated
393 m_pActiveItem = nullptr;// XDM: TESTED
394 m_pLastItem = nullptr;
395 m_pNextItem = nullptr;// XDM3038
396 m_vecAutoAimPrev.Clear();
397 m_hCarryingObject = nullptr;// XDM3033 TESTED
398
399 m_iScoreCombo = 0;// XDM3035
400 m_iLastScoreAward = 0;
401 m_fNextScoreTime = 0.0f;
402
403 // reset all ammo values to 0
404 size_t i;
405 for (i = 0; i < MAX_AMMO_SLOTS; ++i)
406 {
407 m_rgAmmo[i] = 0;
408 m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side)
409 }
410 m_iSequenceDeepIdle = LookupSequence("deep_idle");// XDM3035b: fasterizer
411 m_fGaitAnimFinishTime = 0;// XDM3037
412
413 if (g_pGameRules)
414 g_pGameRules->PlayerSpawn(this);
415
416 // XDM3039 Materialize();// XDM3038c
417
418 UpdateSoundEnvironment(nullptr, 0, 0);// XDM3038a: very old bug many players complained about
419
420 // XDM3038: moved after PlayerSpawn() so it may affect these flags
421 if (FBitSet(pev->flags, FL_DUCKING))
422 UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX);
423 else
424 UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX);
425
426 //pev->oldorigin = pev->origin;// XDM3035: possibly reduces interpolation during respawn
427 if (!IsObserver())
428 {
429 pev->view_ofs = VEC_VIEW_OFFSET;
430 pev->effects = 0;// stay invisible during respawn
431 }
432spawn_end:
433 SetBits(m_iGameFlags, EGF_SPAWNED);// XDM3039
434 //m_iSpawnState = SPAWNSTATE_SPAWNED;// MUST be set AFTER calling PlayerSpawn()
435}
436
437//-----------------------------------------------------------------------------
438// Purpose: Precache resources and reset unsaved data
439// Warning: Sounds and models are precached in ClientPrecache() because clients are not created as ordinary entities
440//-----------------------------------------------------------------------------
441void CBasePlayer::Precache(void)
442{
443 // XDM: some stuff moved to CWorld
444
445 m_bitsDamageType = 0;
446 // init geiger counter vars during spawn and each time we cross a level transition
447 m_iGeigerRange = GEIGER_MAXDIST+1;
448 m_iGeigerRangePrev = GEIGER_MAXDIST+1;
449 m_bitsHUDDamage = -1;
450 m_iTrain = TRAIN_NEW;
451 m_iClientBattery = -1;
452 //m_iUpdateTime = 5; // won't update for 1/2 a second
453 // XDM3038c: TESTME if (g_ClientShouldInitialize[entindex()])// XDM3037
454 // m_fInitHUD = TRUE;
455
456 if (FStringNull(pev->model))// XDM3038a: test?
457 pev->model = MAKE_STRING("models/player.mdl");
458
459 //if (IsOnTrain())// XDM3035c: restore
460 //already m_iTrain |= TRAIN_NEW;
461}
462
463//-----------------------------------------------------------------------------
464// Purpose: XDM3037: Player can be used when alive
465//-----------------------------------------------------------------------------
466int CBasePlayer::ObjectCaps(void)
467{
468 if (IsAlive())
469 return (CBaseMonster::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE;// players can use players
470 else
471 return (CBaseMonster::ObjectCaps() & ~FCAP_ACROSS_TRANSITION);
472}
473
474//-----------------------------------------------------------------------------
475// Purpose: Save player data (hl-compatible override)
476// Input : &save -
477// Output : int - 1 on success, 0 on failure.
478//-----------------------------------------------------------------------------
479int CBasePlayer::Save(CSave &save)
480{
481 if (!CBaseMonster::Save(save))
482 return 0;
483
484 return save.WriteFields("PLAYER", this, m_SaveData, ARRAYSIZE(m_SaveData));
485}
486
487//-----------------------------------------------------------------------------
488// Purpose: Restore from saved game or after map transition
489// Input : &restore -
490// Output : int - 1 on success, 0 on failure.
491//-----------------------------------------------------------------------------
492int CBasePlayer::Restore(CRestore &restore)
493{
494 if (!CBaseMonster::Restore(restore))
495 return 0;
496
497 int status = restore.ReadFields("PLAYER", this, m_SaveData, ARRAYSIZE(m_SaveData));
498 if (status == 0)
499 {
500 conprintf(0, "CBasePlayer::Restore() ERROR: ReadFields(\"PLAYER\") failed!\n");
501 return 0;
502 }
503 SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData;
504 ASSERT(pSaveData != nullptr);
505 // landmark isn't present.
506 if (pSaveData && !pSaveData->fUseLandmark)
507 {
508 conprintf(1, "CBasePlayer::Restore(): error! No landmark: \"%s\"!\n", pSaveData->szLandmarkName);
509 // default to normal spawn
510 CBaseEntity *pSpawnSpot = SpawnPointEntSelect(this, false);
511 if (pSpawnSpot)
512 {
513 pev->origin = pSpawnSpot->pev->origin;
514 pev->origin.z += 1.0f;
515 pev->angles = pSpawnSpot->pev->angles;
516 }
517 }
518 pev->v_angle.z = 0;// Clear out roll
519 pev->angles = pev->v_angle;
520 pev->fixangle = TRUE;// turn this way immediately
521
522 // still no help m_fInitHUD = TRUE;// XDM3035b: CL_Precache needs this
523
524 // Copied from spawn() for now
525 //? m_bloodColor = BLOOD_COLOR_RED;
526 g_iModelIndexPlayer = pev->modelindex;
527
528 if (FBitSet(pev->flags, FL_DUCKING))
529 UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX);
530 else
531 UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX);
532
533 edict_t *pPlayerEdict = edict();
534 ASSERT(pPlayerEdict != nullptr);
535 ClientAssignSerialNumber(pPlayerEdict);// XDM3038c: this gets reset on map change in sp!
536
537 ENGINE_SETPHYSKV(pPlayerEdict, PHYSKEY_DEFAULT, "1");
538
539 if (m_fLongJump)
540 ENGINE_SETPHYSKV(pPlayerEdict, PHYSKEY_LONGJUMP, "1");
541 else
542 ENGINE_SETPHYSKV(pPlayerEdict, PHYSKEY_LONGJUMP, "0");
543
544#if defined(CLIENT_WEAPONS)
545 m_flNextAttack = UTIL_WeaponTimeBase();// in CBaseMonster it is server time
546#endif
547
548 m_iCurrentMusicTrackCommandIssued = 0;// XDM3038c: resend last music player command
549 //if (IsOnTrain())// XDM3035c: doesn't help - too early
550 // m_iTrain |= TRAIN_NEW;
551
552 return status;
553}
554
555//-----------------------------------------------------------------------------
556// Purpose: XDM3038c in case player is deleted, should never be called!
557//-----------------------------------------------------------------------------
558void CBasePlayer::UpdateOnRemove(void)
559{
560 DBG_PRINT_ENT("UpdateOnRemove");
561 if (g_ServerActive)
562 ClientDisconnect(edict());
563
564 DBG_FORCEBREAK
565}
566
567//-----------------------------------------------------------------------------
568// Purpose: Returns an integer that describes the relationship between this monster and target.
569// Warning: May be called before Spawn()!
570// Input : *pTarget -
571// Output : int R_NO
572//-----------------------------------------------------------------------------
573int CBasePlayer::IRelationship(CBaseEntity *pTarget)
574{
575 if (g_pGameRules)
576 {
577 int relation = g_pGameRules->PlayerRelationship(this, pTarget);
578 if (relation == GR_NOTTEAMMATE)
579 return R_DL;
580 else if (relation == GR_ENEMY)
581 return R_HT;
582 else if (relation == GR_TEAMMATE || relation == GR_ALLY)
583 return R_AL;
584 //else // otherwise look into relationship table
585 }
586 return CBaseMonster::IRelationship(pTarget);
587}
588
589//-----------------------------------------------------------------------------
590// Purpose: Can be pushed by friendly players
591// Input : *pOther -
592//-----------------------------------------------------------------------------
593void CBasePlayer::Touch(CBaseEntity *pOther)
594{
595 DBG_PRINT_ENT_TOUCH(Touch);
596 if (g_psv_cheats->value > 0 && m_pTank == this && pOther->pev->takedamage != DAMAGE_NO)// XDM3039: when you feel extreme rage
597 pOther->TakeDamage(this, this, (MAX_PLAYER_HEALTH+MAX_NORMAL_BATTERY)*2.0f, DMG_CRUSH);
598 if (sv_playerpushable.value <= 0.0f)
599 return;
600 if (!pOther->IsPlayer())// || !pOther->IsAlive())// XDM3035c: allow players to push each other
601 return;
602 if (pOther->edict() == pev->groundentity)// Don't send velocity to an entity I'm standing on
603 return;
604
605 int relation = g_pGameRules?g_pGameRules->PlayerRelationship(this, pOther):GR_NEUTRAL;
606 if (sv_playerpushable.value < 2.0f && (relation == GR_NOTTEAMMATE || relation == GR_ENEMY))// XDM3038
607 {
608 if (!(/*FBitSet(pOther->pev->flags, FL_ONGROUND) && */pOther->pev->groundentity && pOther->pev->groundentity == edict()))// if someone's standing on me, allow to push him anyway
609 return;
610 }
611
612 vec_t fLocalSpeed = pev->velocity.Length();
613 vec_t fOtherSpeed = pOther->pev->velocity.Length();
614 float kEnergy;// how much enegry is transfered
615 if (FBitSet(pev->flags, FL_ONGROUND))// I'm on ground
616 {
617 if (FBitSet(pOther->pev->flags, FL_ONGROUND))
618 {
619 if (pOther->pev->friction > 0)
620 kEnergy = pev->friction/pOther->pev->friction;
621 else
622 kEnergy = 0.5;// ?
623 }
624 else// he's in the air
625 kEnergy = 1;
626 }
627 else// I'm not on ground
628 {
629 if (FBitSet(pOther->pev->flags, FL_ONGROUND))// Bumped into a standing player
630 kEnergy = 0.25;
631 else
632 kEnergy = 0.5;// Two flying players
633 }
634 pOther->pev->velocity += pev->velocity*kEnergy;// Inverse logic. Not scientific, but works fine with HL
635 pev->velocity *= (1-kEnergy);// lose energy
636 if (pOther->pev->velocity.Length() > max(fLocalSpeed, fOtherSpeed))
637 pOther->pev->velocity.SetLength(max(fLocalSpeed, fOtherSpeed));
638}
639
640//-----------------------------------------------------------------------------
641// Purpose: Players can 'use' each other. Works for bots and gets attention of players!
642// Input : *pActivator -
643// *pCaller -
644// useType -
645// value -
646//-----------------------------------------------------------------------------
647void CBasePlayer::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
648{
649 if (pActivator->IsPlayer() && pActivator->pev != pev)// using self is really possible
650 {
651 if (IsBot())// Don't mess with real players entvars
652 pev->owner = pActivator->edict();// XBM: bot will react to this change // NOTE: collision will be disabled for one frame
653 else// Show direction from which the player was called
654 {
655 MESSAGE_BEGIN(MSG_ONE, gmsgDamage, nullptr, edict());
656 WRITE_BYTE(HITGROUP_ARMOR);// hithgoup
657 //WRITE_BYTE(0);// impossible damageTaken
658 WRITE_LONG(DMG_NOHITPOINT);// impossible bitsDamage
659 WRITE_COORD(pActivator->pev->origin.x);
660 WRITE_COORD(pActivator->pev->origin.y);
661 WRITE_COORD(pActivator->pev->origin.z);
662 MESSAGE_END();
663 ClientPrint(pev, HUD_PRINTCENTER, "%s\n", STRING(pActivator->pev->netname));// Print caller's name (or use some localized string maybe)
664 }
665 }
666}
667
668//-----------------------------------------------------------------------------
669// Purpose: Cheatshit
670// Input : *onpev -
671// Output : Returns true if the player is attached to a ladder
672//-----------------------------------------------------------------------------
673bool CBasePlayer::OnControls(entvars_t *onpev)
674{
675 if (onpev == pev)
676 return true;
677
678 return CBaseMonster::OnControls(onpev);
679}
680
681//-----------------------------------------------------------------------------
682// Purpose: For everybody to hear
683// UNDONE: TODO: character voice here
684//-----------------------------------------------------------------------------
685void CBasePlayer::DeathSound(void)
686{
687 EMIT_GROUPNAME_SUIT(edict(), "HEV_DEAD");
688}
689
690//-----------------------------------------------------------------------------
691// Purpose: GetGunPosition
692// Output : Vector
693//-----------------------------------------------------------------------------
694Vector CBasePlayer::GetGunPosition(void)
695{
696 //Vector org, ang;
697 //GetAttachment(0, org, ang);
698 //UTIL_MakeVectors(pev->v_angle);
699 //m_HackedGunPos = pev->view_ofs;
700 return pev->origin + pev->view_ofs;// + myForward * 12? undone
701}
702
703//-----------------------------------------------------------------------------
704// Purpose: TraceAttack
705// Input : *pAttacker -
706// flDamage -
707// &vecDir -
708// *ptr -
709// bitsDamageType -
710//-----------------------------------------------------------------------------
711void CBasePlayer::TraceAttack(CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, TraceResult *ptr, int bitsDamageType)
712{
713 //conprintf(1, "%s(%d)::TraceAttack(%g, tr.hg %d, bits %d)\n", STRING(pev->classname), entindex(), flDamage, ptr->iHitgroup, bitsDamageType);
714 if (pev->takedamage == DAMAGE_NO)
715 return;
716
717 /* CBaseMonster if (FBitSet(bitsDamageType, DMG_NOHITPOINT))// XDM3037
718 m_LastHitGroup = HITGROUP_GENERIC;
719 else
720 m_LastHitGroup = ptr->iHitgroup;*/
721
722 switch (ptr->iHitgroup)
723 {
724 case HITGROUP_HEAD:
725 {
726 flDamage *= gSkillData.plrHead;
727 // if (!m_fFrozen)// XDM: undone: move to client
728 // UTIL_ScreenFade(this, Vector(255,20,0), 1.0f, 0.1f, clamp((int)flDamage, 10, 255), FFADE_IN);
729 if (gSkillData.iSkillLevel == SKILL_HARD && m_flNextAttack != 0.0f)// XDM3037
730 {
731 if (m_flNextAttack > UTIL_WeaponTimeBase())
732 m_flNextAttack += 0.5f;
733 else
734 m_flNextAttack = UTIL_WeaponTimeBase() + 0.5f;
735 }
736 }
737 break;
738 case HITGROUP_CHEST:
739 {
740 flDamage *= gSkillData.plrChest;
741 //if (!m_fFrozen)// XDM
742 // UTIL_ScreenFade(this, Vector(255,80,0), 1.0f, 0.1f, clamp((int)flDamage, 10, 255), FFADE_IN);
743 }
744 break;
745 case HITGROUP_STOMACH:
746 {
747 flDamage *= gSkillData.plrStomach;
748 if (FBitSet(bitsDamageType, DMG_BULLET|DMG_ENERGYBEAM))// XDM3038a
749 PunchPitchAxis(-6.0f);
750 }
751 break;
752 case HITGROUP_LEFTARM:// flDamage *= gSkillData.Arm; break;
753 case HITGROUP_RIGHTARM:
754 {
755 flDamage *= gSkillData.plrArm;
756 if (FBitSet(bitsDamageType, DMGM_PUNCHVIEW))// XDM3035: punch player backwards
757 {
758 pev->punchangle.y = clamp(flDamage*0.5f, 0.0f, 10.0f);
759 if (ptr->iHitgroup == HITGROUP_RIGHTARM)
760 pev->punchangle.y *= -1.0f;
761 }
762 // idea does not work :( if (gSkillData.iSkillLevel == SKILL_HARD && m_flNextAttack != 0.0f)// XDM3037: you can't fire if shot in hand
763 // m_flNextAttack += 0.5f;
764 }
765 break;
766 case HITGROUP_RIGHTLEG:
767 case HITGROUP_LEFTLEG:
768 {
769 flDamage *= gSkillData.plrLeg;
770 if (FBitSet(bitsDamageType, DMGM_PUNCHVIEW))
771 {
772 PunchPitchAxis(-4.0f);// punch down
773 pev->punchangle.z = clamp(flDamage*0.5f, 0.0f, 10.0f);
774 if (ptr->iHitgroup == HITGROUP_LEFTLEG)
775 pev->punchangle.z *= -1.0f;
776 }
777 }
778 break;
779 case HITGROUP_ARMOR:
780 {
781 if (!FBitSet(bitsDamageType, DMG_IGNOREARMOR))
782 flDamage *= (1-ARMOR_TAKE_GENERIC);// XDM3037: not a proper way to calculate damage, but better than nothing
783 }
784 break;
785 }
786
787 /* CBaseMonster if (ptr->iHitgroup == HITGROUP_ARMOR)// XDM3035
788 {
789 if (g_pGameRules && g_pGameRules->FAllowEffects())
790 UTIL_Ricochet(ptr->vecEndPos, flDamage);
791 }
792 else if (BloodColor() != DONT_BLEED && !FBitSet(bitsDamageType, DMG_DONT_BLEED))// XDM
793 {
794 UTIL_BloodDrips(ptr->vecEndPos, -vecDir, BloodColor(), (int)flDamage);// a little surface blood.
795 TraceBleed(flDamage, vecDir, ptr, bitsDamageType);
796 }*/
797 CBaseMonster::TraceAttack(pAttacker, flDamage, vecDir, ptr, bitsDamageType);// XDM3038c AddMultiDamage(pAttacker, this, flDamage, bitsDamageType);
798
799 // XDM3038
800 int iMaxArmor = (g_pGameRules?g_pGameRules->GetPlayerMaxArmor():MAX_NORMAL_BATTERY);// XDM3038
801 if (FBitSet(bitsDamageType, DMGM_HUD_DISTORT) && (pev->armorvalue < (iMaxArmor/2)) && (ptr->iHitgroup == HITGROUP_GENERIC ||
802 ptr->iHitgroup == HITGROUP_HEAD || ptr->iHitgroup == HITGROUP_CHEST || ptr->iHitgroup == HITGROUP_STOMACH))
803 {
804 if (FBitSet(bitsDamageType, DMGM_HUDFAIL_COLOR))
805 SetBits(m_iHUDDistortMode, HUD_DISTORT_COLOR);
806
807 if (FBitSet(bitsDamageType, DMGM_HUDFAIL_SPR))
808 SetBits(m_iHUDDistortMode, HUD_DISTORT_SPRITE);
809
810 if (FBitSet(bitsDamageType, DMGM_HUDFAIL_POS))
811 SetBits(m_iHUDDistortMode, HUD_DISTORT_POS);
812
813 if (m_iHUDDistortValue < UCHAR_MAX)
814 m_iHUDDistortValue += (short)(flDamage/10.0f);
815 else
816 m_iHUDDistortValue = UCHAR_MAX;
817
818 m_iHUDDistortUpdate = 1;
819 }
820}
821
822
823//-----------------------------------------------------------------------------
824// Purpose: Take some damage.
825// Warning: Wach call to TakeDamage with bitsDamageType set to a time-based damage type will cause the damage time countdown to be reset.
826// Warning: Thus the ongoing effects of poison, radiation etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC.
827// Input : *pInflictor -
828// *pAttacker -
829// flDamage -
830// bitsDamageType -
831// Output : int
832//-----------------------------------------------------------------------------
833int CBasePlayer::TakeDamage(CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, int bitsDamageType)
834{
835 DBG_PRINT_ENT_TAKEDAMAGE;
836 if (pev->movetype == MOVETYPE_NOCLIP && pev->solid == SOLID_NOT)// XDM3035: disintegration
837 return 0;
838
839 if (FBitSet(pev->effects, EF_NODRAW))// XDM3035: gibbed or respawning or smth
840 return 0;
841
842 DBG_PLR_PRINT("CBasePlayer(%d)::TakeDamage(%g): health was %g of %g\n", entindex(), flDamage, pev->health, pev->max_health);
843
844 // Warning: this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc)
845 // XDM: moved inside
846 int fTookDamage = CBaseMonster::TakeDamage(pInflictor, pAttacker, /*(int)*/flDamage, bitsDamageType);
847 if (fTookDamage > 0)
848 m_lastDamageAmount = (int)flDamage;
849
850 DBG_PLR_PRINT("CBasePlayer()::TakeDamage(): exit CBaseMonster::TakeDamage()\n");
851 // have suit diagnose the problem - ie: report damage type
852 int bitsDamage = bitsDamageType;
853 float flHealthPrev = pev->health;
854
855 // Reset damage time countdown for each type of time based damage player just sustained
856 {
857 for (uint32 i = 0; i < CDMG_TIMEBASED; ++i)
858 if (FBitSet(bitsDamageType, DMG_PARALYZE << i))
859 m_rgbTimeBasedDamage[i] = 0;
860 }
861
862 if (fTookDamage == 0 || !IsAlive())// XDM3037
863 return fTookDamage;
864
865 // Tell director about it
866 if (pInflictor)
867 {
868 if (IsMultiplayer())// XDM3035a: causes buffer overflow in SP
869 {
870 MESSAGE_BEGIN(MSG_SPEC, svc_director);
871 WRITE_BYTE(9);// command length in bytes
872 WRITE_BYTE(DRC_CMD_EVENT);// take damage event
873 WRITE_SHORT(entindex());// index number of primary entity
874 WRITE_SHORT(pInflictor->entindex()); // index number of secondary entity
875 WRITE_LONG(5);// eventflags (priority and flags)
876 MESSAGE_END();
877 }
878 }
879
880 bool ffound = true;
881 bool ftrivial = (pev->health > (pev->max_health * HEALTH_PERCENT_TRIVIAL) || m_lastDamageAmount < 5);// XDM3037: use pev->max_health
882 bool fmajor = (m_lastDamageAmount > (pev->max_health * HEAVY_DAMAGE_PERCENT));
883 bool fcritical = (pev->health <= (pev->max_health * HEALTH_PERCENT_CRITICAL));
884
885 // Handle all bits set in this damage message, let the suit give player the diagnosis
886
887 // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash )
888 // UNDONE: still need to record damage and heal messages for the following types: DMG_BURN DMG_FREEZE DMG_BLAST DMG_SHOCK
889
890 SetBits(m_bitsDamageType, bitsDamage); // Save this so we can report it to the client
891 m_bitsHUDDamage = -1;// make sure the damage bits get resent
892
893 if (FBitSet(bitsDamageType, DMGM_LADDERFALL) && (flDamage > 10) && IsOnLadder())// && fTookDamage)// XDM3037: make the player fall from ladder
894 DisableLadder(1.0f);
895
896 //if (FBitSet(bitsDamageType, DMG_IGNITE) && g_pGameRules && g_pGameRules->FAllowEffects())// XDM: client
897 // UTIL_ScreenFade(this, Vector(255,120,10), 1.0, 0.1, 160, FFADE_MODULATE);
898
899 DBG_PLR_PRINT("CBasePlayer()::TakeDamage(): start sorting damage types\n");
900 while (fTookDamage && (!ftrivial || FBitSet(bitsDamage, DMGM_TIMEBASED)) && ffound && bitsDamage)
901 {
902 ffound = false;
903 if (FBitSet(bitsDamage, DMG_CLUB))
904 {
905 if (fmajor)
906 SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture
907
908 if (gSkillData.iSkillLevel == SKILL_HARD)
909 pev->punchangle += RandomVector(2.0f);// XDM3035c
910
911 ClearBits(bitsDamage, DMG_CLUB);
912 ffound = true;
913 }
914
915 if (FBitSet(bitsDamage, DMG_FALL|DMG_CRUSH))
916 {
917 if (fmajor)
918 SetSuitUpdate("!HEV_DMG5", FALSE, SUIT_NEXT_IN_30SEC);// major fracture
919 else
920 SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC);// minor fracture
921
922 ClearBits(bitsDamage, DMG_FALL | DMG_CRUSH);
923 ffound = true;
924 }
925
926 if (FBitSet(bitsDamage, DMG_BULLET))
927 {
928 if (m_lastDamageAmount > 5)
929 SetSuitUpdate("!HEV_DMG6", FALSE, SUIT_NEXT_IN_30SEC);// blood loss detected
930 //else
931 // SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC);// minor laceration
932
933 ClearBits(bitsDamage, DMG_BULLET);
934 ffound = true;
935 }
936
937 if (FBitSet(bitsDamage, DMG_SLASH))
938 {
939 if (fmajor)
940 SetSuitUpdate("!HEV_DMG1", FALSE, SUIT_NEXT_IN_30SEC); // major laceration
941 else
942 SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration
943
944 ClearBits(bitsDamage, DMG_SLASH);
945 ffound = true;
946 }
947
948 if (FBitSet(bitsDamage, DMG_SONIC))
949 {
950 if (fmajor)
951 SetSuitUpdate("!HEV_DMG2", FALSE, SUIT_NEXT_IN_1MIN); // internal bleeding
952
953 ClearBits(bitsDamage, DMG_SONIC);
954 ffound = true;
955 }
956
957 if (FBitSet(bitsDamage, DMG_POISON | DMG_PARALYZE))
958 {
959 /* XDM3037: client if (!m_fFrozen)
960 UTIL_ScreenFade(this, Vector(0,255,0), 1.0, 0.2, (int)flDamage + 20, FFADE_IN);*/
961 SetSuitUpdate("!HEV_DMG3", FALSE, SUIT_NEXT_IN_1MIN); // blood toxins detected
962 ClearBits(bitsDamage, DMG_POISON | DMG_PARALYZE);
963 ffound = TRUE;
964 }
965
966 if (FBitSet(bitsDamage, DMG_ACID))
967 {
968 SetSuitUpdate("!HEV_DET1", FALSE, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected
969 ClearBits(bitsDamage, DMG_ACID);
970 ffound = true;
971 }
972
973 if (FBitSet(bitsDamage, DMG_NERVEGAS))
974 {
975 SetSuitUpdate("!HEV_DET0", FALSE, SUIT_NEXT_IN_1MIN); // biohazard detected
976 ClearBits(bitsDamage, DMG_NERVEGAS);
977 ffound = true;
978 }
979
980 if (FBitSet(bitsDamage, DMG_RADIATION))
981 {
982 SetSuitUpdate("!HEV_DET2", FALSE, SUIT_NEXT_IN_1MIN); // radiation detected
983 ClearBits(bitsDamage, DMG_RADIATION);
984 ffound = true;
985 }
986
987 /* XDM3037: client if (FBitSet(bitsDamage, DMG_DROWN))
988 {
989 if (!m_fFrozen)
990 UTIL_ScreenFade(this, Vector(200,200,200), 1.0, 0.2, 120, FFADE_IN);
991 }*/
992
993 if (FBitSet(bitsDamage, DMG_SHOCK))
994 {
995 SetSuitUpdate("!HEV_SHOCK", FALSE, SUIT_NEXT_IN_30SEC); // XDM: electric
996 ClearBits(bitsDamage, DMG_SHOCK);
997 ffound = true;
998 }
999
1000 if (FBitSet(bitsDamage, DMG_BURN))// XDM: fire
1001 {
1002 SetSuitUpdate("!HEV_FIRE", FALSE, SUIT_NEXT_IN_1MIN);
1003 ClearBits(bitsDamage, DMG_BURN);
1004 if (m_fFrozen)
1005 FrozenEnd();
1006
1007 ffound = true;
1008 }
1009
1010 if (FBitSet(bitsDamage, DMG_SLOWBURN))// XDM: fire
1011 {
1012 ClearBits(bitsDamage, DMG_SLOWBURN);
1013 if (m_fFrozen)
1014 FrozenEnd();
1015
1016 ffound = true;
1017 }
1018 }
1019
1020 if (fTookDamage)
1021 {
1022 if (flHealthPrev >= (HEALTH_PERCENT_TRIVIAL*pev->max_health))
1023 {
1024 if (!ftrivial)
1025 {
1026 // first time we take major damage...
1027 SetSuitUpdate("!HEV_MED1", FALSE, SUIT_NEXT_IN_30MIN);// automedic on
1028 // give morphine shot if not given recently
1029 if (fmajor)
1030 SetSuitUpdate("!HEV_HEAL7", FALSE, SUIT_NEXT_IN_30MIN);// morphine shot
1031 }
1032 }
1033 else// if (!ftrivial && fcritical && flHealthPrev < 75)
1034 {
1035 if (!ftrivial && fcritical)
1036 {
1037 // already took major damage, now it's critical...
1038 if (pev->health < (pev->max_health * HEALTH_PERCENT_NEARDEATH))
1039 SetSuitUpdate("!HEV_HLTH3", FALSE, SUIT_NEXT_IN_10MIN); // near death
1040 else if (pev->health < (pev->max_health * HEALTH_PERCENT_CRITICAL))
1041 SetSuitUpdate("!HEV_HLTH2", FALSE, SUIT_NEXT_IN_10MIN); // health critical
1042
1043 // give critical health warnings
1044 if (!RANDOM_LONG(0,3) && (flHealthPrev < pev->max_health/2))
1045 SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention
1046 }
1047 // if we're taking time based damage, warn about its continuing effects
1048 if (FBitSet(bitsDamageType, DMGM_TIMEBASED))
1049 {
1050 if (flHealthPrev < pev->max_health/2)
1051 SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention
1052 else
1053 SetSuitUpdate("!HEV_HLTH1", FALSE, SUIT_NEXT_IN_10MIN); // health dropping
1054 }
1055 }
1056 }
1057 return fTookDamage;
1058}
1059
1060//-----------------------------------------------------------------------------
1061// Purpose: Killed
1062// Input : *pInflictor -
1063// *pAttacker -
1064// iGib -
1065//-----------------------------------------------------------------------------
1066void CBasePlayer::Killed(CBaseEntity *pInflictor, CBaseEntity *pAttacker, int iGib)
1067{
1068 DBG_PLR_PRINT("CBasePlayer(%d)::Killed(%d %s, %d %s, %d)\n", entindex(), pInflictor?pInflictor->entindex():0, pInflictor?STRING(pInflictor->pev->classname):"", pAttacker?pAttacker->entindex():0, pAttacker?STRING(pAttacker->pev->netname):"", iGib);
1069 SetBits(m_iGameFlags, EGF_DIED);// XDM3038c
1070 if (pev->deadflag == DEAD_NO)// XDM3035: player may get 'killed' second time like dead monsters
1071 {
1072 if (pev->health > 0)// XDM3037a: ?!
1073 {
1074 //DBG_FORCEBREAK// XDM3037a: TEST
1075 pev->health = 0;
1076 }
1077 SetAnimation(PLAYER_DIE);// Call this while pev->deadflag is 0!
1078 pev->gaitsequence = GAITSEQUENCE_DISABLED;// XDM3037: don't move legs
1079 pev->deadflag = DEAD_DYING;// set here so weapons may detect it
1080 pev->button = 0;// XDM3037a
1081 //m_bReadyPressed = false;// XDM3037a
1082 m_fDiedTime = gpGlobals->time;// XDM3038c
1083
1084 // Holster weapon immediately, to allow it to cleanup
1085 if (m_pActiveItem)// && m_pActiveItem->GetHost())// XDM3035a: somehow glock with m_pPlayer == nullptr got here
1086 {
1087 m_pActiveItem->Holster();// some weapons may trigger fire when put away
1088 m_pActiveItem->SetThinkNull();// XDM3035b
1089 m_pActiveItem->DontThink();// XDM3038a
1090 // NO! PackDeadPlayerItems needs it: m_pActiveItem = nullptr;
1091 m_pLastItem = nullptr;
1092 }
1093 m_pNextItem = nullptr;// XDM
1094 m_iDeaths++;// XDM3038c: before GameRules()!
1095
1096//#if defined (_DEBUG)// now
1097 //if (pAttacker == this)
1098 // DBG_FORCEBREAK;
1099//#endif
1100 // MUST be called BEORE removing any items!
1101 if (g_pGameRules)
1102 g_pGameRules->PlayerKilled(this, pAttacker, pInflictor);
1103
1104 if (m_pTank != nullptr)
1105 {
1106 m_pTank->Use(this, this, USE_OFF, 0);
1107 m_pTank = nullptr;
1108 }
1109
1110 TrainDetach();
1111
1112 if (m_hCarryingObject)
1113 {
1114 m_hCarryingObject->Use(this, this, USE_TOGGLE/*COU_DROP*/, 0.0);
1115 m_hCarryingObject = nullptr;
1116 }
1117
1118 // this client isn't going to be thinking for a while, so reset the sound until they respawn
1119 CSound *pSound = CSoundEnt::SoundPointerForIndex(CSoundEnt::ClientSoundIndex(edict()));
1120 {
1121 if (pSound)
1122 pSound->Reset();
1123 }
1124 m_iRespawnFrames = 0;
1125 pev->modelindex = g_iModelIndexPlayer;// don't use eyes
1126 pev->movetype = MOVETYPE_TOSS;
1127 pev->friction *= 0.5f;// XDM3038a
1128 ClearBits(pev->flags, FL_ONGROUND);
1129 Remember(bits_MEMORY_KILLED);// XDM3037
1130 if (pev->velocity.z < 10)
1131 pev->velocity.z += min(10+fabs(pev->health)*2,300);// XDM3038a: was RANDOM_FLOAT(0, 300);
1132
1133 SetSuitUpdate(nullptr, FALSE, 0);// clear out the suit message cache so we don't keep chattering
1134
1135 // XDM3037: UpdateClientData(clientdata_s) takes care of weapons now
1136 pev->fov = 0.0f;// reset FOV
1137
1138 EnableControl(true);// XDM3037: FIX: players died with controls disabled can not respawn!
1139 }//pev->deadflag == DEAD_NO
1140
1141 if (FBitSet(pev->effects, EF_NODRAW))// XDM3035: gibbed or respawning or smth (should never get here)
1142 return;
1143
1144 // *** ONLY EFFECTS AFTER THIS LINE! No logic and deadflags! ***
1145 CSoundEnt::InsertSound(bits_SOUND_DEATH, pev->origin, 256, 1.0f);// XDM3035c
1146
1147 // UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12
1148 // UTIL_ScreenFade( edict(), Vector(128,0,0), 6, 15, 255, FFADE_OUT | FFADE_MODULATE );
1149 if (iGib == GIB_DISINTEGRATE)
1150 {
1151 pev->movetype = MOVETYPE_NOCLIP;
1152 pev->solid = SOLID_NOT;
1153 SetBits(pev->flags, FL_FLY);
1154 pev->velocity.Set(0.0f,0.0f,4.0f);
1155 pev->origin.z += 0.5f;// HACK
1156 pev->avelocity.Set(RANDOM_FLOAT(-2.0, 2.0), RANDOM_FLOAT(-2.0, 2.0), RANDOM_FLOAT(-4.0, 4.0));
1157 SetBits(pev->effects, EF_MUZZLEFLASH);
1158 pev->rendermode = kRenderTransTexture;
1159 pev->rendercolor.Set(127,127,127);
1160 pev->renderamt = 160;
1161 pev->framerate *= 0.25f;
1162 pev->gravity = 0.0f;
1163 pev->takedamage = DAMAGE_NO;
1164 if (g_pGameRules && g_pGameRules->FAllowEffects())// Disintegration effect allowed?
1165 {
1166 ParticleBurst(pev->origin, 20, 5, 10);// Center() is not necessary for players
1167 UTIL_ScreenShakeOne(this, pev->origin, 10.0f, 0.5f, 3.0f);
1168 pev->punchangle = pev->avelocity * 20.0f;//Set(RANDOM_FLOAT(-48,48), RANDOM_FLOAT(-48,48), RANDOM_FLOAT(-48,48));
1169 }
1170 UTIL_ScreenFade(this, g_vecZero, 1.5f, mp_respawntime.value + 1.0f, 255, FFADE_OUT | FFADE_MODULATE | FFADE_STAYOUT);// XDM3035 :3
1171
1172 // this shit does nothing SetThink(&CBasePlayer::PlayerDeathThink);
1173 // DON'T Disintegrate(); players!! Emulate!
1174 }
1175 else if (iGib == GIB_FADE)// XDM3037: UNDONE
1176 {
1177 pev->rendermode = kRenderTransTexture;
1178 pev->rendercolor.Set(127,127,127);
1179 pev->renderamt = 127;
1180 }
1181 else if (iGib == GIB_REMOVE)// XDM3037
1182 {
1183 SetBits(pev->effects, EF_NODRAW|EF_NOINTERP);
1184 pev->renderamt = 0;
1185 }
1186 else if (ShouldGibMonster(iGib))// XDM3034
1187 {
1188 //pev->modelindex = 0;// XDM3038a: don't mess with model indexes
1189 pev->avelocity = RandomVector(RANDOM_FLOAT(-320,320), RANDOM_FLOAT(-320,320), RANDOM_FLOAT(-160,160));// XDM
1190 pev->solid = SOLID_NOT;
1191 if (pAttacker && pAttacker->IsPlayer())
1192 ((CBasePlayer *)pAttacker)->m_Stats[STAT_GIB_COUNT]++;// XDM3038c
1193
1194 if (GibMonster())// This clears pev->model
1195 {
1196 CGib *pHeadGib = CGib::SpawnHeadGib(this);
1197 if (pHeadGib)
1198 {
1199 pHeadGib->pev->owner = edict();
1200 UTIL_SetView(edict(), pHeadGib->edict());// this is potentially dangerous, the gib must return view if removed
1201 pev->view_ofs.Clear();// Set(0,0,8);
1202 }
1203 }
1204 //SET_MODEL(edict(), g_szDefaultGibsHuman);// this doesn't work
1205 //pev->movetype = MOVETYPE_TOSS;
1206 SetBits(pev->effects, EF_NODRAW|EF_NOINTERP);
1207
1208 if (mp_playerexplode.value > 0)// XDM3034: players carrying fuel tanks will explode! :D
1209 {
1210 if (m_pActiveItem && m_pActiveItem->pszAmmo1())// check if pszAmmo1 exists (!= nullptr) before comparing!
1211 {
1212 if (strcmp(m_pActiveItem->pszAmmo1(), "fuel") == 0)// XDM3038a: full check to prevent name collisions!
1213 {
1214 CBasePlayerWeapon *pWeapon = m_pActiveItem->GetWeaponPtr();
1215 if (pWeapon && pWeapon->PrimaryAmmoIndex() > AMMOINDEX_NONE && pWeapon->HasAmmo(AMMO_PRIMARY))
1216 {
1217 pev->takedamage = DAMAGE_NO;// ! stack overflow prevention
1218 ExplosionCreate(pev->origin, pev->angles, nullptr, this, 2.5f*AmmoInventory(pWeapon->PrimaryAmmoIndex()), SF_NOSPARKS|SF_NOPARTICLES, 0.0f);
1219 m_rgAmmo[pWeapon->PrimaryAmmoIndex()] = 0;// XDM3038c
1220 }
1221 }
1222 }
1223 }
1224 }
1225 else if (!HasMemory(bits_MEMORY_KILLED))// XDM3037: don't play DeathSound() everytime this dead body is hit
1226 {
1227 DeathSound();
1228 SetBits(pev->flags, FL_FLOAT);// XDM3037a
1229 // XDM3035 pev->angles.x = 0;
1230 //pev->angles.z = 0;
1231 // this shit does nothing SetThink(&CBasePlayer::PlayerDeathThink);
1232 if (FlashlightIsOn() && IsMultiplayer())// XDM3037a: in singleplayer it's fine
1233 FlashlightTurnOff();
1234 }
1235 SetNextThink(0.1f);// does nothing on players
1236}
1237
1238//-----------------------------------------------------------------------------
1239// Purpose: Get position where to shoot at (for enemies)
1240// Note : Called by PostThink() and also may get called by some events
1241// Input : posSrc -
1242// Output :
1243//-----------------------------------------------------------------------------
1244Vector CBasePlayer::BodyTarget(const Vector &posSrc)
1245{
1246 Vector vecTarget(Center());// don't static this
1247 vecTarget += pev->view_ofs * RANDOM_FLOAT(0.5f, 1.0f);
1248 return vecTarget;
1249}
1250
1251//-----------------------------------------------------------------------------
1252// Purpose: Set the activity based on an event or current state
1253// Note : Called by PostThink() and also may get called by some events
1254// Input : playerAnim -
1255//-----------------------------------------------------------------------------
1256void CBasePlayer::SetAnimation(const int &iNewPlayerAnim)
1257{
1258 if (pev->deadflag != DEAD_NO)
1259 return;
1260
1261 vec_t speed2D;
1262 int playerAnim = iNewPlayerAnim;
1263 int animDesired = planim_look_idle;
1264 char szAnim[32];// XDM3037: mstudioseqdesc_t has 32 anyway
1265 bool bIsJumping;
1266 if (FBitSet(pev->flags, FL_FROZEN))
1267 {
1268 speed2D = 0.0f;
1269 bIsJumping = false;
1270 playerAnim = PLAYER_IDLE;
1271 }
1272 else
1273 {
1274 speed2D = pev->velocity.Length2D();// 2D is required!
1275 bIsJumping = !FBitSet(pev->flags, FL_ONGROUND) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) && (m_fGaitAnimFinishTime > gpGlobals->time);
1276 }
1277 //else if (m_Activity == ACT_RANGE_ATTACK1)
1278 // goto skipplayeranim;
1279 //conprintf(1, "CBasePlayer::SetAnimation(%d) BEGIN speed %g, m_Activity %d, bIsJumping %d\n", playerAnim, speed2D, m_Activity, bIsJumping?1:0);
1280
1281 if (m_fSequenceFinished && m_Activity == ACT_RANGE_ATTACK1)
1282 m_Activity = ACT_RESET;// finished shooting, unblock other animaitons
1283
1284 switch (playerAnim)
1285 {
1286 case PLAYER_JUMP:
1287 {
1288 m_IdealActivity = ACT_HOP;
1289 m_movementActivity = m_IdealActivity;
1290 bIsJumping = true;
1291 }
1292 break;
1293 case PLAYER_SUPERJUMP:
1294 {
1295 m_IdealActivity = ACT_LEAP;
1296 m_movementActivity = m_IdealActivity;
1297 bIsJumping = true;
1298 }
1299 break;
1300 case PLAYER_DIE:
1301 {
1302 //no! m_IdealActivity = ACT_DIESIMPLE;
1303 m_IdealActivity = GetDeathActivity();
1304 m_movementActivity = ACT_RESET;
1305 }
1306 break;
1307 case PLAYER_ATTACK1:
1308 {
1309 switch (m_Activity)// don't interrupt some of higher priority animations
1310 {
1311 //case ACT_HOVER:
1312 case ACT_SWIM:
1313 //case ACT_HOP:
1314 case ACT_LEAP:
1315 //this may be a new attack with a different weapon case ACT_RANGE_ATTACK1:
1316 case ACT_DIESIMPLE:
1317 case ACT_DIEBACKWARD:
1318 case ACT_DIEFORWARD:
1319 case ACT_DIEVIOLENT:
1320 case ACT_DIE_HEADSHOT:
1321 case ACT_DIE_CHESTSHOT:
1322 case ACT_DIE_GUTSHOT:
1323 case ACT_DIE_BACKSHOT:
1324 m_IdealActivity = m_Activity;
1325 //conprintf(1, " SetAnimation(PLAYER_ATTACK1) IGNORE %d\n", RANDOM_LONG(1000,9999));
1326 break;
1327 default:
1328 m_IdealActivity = ACT_RANGE_ATTACK1;
1329 //conprintf(1, " SetAnimation(PLAYER_ATTACK1) ACCEPT %d\n", RANDOM_LONG(1000,9999));
1330 break;
1331 }
1332 // this is the place where we do not touch m_movementActivity
1333 }
1334 break;
1335 case PLAYER_IDLE:
1336 case PLAYER_WALK:
1337 {
1338 if (bIsJumping)// Still jumping
1339 {
1340 m_IdealActivity = m_Activity;
1341 }
1342 else if (!FBitSet(pev->flags, FL_ONGROUND) && pev->waterlevel > WATERLEVEL_NONE)
1343 {
1344 if (speed2D <= PLAYER_MAX_WALK_SPEED/20)// XDM3037: must be synchronized with PM_WaterMove()!
1345 m_IdealActivity = ACT_HOVER;
1346 else
1347 m_IdealActivity = ACT_SWIM;
1348 }
1349 else
1350 {
1351 if (speed2D > PLAYER_MAX_STAND_SPEED)
1352 m_IdealActivity = ACT_WALK;
1353 else
1354 m_IdealActivity = ACT_IDLE;
1355 }
1356 m_movementActivity = m_IdealActivity;
1357 }
1358 break;
1359 case PLAYER_ARM:
1360 m_IdealActivity = ACT_ARM;// XDM: draw animations
1361 break;
1362 case PLAYER_DISARM:
1363 m_IdealActivity = ACT_DISARM;// XDM: holster animations
1364 break;
1365 case PLAYER_RELOAD:
1366 m_IdealActivity = ACT_RELOAD;// XDM: reload animations
1367 break;
1368 case PLAYER_CLIMB:
1369 {
1370 m_IdealActivity = ACT_WALK;// XDM: ladder climb animations replacement
1371 m_movementActivity = m_IdealActivity;
1372 }
1373 break;
1374 case PLAYER_FALL:// XDM: fall animations
1375 {
1376 if (pev->waterlevel > WATERLEVEL_FEET)
1377 m_movementActivity = ACT_HOVER;
1378 else if (bIsJumping)// Still jumping
1379 m_movementActivity = m_Activity;
1380 else if (FBitSet(m_afButtonPressed, IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT))
1381 m_movementActivity = ACT_RUN;
1382 else
1383 m_IdealActivity = ACT_HOVER;// UNDONE: ACT_FALL; there's no such animaiton :(
1384
1385 //m_movementActivity = m_IdealActivity;
1386 }
1387 break;
1388 case PLAYER_USE:// XDM3039
1389 m_IdealActivity = ACT_USE;
1390 break;
1391 }
1392
1393 //conprintf(1, " SetAnimation(%d) m_IdealActivity %d\n", playerAnim, m_IdealActivity);
1394//skipplayeranim:
1395
1396 bool bForceRestart = false;
1397 switch (m_IdealActivity)
1398 {
1399 case ACT_RANGE_ATTACK1:// player fires a weapon
1400 {
1401 if (FBitSet(pev->flags, FL_DUCKING))
1402 strcpy(szAnim, "crouch_shoot_");
1403 else
1404 strcpy(szAnim, "ref_shoot_");
1405
1406 strcat(szAnim, m_szAnimExtention);
1407 animDesired = LookupSequence(szAnim);
1408 ASSERT(animDesired != ACTIVITY_NOT_AVAILABLE);
1409 if (animDesired == ACTIVITY_NOT_AVAILABLE && !FBitSet(pev->flags, FL_DUCKING))
1410 animDesired = planim_shoot_1;// set to default
1411
1412 m_Activity = m_IdealActivity;// even if animation fails?
1413 bForceRestart = true;
1414 }
1415 break;
1416 case ACT_DIESIMPLE:
1417 case ACT_DIEBACKWARD:
1418 case ACT_DIEFORWARD:
1419 case ACT_DIEVIOLENT:
1420 case ACT_DIE_HEADSHOT:
1421 case ACT_DIE_CHESTSHOT:
1422 case ACT_DIE_GUTSHOT:
1423 case ACT_DIE_BACKSHOT:
1424 {
1425 if (m_Activity != m_IdealActivity)
1426 {
1427 animDesired = LookupActivity(m_IdealActivity);
1428 m_Activity = m_IdealActivity;
1429 }
1430 }
1431 break;
1432 default:
1433 /*{
1434 if (m_Activity != m_IdealActivity || m_fSequenceFinished)
1435 animDesired = LookupActivity(m_Activity);
1436 else
1437 animDesired = pev->sequence;// don't re-pick sequence for same activity!
1438
1439 m_Activity = m_IdealActivity;
1440 }
1441 break;
1442 case ACT_IDLE:
1443 case ACT_WALK:*/
1444 {
1445 if (m_Activity == ACT_RESET || /*m_IdealActivity == ACT_ARM || */((m_fSequenceFinished || bIsJumping) && m_Activity != ACT_RANGE_ATTACK1))// if we're NOT shooting and previous sequence is finished
1446 {
1447 if (pev->weaponmodel > 0 || (m_pActiveItem && !m_pActiveItem->IsHolstered()))// || m_pNextItem)// this decides. Don't start idle animations while switching weapon
1448 {
1449 if (m_szAnimExtention[0] != '\0')
1450 {
1451 if (FBitSet(pev->flags, FL_DUCKING))
1452 strcpy(szAnim, "crouch_aim_");
1453 else
1454 strcpy(szAnim, "ref_aim_");
1455
1456 strcat(szAnim, m_szAnimExtention);
1457 animDesired = LookupSequence(szAnim);// we don't use LookupActivity() here
1458 ASSERT(animDesired != ACTIVITY_NOT_AVAILABLE);
1459 }
1460 else
1461 animDesired = ACTIVITY_NOT_AVAILABLE;
1462
1463 if (animDesired == ACTIVITY_NOT_AVAILABLE)
1464 {
1465 if (FBitSet(pev->flags, FL_DUCKING))
1466 animDesired = planim_crouch_idle;// set to default
1467 else
1468 animDesired = planim_aim_1;// set to default
1469 }
1470 }
1471 else if (m_pTank.Get())
1472 {
1473 if (FBitSet(pev->flags, FL_DUCKING))
1474 animDesired = planim_crouch_idle;//LookupSequence();
1475 else
1476 animDesired = planim_aim_2;
1477 }
1478 else if (m_IdealActivity != m_Activity || m_fSequenceFinished)// activity changed OR last activity just finished playing
1479 {
1480 if (m_IdealActivity == ACT_IDLE && (m_pActiveItem && (UTIL_WeaponTimeBase() - m_pActiveItem->GetLastUseTime() >= WEAPON_LONG_IDLE_TIME)))//&& RANDOM_LONG(0,3) == 0)
1481 animDesired = m_iSequenceDeepIdle;
1482 else
1483 animDesired = LookupActivity(m_IdealActivity);// update or renew animaiton
1484 }
1485
1486 m_Activity = m_IdealActivity;
1487 }
1488 else
1489 animDesired = pev->sequence;// keep old upper body animation
1490 }
1491 break;
1492 }
1493
1494 //conprintf(1, " SetAnimation(%d) animDesired %d\n", playerAnim, animDesired);
1495
1496 //ASSERT(animDesired != ACTIVITY_NOT_AVAILABLE);
1497 if (animDesired == ACTIVITY_NOT_AVAILABLE)
1498 animDesired = planim_look_idle;
1499
1500 if (pev->sequence != animDesired)// Already using the desired animation?
1501 {
1502 if (!m_fSequenceLoops)
1503 SetBits(pev->effects, EF_NOINTERP);
1504
1505 pev->sequence = animDesired;
1506 pev->frame = 0;// Reset to first frame of desired animation
1507 ResetSequenceInfo();
1508 //conprintf(1, " SetAnimation(%d) animDesired %d ResetSequenceInfo()\n", playerAnim, animDesired);
1509 }
1510
1511 if (!m_fSequenceLoops && (bForceRestart || m_fSequenceFinished))
1512 pev->frame = 0;
1513
1514//skipsequence:
1515 //conprintf(1, " ::SetAnimation(%d) pev->sequence %d pev->gaitsequence %d END\n", playerAnim, pev->sequence, pev->gaitsequence);
1516}
1517
1518//-----------------------------------------------------------------------------
1519// Purpose: Updates gaitsequence according to current situation
1520// Note : Called "Set" to match SetAnimation() and not to conflict with Update functions
1521// Warning: gaitsequence can ONLY be toggled ON/OFF! No control on server side. It is always looped.
1522// Warning: we can only ASSUME gaitsequence length, so it's a barely reliable HACK
1523// Warning: XDM3037 allows gaitsequence 0! OFF is -1!
1524//-----------------------------------------------------------------------------
1525void CBasePlayer::SetGaitAnimation(void)
1526{
1527 if (pev->deadflag != DEAD_NO)
1528 return;
1529
1530 // A little info: gaitsequence is played on client side, it is always looped (forced), it cannot be controlled on server (on/off only).
1531 vec_t speed2D;
1532 int oldgaitsequence = pev->gaitsequence;
1533 bool bIsJumping;
1534 bool bMoveControls = FBitSet(pev->button, (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT));
1535 //bool bDuckControls = FBitSet(pev->button, IN_DUCK);
1536 if (FBitSet(pev->flags, FL_FROZEN))
1537 {
1538 speed2D = 0.0f;
1539 bIsJumping = false;
1540 pev->gaitsequence = m_iSequenceDeepIdle;
1541 }
1542 else
1543 {
1544 speed2D = pev->velocity.Length2D();// 2D is required!
1545 //bIsJumping = !FBitSet(pev->flags, FL_ONGROUND) && (m_movementActivity == ACT_HOP || m_movementActivity == ACT_LEAP);
1546 bIsJumping = !FBitSet(pev->flags, FL_ONGROUND) && (m_movementActivity == ACT_HOP || m_movementActivity == ACT_LEAP) && (m_fGaitAnimFinishTime > gpGlobals->time);
1547
1548 if (FBitSet(pev->flags, FL_DUCKING))
1549 {
1550 if (pev->waterlevel >= WATERLEVEL_WAIST)
1551 pev->gaitsequence = LookupActivity(ACT_SWIM);// animation with horizontal body orientation
1552 else if (bIsJumping && m_flFallVelocity < 20)// lots of hacks here
1553 pev->gaitsequence = LookupActivity(m_movementActivity);// lower body will do jumping
1554 else if (speed2D > PLAYER_MAX_STAND_SPEED || bMoveControls)// even in the air
1555 pev->gaitsequence = LookupActivity(ACT_CROUCH);// moving while crouching
1556 else
1557 pev->gaitsequence = LookupActivity(ACT_CROUCHIDLE);// crouching position
1558 }
1559 /* wrong! else if (bDuckControls)// player just pressed CROUCH, but crouching not finished
1560 {
1561 if (pev->waterlevel >= WATERLEVEL_WAIST)
1562 pev->gaitsequence = LookupActivity(ACT_SWIM);
1563 else
1564 pev->gaitsequence = LookupActivity(ACT_CROUCH);// move legs
1565 }*/
1566 else if (bIsJumping)// HACK: there's NO !m_fSequenceFinished for GAITsequences and will never be, so we just check FL_ONGROUND :(
1567 {
1568 if (pev->waterlevel >= WATERLEVEL_WAIST)
1569 pev->gaitsequence = LookupActivity(ACT_HOVER);
1570 else
1571 pev->gaitsequence = LookupActivity(m_movementActivity);//ACT_HOP/ACT_LEAP);
1572 }
1573 else if (speed2D > PLAYER_MAX_WALK_SPEED)// run detected
1574 {
1575 if (pev->waterlevel >= WATERLEVEL_WAIST)
1576 pev->gaitsequence = LookupActivity(ACT_SWIM);
1577 else if (FBitSet(pev->flags, FL_ONGROUND) || bMoveControls)// move legs while onground or indicate attempts to move in the air
1578 pev->gaitsequence = LookupActivity(ACT_RUN);
1579 else
1580 pev->gaitsequence = GAITSEQUENCE_DISABLED;// falling. better ideas?
1581 }
1582 else if (speed2D > PLAYER_MAX_STAND_SPEED || bMoveControls)// walk detected
1583 {
1584 if (pev->waterlevel >= WATERLEVEL_WAIST)
1585 pev->gaitsequence = LookupActivity(ACT_HOVER);
1586 else if (FBitSet(pev->flags, FL_ONGROUND) || bMoveControls)// move legs while onground or indicate attempts to move in the air
1587 pev->gaitsequence = LookupActivity(ACT_WALK);
1588 else
1589 pev->gaitsequence = GAITSEQUENCE_DISABLED;
1590 }
1591 else// not moving
1592 {
1593 if (pev->waterlevel >= WATERLEVEL_WAIST && !FBitSet(pev->flags, FL_ONGROUND))
1594 pev->gaitsequence = LookupActivity(ACT_HOVER);
1595 else
1596 pev->gaitsequence = pev->sequence;// TESTME: use upper body animaiton
1597 // acceptable: m_iSequenceDeepIdle;
1598 // not acceptable: LookupActivity(ACT_IDLE); will pick random animaiton EVERY GAME FRAME!
1599 }
1600
1601 if (pev->gaitsequence == ACTIVITY_NOT_AVAILABLE)
1602 pev->gaitsequence = GAITSEQUENCE_DISABLED;
1603 }
1604
1605 if (pev->gaitsequence != oldgaitsequence)
1606 {
1607 //conprintf(1, "pev->gaitsequence = %d CHANGED\n", pev->gaitsequence);
1608 if (pev->gaitsequence != GAITSEQUENCE_DISABLED)// XDM3037: HACK! Assume the gaitsequence length (time when it finishes).
1609 {
1610 studiohdr_t *pStudioHdr = (studiohdr_t *)GET_MODEL_PTR(edict());
1611 if (pStudioHdr)
1612 {
1613 mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)pStudioHdr + pStudioHdr->seqindex) + pev->gaitsequence;
1614 if (pseqdesc)// WARNING! gaitsequence fps is not constant!! we can't calculate it here!
1615 m_fGaitAnimFinishTime = gpGlobals->time + (pseqdesc->numframes / pseqdesc->fps);// +animaiton length
1616 else
1617 m_fGaitAnimFinishTime = 0;
1618 }
1619 else
1620 m_fGaitAnimFinishTime = 0;
1621 }
1622 else
1623 m_fGaitAnimFinishTime = 0;
1624 }
1625}
1626
1627//-----------------------------------------------------------------------------
1628// Purpose: Safe method to set animation extnsion
1629// Input : *szExtention -
1630//-----------------------------------------------------------------------------
1631void CBasePlayer::SetWeaponAnimType(const char *szExtention)
1632{
1633 if (szExtention)
1634 {
1635 strncpy(m_szAnimExtention, szExtention, 31);
1636 m_szAnimExtention[31] = '\0';
1637 }
1638}
1639
1640//-----------------------------------------------------------------------------
1641// Purpose: Called at the start of every frame regardless of waterlevel
1642//-----------------------------------------------------------------------------
1643void CBasePlayer::WaterMove(void)
1644{
1645 if (pev->movetype == MOVETYPE_NOCLIP)
1646 return;
1647
1648 if (pev->waterlevel >= WATERLEVEL_WAIST)// XDM3038a
1649 {
1650 m_flBurnTime = 0.0f;
1651 ClearBits(m_bitsDamageType, DMGM_FIRE|DMG_IGNITE);
1652
1653 if (pev->waterlevel == WATERLEVEL_WAIST)// water circles
1654 {
1655 if (g_pGameRules && g_pGameRules->FAllowEffects() && m_fWaterCircleTime < gpGlobals->time)
1656 {
1657 BeamEffect(TE_BEAMDISK, pev->origin + Vector(0,0,4), pev->origin + Vector(0,0,56), g_iModelIndexShockWave, 0, 10, 10, 1, 64/*noise*/, Vector(95,95,95), 64, 0);
1658 m_fWaterCircleTime = gpGlobals->time + 1.0f;
1659 }
1660 }
1661 if (!FBitSet(pev->flags, FL_INWATER))
1662 SetBits(pev->flags, FL_INWATER);
1663 }
1664
1665 if (pev->waterlevel < WATERLEVEL_HEAD)
1666 {
1667 // 'up for air' sound
1668 if (pev->air_finished < gpGlobals->time)
1669 EMIT_SOUND(edict(), CHAN_VOICE, "player/pl_wade1.wav", VOL_NORM, ATTN_NORM);
1670 else if (pev->air_finished < gpGlobals->time + gSkillData.PlrAirTime*0.5f)
1671 EMIT_SOUND(edict(), CHAN_VOICE, "player/pl_wade2.wav", VOL_NORM, ATTN_NORM);
1672
1673 pev->air_finished = gpGlobals->time + gSkillData.PlrAirTime;
1674 pev->dmg = 2.0f;
1675
1676 if (m_idrowndmg > m_idrownrestored)// if we took drowning damage, give it back slowly
1677 {
1678 // Set drowning damage bit. HACK: dmg_drownrecover actually makes the time based damage code 'give back' health over time.
1679 // make sure counter is cleared so we start count correctly.
1680 // NOTE: this actually causes the count to continue restarting until all drowning damage is healed.
1681 ClearBits(m_bitsDamageType, DMG_DROWN);
1682 SetBits(m_bitsDamageType, DMG_DROWNRECOVER);
1683 m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;
1684 }
1685
1686 if (pev->waterlevel == WATERLEVEL_NONE)
1687 {
1688 if (FBitSet(pev->flags, FL_INWATER))
1689 ClearBits(pev->flags, FL_INWATER);
1690
1691 return;
1692 }
1693 }
1694 else// fully under water
1695 {
1696 // stop restoring damage while underwater
1697 ClearBits(m_bitsDamageType, DMG_DROWNRECOVER);
1698 m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;
1699
1700 if (pev->air_finished < gpGlobals->time)// drown
1701 {
1702 if (pev->pain_finished < gpGlobals->time)
1703 {
1704 pev->dmg += 1.0f;
1705 if (pev->dmg > 5.0f)
1706 pev->dmg = 5.0f;
1707
1708 TakeDamage(g_pWorld, g_pWorld, pev->dmg, DMG_DROWN);// XDM3034
1709 pev->pain_finished = gpGlobals->time + 1.0;
1710 m_idrowndmg += pev->dmg;// track drowning damage to give it back when player finally takes a breath
1711 }
1712 }
1713 else
1714 {
1715 ClearBits(m_bitsDamageType, DMG_DROWN);
1716
1717 if (RANDOM_LONG(0,31) == 0 && RANDOM_LONG(0,gSkillData.PlrAirTime-1) >= (int)(pev->air_finished - gpGlobals->time))
1718 {
1719 //FX_BubblesPoint(GetGunPosition(), VECTOR_CONE_45DEGREES, 16); bubbles are in CBaseMonster::TakeDamage()
1720 switch (RANDOM_LONG(0,3))
1721 {
1722 case 0: EMIT_SOUND(edict(), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break;
1723 case 1: EMIT_SOUND(edict(), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break;
1724 case 2: EMIT_SOUND(edict(), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break;
1725 case 3: EMIT_SOUND(edict(), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break;
1726 }
1727 }
1728 }
1729 }
1730}
1731
1732//-----------------------------------------------------------------------------
1733// Purpose: Handles +USE keypress, not for spectator
1734//-----------------------------------------------------------------------------
1735void CBasePlayer::PlayerUse(void)
1736{
1737 if (!FBitSet(pev->button | m_afButtonPressed | m_afButtonReleased, IN_USE))// was USE pressed, released, changed?
1738 return;
1739
1740 if (FBitSet(m_afButtonPressed, IN_USE))
1741 {
1742 if (m_pTank != nullptr)
1743 {
1744 // Stop controlling the tank
1745 // TODO: Send HUD Update
1746 m_pTank->Use(this, this, USE_OFF, 0);
1747 m_pTank = nullptr;
1748 return;
1749 }
1750 else
1751 {
1752 if (IsOnTrain())// XDM3035b: +USE on a train
1753 {
1754 TrainDetach();// don't stop the train, just disable controls
1755 return;
1756 }
1757 else// Start controlling the train!
1758 {
1759 if (!FBitSet(pev->button, IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND))
1760 {
1761 CBaseEntity *pTrain = CBaseEntity::Instance(pev->groundentity);
1762 if (pTrain && FBitSet(pTrain->ObjectCaps(), FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev))
1763 {
1764 if (TrainAttach(pTrain))// XDM3035: HACKHACKHACK!!!!!!!!
1765 EMIT_SOUND(edict(), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM);
1766
1767 return;// we're done here
1768 }
1769 }
1770 }
1771 }
1772 }
1773
1774 int caps = 0;
1775 float flMaxDot = VIEW_FIELD_NARROW;
1776 float flDot;
1777 CBaseEntity *pObject = nullptr;
1778 CBaseEntity *pClosest = nullptr;
1779 Vector vecLOS;
1780 Vector vecSrc(GetGunPosition());
1781 UTIL_MakeVectors(pev->v_angle);// so we know which way we are facing
1782 // Don't filter out invisible entities!
1783 while ((pObject = UTIL_FindEntityInSphere(pObject, pev->origin, PLAYER_USE_SEARCH_RADIUS)) != nullptr)
1784 {
1785 if (pObject == this)// XDM3035
1786 continue;
1787 //if (pObject->IsProjectile())// XDM3037: NO! Satchels and mines are usable!
1788 // continue;
1789 if (pObject->IsPlayerItem() && FBitSet(pObject->pev->effects, EF_NODRAW))// XDM3037
1790 continue;
1791
1792 caps = pObject->ObjectCaps();
1793 if (FBitSet(caps, FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE))
1794 {
1795 // XDM3037: TODO: move this to a separate PlayerUseCheck() function
1796 // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that
1797 // this object is actually usable? This dot is being done for every object within PLAYER_USE_SEARCH_RADIUS
1798 // when player hits the use key. How many objects can be in that area, anyway? (sjb)
1799 vecLOS = (pObject->Center() - vecSrc);// old: VecBModelOrigin
1800
1801 // This essentially moves the origin of the target to the corner nearest the player to test to see
1802 // if it's "hull" is in the view cone
1803 vecLOS = UTIL_ClampVectorToBox(vecLOS, pObject->pev->size * 0.5f);
1804 // can't see what is this vector anyway UTIL_DebugBeam(pObject->Center(), pObject->Center() + vecLOS, 5.0);
1805
1806 // XDM3037: traceline here to prevent USEing buttons through walls
1807 // XDM3038c: update: some mappers like to use non-solid buttons overlapped by solid entities in which case the trace always fails
1808 if (FBitSet(caps, FCAP_ONLYDIRECT_USE) && (pObject->pev->solid != SOLID_NOT && pObject->pev->solid != SOLID_TRIGGER))
1809 {
1810 TraceResult tr;
1811 UTIL_TraceLine(vecSrc, pObject->Center(), dont_ignore_monsters, dont_ignore_glass, edict(), &tr);// UNDONE: trace to object's closest plane, not center // XDM3038a: new: dont_ignore_monsters TESTME!
1812 //UTIL_DebugBeam(vecSrc, pObject->Center(), 4.0, 0,255,255);
1813 //UTIL_DebugBeam(vecSrc, tr.vecEndPos, 4.0, 255,0,0);
1814 if (tr.flFraction < 1.0f && tr.pHit != pObject->edict())
1815 continue;//CBaseEntity *pEnt = CBaseEntity::Instance(tr.pHit);UTIL_PrintEntInfo(pEnt); for debugging/breakpoints
1816 }
1817
1818 flDot = DotProduct(vecLOS, gpGlobals->v_forward);
1819 if (flDot > flMaxDot)// only if the item is in front of the user
1820 {
1821 pClosest = pObject;
1822 flMaxDot = flDot;
1823 }
1824 }
1825 }
1826 //pObject = pClosest;
1827 float value;
1828 if (FBitSet(m_afPhysicsFlags, PFLAG_USING) || pClosest && pClosest->GetState() == STATE_OFF)
1829 value = 1.0f;
1830 else
1831 value = 0.0f;
1832
1833 short iButtonState;
1834 if (FBitSet(m_afButtonPressed, IN_USE))
1835 iButtonState = 1;
1836 else if (FBitSet(pev->button, IN_USE))
1837 iButtonState = 2;
1838 else if (FBitSet(m_afButtonReleased, IN_USE))
1839 iButtonState = 3;
1840 else
1841 iButtonState = 0;
1842
1843 if (iButtonState > 0)// only when pressed, held or released
1844 PlayerUseObject(iButtonState, pClosest, value);// XDM3037
1845}
1846
1847//-----------------------------------------------------------------------------
1848// Purpose: XDM3037: may be called externally
1849// Input : iButtonState - 0-nothing; 1-pressed once; 2-held; 3-released
1850// *pObject - can be NULL!
1851// value - Use() value
1852//-----------------------------------------------------------------------------
1853void CBasePlayer::PlayerUseObject(const short iButtonState, CBaseEntity *pObject, const float value)
1854{
1855 if (pObject)
1856 {
1857 if (FBitSet(m_afButtonPressed, IN_USE))// only play sound if called on server
1858 EMIT_SOUND(edict(), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_IDLE);
1859
1860 int caps = pObject->ObjectCaps();
1861 if (iButtonState == 2 && FBitSet(caps, FCAP_CONTINUOUS_USE))
1862 {
1863 SetBits(m_afPhysicsFlags, PFLAG_USING);
1864 pObject->Use(this, this, USE_SET, value);
1865 }
1866 else if (iButtonState == 1 && FBitSet(caps, FCAP_IMPULSE_USE|FCAP_ONOFF_USE))
1867 pObject->Use(this, this, USE_SET, value);
1868 else if (iButtonState == 3 && FBitSet(caps, FCAP_ONOFF_USE))// This is an "off" use
1869 pObject->Use(this, this, USE_SET, value);
1870 }
1871 else
1872 {
1873 if (iButtonState == 1)
1874 EMIT_SOUND(edict(), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_IDLE);
1875 }
1876}
1877
1878//-----------------------------------------------------------------------------
1879// Purpose: Jump
1880//-----------------------------------------------------------------------------
1881void CBasePlayer::Jump(void)
1882{
1883 DBG_PLR_PRINT("CBasePlayer(%d)::Jump()\n", entindex());
1884 if (FBitSet(pev->flags, FL_WATERJUMP))
1885 return;
1886
1887 if (pev->waterlevel >= WATERLEVEL_WAIST)
1888 return;
1889
1890 // jump velocity is sqrt( height * gravity * 2)
1891 // If this isn't the first frame pressing the jump button, break out.
1892 if (!FBitSet(m_afButtonPressed, IN_JUMP))
1893 return; // don't pogo stick
1894
1895 if (!FBitSet(pev->flags, FL_ONGROUND))// XDM3038: FAIL on world! || FNullEnt(pev->groundentity))
1896 return;
1897
1898 if (UTIL_IsValidEntity(pev->groundentity))// XDM3037
1899 {
1900 // many features in this function use v_forward, so makevectors now.
1901//XDM3038:??? UTIL_MakeVectors(pev->angles);
1902 // ClearBits(pev->flags, FL_ONGROUND); // don't stairwalk
1903
1904 if (sv_jumpaccuracy.value <= 0.0f)// XDM
1905 PunchPitchAxis(4.0f);
1906
1907 if (m_fLongJump &&
1908 FBitSet(pev->button, IN_DUCK) &&
1909 (pev->flDuckTime > 0) &&
1910 pev->velocity.Length() > 50)
1911 {
1912 SetAnimation(PLAYER_SUPERJUMP);
1913 }
1914 else
1915 SetAnimation(PLAYER_JUMP);
1916
1917 // If you're standing on a conveyor, add it's velocity to yours (for momentum)
1918 if (FBitSet(pev->groundentity->v.flags, FL_CONVEYOR))// XDM3038: I forgot what was that
1919 pev->velocity += pev->basevelocity;
1920 }
1921}
1922
1923//-----------------------------------------------------------------------------
1924// Purpose: Called when ducking
1925//-----------------------------------------------------------------------------
1926void CBasePlayer::Duck(void)
1927{
1928 if (m_IdealActivity != ACT_LEAP)
1929 SetAnimation(PLAYER_WALK);
1930}
1931
1932//-----------------------------------------------------------------------------
1933// Purpose: XDM3037: separate function
1934// Check to see if player landed hard enough to make a sound or do damage.
1935// Falling farther than half of the maximum safe distance, but not as far a max safe distance will play a bootscrape sound, and no damage will be inflicted.
1936// Falling a distance shorter than half of maximum safe distance will make no sound.
1937// Falling farther than max safe distance will play a fallpain sound, and damage will be inflicted based on how far the player fell.
1938//-----------------------------------------------------------------------------
1939void CBasePlayer::Land(void)
1940{
1941 if (m_movementActivity == ACT_HOP || m_movementActivity == ACT_LEAP)
1942 m_fGaitAnimFinishTime = gpGlobals->time;
1943
1944 if (m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD)
1945 {
1946 // conprintf(1, "%f\n", m_flFallVelocity );
1947 if (pev->watertype == CONTENTS_WATER && pev->waterlevel >= WATERLEVEL_WAIST)// XDM3035a: player still get hit on shallow areas
1948 {
1949 // Did he hit the world or a non-moving entity?
1950 // BUG - this happens all the time in water, especially when water has current force
1951 // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 )
1952 // EMIT_SOUND(edict(), CHAN_BODY, "player/pl_wade1.wav", VOL_NORM, ATTN_NORM);
1953 // see PM_CheckFalling g_usPM_Fall
1954 m_fWaterCircleTime = gpGlobals->time;
1955 }
1956 else if (m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED)// after this point, we start doing damage
1957 {
1958 float flFallDamage = FallDamage(m_flFallVelocity);
1959 if (flFallDamage > 0.0f)
1960 {
1961 if (pev->groundentity)// XDM3035b: TESTME! ooops! We've landed on someone's head!
1962 {
1963 CBaseEntity *pLandedOn = CBaseEntity::Instance(pev->groundentity);
1964 if (pLandedOn && pLandedOn->pev->takedamage != DAMAGE_NO)
1965 {
1966 // XDM3038c: TESTME: experimental! if (pLandedOn->IsPlayer() || pLandedOn->IsMonster())
1967 pLandedOn->TakeDamage(/*this confuses DeathNotice()*/g_pWorld, this, flFallDamage, DMG_CRUSH/* | DMG_NEVERGIB*/);// we can do headshots without DMG_NOHITPOINT =)
1968 flFallDamage *= 0.5f;// XDM3038c: cushion!
1969 }
1970 }
1971 if (flFallDamage > pev->health)// XDM3035// NOTE: play on item/voice channel because we play footstep landing on body channel
1972 {
1973 if (m_fFrozen)
1974 EMIT_SOUND_DYN(edict(), CHAN_VOICE, gBreakSoundsGlass[RANDOM_LONG(0,NUM_BREAK_SOUNDS-1)], VOL_NORM, ATTN_NORM, 0, RANDOM_LONG(96, 104));
1975 else
1976 EMIT_SOUND_DYN(edict(), CHAN_VOICE, "common/bodysplat.wav", VOL_NORM, ATTN_NORM, 0, RANDOM_LONG(96, 104));
1977
1978 UTIL_DecalPoints(pev->origin, pev->origin-Vector(0.0f,0.0f,m_flFallVelocity), edict(), DECAL_BLOODSMEARR1);// the biggest spot // XDM3035a
1979 pev->punchangle.x = 0.0f;
1980 }
1981 else if (flFallDamage > pev->health/2)
1982 {
1983 UTIL_DecalPoints(pev->origin, pev->origin-Vector(0.0f,0.0f,m_flFallVelocity), edict(), DECAL_BLOODSMEARR2 + RANDOM_LONG(0,1));// XDM3035a
1984 }
1985 TakeDamage(g_pWorld, g_pWorld, flFallDamage, DMG_FALL | DMG_NEVERGIB | DMG_NOHITPOINT);// XDM: never gib! XDM3034 VARS(eoNullEntity)
1986 }
1987 }
1988 m_flFallVelocity = 0;
1989
1990 if (IsAlive())// XDM3037: don't interrupt possible death
1991 {
1992 SetAnimation(PLAYER_WALK);
1993 if (m_flFallVelocity >= PLAYER_MIN_BOUNCE_SPEED)// XDM && !IsMultiplayer())
1994 CSoundEnt::InsertSound(bits_SOUND_PLAYER, pev->origin, (int)m_flFallVelocity, 0.2);
1995 }
1996 }
1997}
1998
1999//-----------------------------------------------------------------------------
2000// Purpose: Override
2001// Output : float - final fall damage value
2002//-----------------------------------------------------------------------------
2003float CBasePlayer::FallDamage(const float &flFallVelocity)
2004{
2005 if (flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED)
2006 return g_pGameRules?g_pGameRules->GetPlayerFallDamage(this):(flFallVelocity*DAMAGE_FOR_FALL_SPEED);
2007
2008 return 0.0f;
2009}
2010
2011//-----------------------------------------------------------------------------
2012// Purpose: PlayerPreThink - called at the beginning of each frame
2013// Warning: Keep as clean as possible!!!
2014// Callers: PlayerPreThink()
2015//-----------------------------------------------------------------------------
2016void CBasePlayer::PreThink(void)
2017{
2018 //DBG_PRINT_ENT_THINK(PreThink);
2019//useless if (FBitSet(pev->button, IN_SCORE) || FBitSet(m_afButtonLast, IN_SCORE))// XDM3037a: disable accidental shots/respawn when player is interacting with the score panel
2020// pev->button &= ~BUTTONS_READY;//(IN_ATTACK|IN_ATTACK2)
2021
2022 int buttonsChanged;
2023 if (FBitSet(pev->flags, FL_FROZEN))// XDM3035b: disable everyting
2024 buttonsChanged = 0;
2025 else
2026 buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame
2027
2028 bool bWasAlive = IsAlive();
2029 // Debounced button codes for pressed/released
2030 // UNDONE: Do we need auto-repeat?
2031 m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed"
2032 m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released"
2033 //m_vecLastPosition = pev->origin;// for future use
2034
2035 /* DEBUG if (FBitSet(m_afButtonPressed, IN_ATTACK))
2036 UTIL_DebugAngles(pev->origin, pev->v_angle, 1.0, 64.0f);
2037 if (FBitSet(m_afButtonPressed, IN_ATTACK2))
2038 UTIL_DebugAngles(pev->origin, pev->angles, 1.0, 64.0f);*/
2039
2040 if (g_pGameRules)
2041 {
2042 g_pGameRules->PlayerThink(this);
2043 //DBG_PRINTF("CBasePlayer::PreThink() 1\n");
2044 if (g_pGameRules->IsGameOver())
2045 {
2046 UpdateStatusBar();// XDM3037: display winner name
2047 goto prethink_end;//?
2048 //return;// intermission or finale
2049 }
2050 }
2051
2052 if (IsObserver())// XDM3037: allow spectators to change mode?
2053 {
2054 if (HasWeapons())// XDM3038a: shoud've been delayed from ObserverStart
2055 {
2056 PackImportantItems();// XDM3038c
2057 RemoveAllItems(true, true);
2058 }
2059 ObserverPreThink();
2060 // XDM3038b pev->impulse = 0;
2061 goto prethink_end;// XDM3037a: do UpdateClientData() and InitHUD()!
2062 }
2063
2064 CheckEnvironment();// XDM3035b: this may cause death
2065
2066 UTIL_MakeVectors(pev->v_angle); // is this still used?
2067
2068 // BUGBUG access violations, priveleged instructions always appear here. WTF!?
2069
2070 if (IsAlive())// XDM3034: don't send useless data
2071 {
2072 CheckTimeBasedDamage();// this may cause death
2073 CheckSuitUpdate();
2074 }
2075
2076 if (!IsAlive())// XDM3037
2077 {
2078 if (!bWasAlive)
2079 PlayerDeathThink();// DON'T CALL THIS ON THE SAME FRAME AS PLAYER DIED!!!
2080
2081 goto prethink_end;//return;
2082 }
2083
2084 // IMPORTANT: Only updates that are made when the player is alive are allowed after this point!
2085 if (m_fFrozen)
2086 FrozenThink();
2087
2088 if (m_flBurnTime > 0 && m_flBurnTime <= gpGlobals->time)// XDM3038a
2089 ClearBits(m_bitsDamageType, DMGM_FIRE|DMG_IGNITE);
2090
2091// if (!IsObserver())// XDM: BUGBUG: this prevents crash when entering spectator mode without crowbar
2092#if defined (USE_EXCEPTIONS)
2093 try
2094 {
2095#endif
2096 ItemPreFrame();
2097#if defined (USE_EXCEPTIONS)
2098 }
2099 catch(...)
2100 {
2101 DBG_PRINTF("*** CBasePlayer(%d)::PreThink() ItemPreFrame() exception!\n", entindex());
2102 DBG_FORCEBREAK
2103 }
2104#endif
2105
2106 if (FBitSet(m_afPhysicsFlags, PFLAG_ONBARNACLE))
2107 {
2108 if (m_hEnemy.Get())
2109 {
2110 //SetBits(pev->effects, EF_INVLIGHT);
2111 pev->velocity.Clear();
2112 TrainDetach();// XDM3035b: don't stop the train!
2113 //ClearBits(m_afPhysicsFlags, PFLAG_ONTRAIN);
2114 m_flFallVelocity = 0.0f;
2115 }
2116 else
2117 {
2118 conprintf(2, "CBasePlayer::PreThink() error: PFLAG_ONBARNACLE with no m_hEnemy (barnacle)! Releasing.\n");
2119 BarnacleVictimReleased();
2120 }
2121 }
2122 else
2123 {
2124 // If trying to duck, already ducked, or in the process of ducking
2125 if (FBitSet(pev->button, IN_DUCK) || FBitSet(pev->flags, FL_DUCKING) || FBitSet(m_afPhysicsFlags, PFLAG_DUCKING) )
2126 Duck();
2127
2128 if (FBitSet(pev->button, IN_JUMP))
2129 Jump();
2130
2131 if (!FBitSet(pev->flags, FL_ONGROUND))
2132 m_flFallVelocity = -pev->velocity.z;
2133 }
2134
2135 if (m_iHUDDistortMode != 0)// XDM3038: someone has restored the power!
2136 {
2137 int iMaxArmor = (g_pGameRules?g_pGameRules->GetPlayerMaxArmor():MAX_NORMAL_BATTERY);// XDM3038
2138 if (pev->armorvalue >= iMaxArmor/10)
2139 {
2140 m_iHUDDistortUpdate = 1;
2141 m_iHUDDistortMode = 0;
2142 m_iHUDDistortValue = 0;
2143 }
2144 }
2145
2146 TrainPreFrame();
2147
2148 // Semi-secret weapon code here
2149 if (!m_fFrozen && !FBitSet(m_afPhysicsFlags, PFLAG_ONBARNACLE))// XDM3035
2150 {
2151 if (m_flThrowNDDTime > 0.0f && m_flThrowNDDTime <= gpGlobals->time)// XDM3035
2152 {
2153 int aiUranium = GetAmmoIndexFromRegistry("uranium");
2154 int aiSatchelCharge = GetAmmoIndexFromRegistry("Satchel Charge");
2155 int evflags = FEV_HOSTONLY|FEV_UPDATE;
2156 if (!GetInventoryItem(WEAPON_EGON) || AmmoInventory(aiUranium) < NUKE_AMMO_USE_URANIUM || AmmoInventory(aiSatchelCharge) < NUKE_AMMO_USE_SATCHEL)
2157 {
2158 PLAYBACK_EVENT_FULL(evflags, edict(), g_usNuclearDevice, 0.0f, pev->origin, pev->angles, 0.0f, 0.0f, 0, 0, 0, 0);
2159 }
2160 else
2161 {
2162 UTIL_MakeVectors(pev->v_angle);
2163 CBaseEntity *pNDD = Create("nucdevice", pev->origin + gpGlobals->v_forward * 32.0f, pev->angles, pev->velocity + gpGlobals->v_forward * 240.0f, edict(), SF_NORESPAWN);
2164 if (pNDD)
2165 {
2166 pNDD->pev->owner = edict();// XDM3037: hack! Must call SetIgnoreEnt()!
2167 m_rgAmmo[aiUranium] -= NUKE_AMMO_USE_URANIUM;
2168 m_rgAmmo[aiSatchelCharge] -= NUKE_AMMO_USE_SATCHEL;
2169 CBasePlayerItem *pEgon = GetInventoryItem(WEAPON_EGON);
2170 if (pEgon)
2171 {
2172 if (RemovePlayerItem(pEgon))// XDM3038a: if
2173 pEgon->Killed(this, this, GIB_NEVER);// XDM3038a
2174 }
2175 PLAYBACK_EVENT_FULL(evflags, edict(), g_usNuclearDevice, 0.0f, pev->origin, pev->angles, 0.0f, 0.0f, 1, 0, 0, 0);
2176 }
2177 }
2178 m_flThrowNDDTime = 0.0f;
2179 m_flNextAttack = UTIL_WeaponTimeBase() + 0.5f;
2180 if (m_pActiveItem)
2181 DeployActiveItem();
2182 }
2183 }
2184 // StudioFrameAdvance();//!!!HACKHACK!!! Can't be hit by traceline when not animating?
2185
2186 WaterMove();
2187
2188prethink_end:
2189 // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client
2190 UpdateClientData();
2191}
2192
2193//-----------------------------------------------------------------------------
2194// Purpose:
2195// Warning: DON'T return;!!
2196// Callers: PlayerPostThink()
2197//-----------------------------------------------------------------------------
2198void CBasePlayer::PostThink(void)
2199{
2200 //DBG_PRINT_ENT_THINK(PostThink);
2201 if (IsObserver())// XDM3035
2202 {
2203 // XDM3038b: TODO: ObserverImpulseCommands()?
2204 if (pev->impulse == 100)
2205 {
2206 if (FBitSet(pev->effects, EF_DIMLIGHT))
2207 ClearBits(pev->effects, EF_DIMLIGHT);
2208 else
2209 SetBits(pev->effects, EF_DIMLIGHT);
2210
2211 pev->impulse = 0;
2212 }
2213 goto postthink_end;
2214 }
2215
2216 if (!IsAlive())
2217 goto postthink_end;
2218
2219 if (g_pGameRules && g_pGameRules->IsGameOver())
2220 goto postthink_end;
2221
2222 // Handle Tank controlling
2223 if (m_pTank != nullptr)// if player moved too far from the gun, or selected a weapon, unuse the tank
2224 {
2225 if (m_pTank->OnControls(pev))// XDM3037 && !pev->weaponmodel)
2226 {
2227 m_pTank->Use(this, this, USE_SET, 2); // try fire the gun
2228 }
2229 else// moved off controls
2230 {
2231 m_pTank->Use(this, this, USE_OFF, 0);
2232 m_pTank = nullptr;
2233 }
2234 }
2235
2236 if (m_flIgnoreLadderStopTime != 0.0f && m_flIgnoreLadderStopTime < gpGlobals->time)
2237 {
2238 ENGINE_SETPHYSKV(edict(), PHYSKEY_IGNORELADDER, "0");
2239 m_flIgnoreLadderStopTime = 0.0f;
2240 }
2241
2242 if ((FBitSet(pev->flags, FL_ONGROUND) || IsOnLadder()) && (pev->health > 0) && m_flFallVelocity > 0)
2243 {
2244 Land();// may cause death!
2245
2246 if (!IsAlive())// XDM3037: don't interrupt possible death
2247 goto postthink_end;// XDM3037: otherwise we would need to check below
2248 }
2249
2250 // select the proper animation for the player character
2251 if (gpGlobals->frametime > 0.0)// XDM3037: don't spam while game is paused
2252 {
2253 //---- top animation: sequence ----
2254 if (pev->velocity.IsZero())
2255 SetAnimation(PLAYER_IDLE);
2256 else if (!FBitSet(pev->flags, FL_ONGROUND)/* && pev->waterlevel == 0 */&& m_flFallVelocity > 2.0f)// && (m_Activity != ACT_HOP && m_Activity != ACT_LEAP))
2257 SetAnimation(PLAYER_FALL);
2258 else
2259 SetAnimation(PLAYER_WALK);// swim animations will be used automatically
2260
2261 //---- bottom animation: gaitsequence ----
2262 SetGaitAnimation();// XDM3037
2263
2264 StudioFrameAdvance();
2265 //pev->modelindex = g_iModelIndexPlayer;// XDM: ? was CheckPowerups() // XDM3037: since we don't use StartDeathCam(), it's probably obsolete
2266 UpdatePlayerSound();
2267 }
2268
2269 // redundant if (!IsObserver())
2270 PlayerUse();// Handle +use commands
2271
2272 ImpulseCommands();// Handle inmpulse commands
2273 ItemPostFrame();// Do weapon stuff (can affect animation)
2274
2275postthink_end:
2276
2277#if defined(CLIENT_WEAPONS)
2278#error UNDONE: HACK: fix this code when implementing local weapons
2279 // Decay timers on weapons
2280 // go through all of the weapons and make a list of the ones to pack
2281 for (size_t i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
2282 {
2283 CBasePlayerItem *pInvItem = GetInventoryItem(i);
2284 if (pInvItem)
2285 {
2286 CBasePlayerWeapon *pWeapon = pInvItem->GetWeaponPtr();
2287 if (pWeapon && pWeapon->UseDecrement())
2288 {
2289 pWeapon->m_flNextPrimaryAttack = max(pWeapon->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0);
2290 pWeapon->m_flNextSecondaryAttack = max(pWeapon->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001);
2291
2292 if (pWeapon->m_flTimeWeaponIdle != 1000)
2293 pWeapon->m_flTimeWeaponIdle = max(pWeapon->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001);
2294
2295 if (pWeapon->pev->fuser1 != 1000)
2296 pWeapon->pev->fuser1 = max(pWeapon->pev->fuser1 - gpGlobals->frametime, -0.001);
2297
2298 // Only decrement if not flagged as NO_DECREMENT
2299 if (pWeapon->m_flPumpTime != 1000)
2300 pWeapon->m_flPumpTime = max(pWeapon->m_flPumpTime - gpGlobals->frametime, -0.001);
2301
2302 if (pWeapon->m_flNextAmmoBurn != 1000)// XDM: unhack
2303 pWeapon->m_flNextAmmoBurn = max(pWeapon->m_flNextAmmoBurn - gpGlobals->frametime, -0.001);
2304 }
2305 }
2306 }
2307
2308 m_flNextAttack -= gpGlobals->frametime;
2309 if (m_flNextAttack < -0.001)
2310 m_flNextAttack = -0.001;
2311#endif
2312
2313 // Track button info so we can detect 'pressed' and 'released' buttons next frame
2314 m_afButtonLast = pev->button;
2315
2316 if (IsOnLadder())// XDM3038a: we need this flag to track changes. Must be last in the frame!
2317 SetBits(m_afPhysicsFlags, PFLAG_ONLADDER);
2318 else
2319 ClearBits(m_afPhysicsFlags, PFLAG_ONLADDER);
2320}
2321
2322//-----------------------------------------------------------------------------
2323// Purpose: Update/think function after player is Killed() (from PreThink())
2324// Warning: DON'T CALL THIS ON THE SAME FRAME AS PLAYER DIED!!!
2325// Warning: It will remove weapons that are still working (stack execution) and nullify their m_pPlayer!
2326//-----------------------------------------------------------------------------
2327void CBasePlayer::PlayerDeathThink(void)
2328{
2329 DBG_PLR_PRINT("CBasePlayer(%d)::PlayerDeathThink()\n", entindex());
2330 if (pev->movetype == MOVETYPE_NOCLIP)// XDM3035 Disintegrate
2331 {
2332 if (HasWeapons())// XDM3038a: when disintegrating, don't drop anything
2333 {
2334 PackImportantItems();// XDM3038c
2335 RemoveAllItems(true, true);
2336 }
2337
2338 ClearBits(pev->flags, FL_ONGROUND);
2339 if (pev->renderamt > 1)
2340 {
2341 pev->renderamt -= 1;
2342 //pev->origin.z += 0.5f;// HACK
2343 if (g_pGameRules == nullptr || g_pGameRules->FAllowEffects())
2344 {
2345 if (RANDOM_LONG(0,5) == 0)
2346 {
2347 SetBits(pev->effects, EF_MUZZLEFLASH);
2348 UTIL_Sparks(pev->origin);
2349 }
2350 }
2351 }
2352 else
2353 pev->renderamt = 0;// add EF_NODRAW?
2354 }
2355 else// if (pev->movetype != MOVETYPE_NOCLIP)
2356 {
2357 // we drop the guns here because weapons that have an area effect and can kill their user
2358 // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the
2359 // player class sometimes is freed. It's safer to manipulate the weapons once we know
2360 // we aren't calling into any of their code anymore through the player pointer.
2361 if (HasWeapons())
2362 PackDeadPlayerItems();
2363
2364 if (FBitSet(pev->flags, FL_ONGROUND))
2365 {
2366 vec_t flForward = pev->velocity.Length() - 20.0f;
2367 if (flForward <= 0)
2368 pev->velocity.Clear();
2369 else
2370 pev->velocity.SetLength(flForward);// = flForward * pev->velocity.Normalize();
2371
2372 pev->avelocity.Clear();// XDM3038c
2373 }
2374 }
2375
2376 m_flNextSBarUpdateTime = gpGlobals->time;
2377 UpdateStatusBar();// XDM3037: display killer's name
2378
2379 if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING))// XDM3038a: TODO: check EF_NODRAW instead of modelindex?
2380 {
2381 StudioFrameAdvance();
2382 ++m_iRespawnFrames;// Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands
2383 if (m_iRespawnFrames < 120)// Animations should be no longer than this
2384 return;
2385 }
2386
2387 // once we're done animating our death and we're on the ground, we want to set movetype to None so our dead body won't do collisions and stuff anymore
2388 // this prevents a bug where the dead body would go to a player's head if he walked over it while the dead player was clicking their button to respawn
2389 //if (pev->movetype != MOVETYPE_NONE && FBitSet(pev->flags, FL_ONGROUND))
2390 // if (pev->movetype != MOVETYPE_NOCLIP && pev->waterlevel < 3)// XDM3035
2391 // pev->movetype = MOVETYPE_NONE;// XDM3037a: TODO: remove this, but there might be some MOVETYPE_NONE checks elsewhere!
2392 // XDM3038: keep MOVETYPE_TOSS and react to conveyors
2393
2394 // wait for all buttons released
2395 if (pev->deadflag == DEAD_DYING)// XDM3034
2396 {
2397 pev->deadflag = DEAD_DEAD;
2398 }
2399 else if (pev->deadflag == DEAD_DEAD)// XDM3034
2400 {
2401 ASSERT(m_fDiedTime != 0);// XDM3038c: moved to Killed() m_fDiedTime = gpGlobals->time;
2402 SetBits(pev->effects, EF_NOINTERP);
2403 pev->deadflag = DEAD_RESPAWNABLE;
2404 pev->button = 0;// XDM3037a: stop, have a break;
2405 StopAnimation();// pev->framerate = 0.0;
2406 UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX);// XDM3038c: HACK: become smaller, block less (but still do!)
2407 }
2408 else if (pev->deadflag == DEAD_RESPAWNABLE)
2409 {
2410 if (IsObserver())// HL20130901
2411 return;
2412
2413 bool bReadyButtonDown = !FBitSet(pev->button, IN_SCORE) && FBitSet(pev->button, BUTTONS_READY);// XDM3037a: never attempt anything while scoreboard is open // we could use m_bReadyPressed, but it's set in MP
2414 bool bForceRespawn = g_pGameRules && g_pGameRules->FForceRespawnPlayer(this);
2415 // wait for any button down, or mp_forcerespawn is set and the respawn time is up
2416 if (bReadyButtonDown || bForceRespawn)// XDM3034
2417 {
2418 if (g_pGameRules == nullptr || !g_pGameRules->IsMultiplayer())// restart the entire server
2419 {
2420 if ((gpGlobals->time - m_fDiedTime) > SINGLEPLAYER_RESTART_DELAY)// XDM3037a
2421 SERVER_COMMAND("reload\n");
2422 else
2423 pev->button = 0;// release all buttons
2424 }
2425 else if (g_pGameRules)
2426 {
2427 m_iRespawnFrames = 0;
2428 m_pLastItem = nullptr;// XDM3038: TESTME
2429 m_pNextItem = nullptr;// XDM3038
2430 pev->button = 0;
2431 pev->impulse = 0;// XDM3035a: clear all pending impulse commands (flashlight, flares, etc.)
2432 SetBits(pev->effects, EF_NODRAW|EF_NOINTERP);// XDM
2433 ClearBits(pev->flags, FL_FLOAT);// XDM3037a
2434 DontThink();// XDM3038c pev->nextthink = -1;
2435 if ((gpGlobals->time - m_fDiedTime) > g_pGameRules->GetPlayerRespawnDelay(this))
2436 {
2437 if (g_pGameRules->FPlayerCanRespawn(this))// CanRespawn is critical for LMS!
2438 Spawn(FALSE);// XDM3037: IMPORTANT: call with this explicit "not restoring" flag!
2439 else if (g_pGameRules->FPlayerStartAsObserver(this))// XDM3039: bReadyButtonDown && ?
2440 ObserverStart(pev->origin, pev->angles, m_hLastKiller.IsValid()?OBS_CHASE_FREE:OBS_ROAMING, m_hLastKiller);
2441 }
2442 }// NO
2443 }// CODE
2444 }// AFTER
2445 else// THESE
2446 conprintf(0, "PlayerDeathThink: BAD DEADFLAG!!!\n");
2447}// LINES!!!
2448
2449//-----------------------------------------------------------------------------
2450// Purpose: Apply long-lasting damage here
2451//-----------------------------------------------------------------------------
2452void CBasePlayer::CheckTimeBasedDamage(void)
2453{
2454 if (!FBitSet(m_bitsDamageType, DMGM_TIMEBASED))
2455 return;
2456 if (abs(gpGlobals->time - m_tbdPrev) < TIMEBASED_DAMAGE_INTERVAL)
2457 return;
2458
2459 size_t i;
2460 byte bDuration = 0;
2461 m_tbdPrev = gpGlobals->time;
2462
2463 for (i = 0; i < CDMG_TIMEBASED; ++i)
2464 {
2465 // Do not use DMG_types here to prevent positive feedback and infinite damage
2466 if (FBitSet(m_bitsDamageType, (DMG_PARALYZE << i)))
2467 {
2468 switch (i)
2469 {
2470 case itbd_Paralyze:
2471 // UNDONE - flag movement as half-speed
2472 bDuration = TD_PARALYZE_DURATION;
2473 break;
2474 case itbd_NerveGas:
2475 //TakeDamage(this, this, TD_NERVEGAS_DAMAGE, DMG_GENERIC);
2476 bDuration = TD_NERVEGAS_DURATION;
2477 break;
2478 case itbd_Poison:
2479 TakeDamage(this, g_pWorld, TD_POISON_DAMAGE, DMG_GENERIC);// XDM3035: world
2480 bDuration = TD_POISON_DURATION;
2481 break;
2482 case itbd_Radiation:
2483 //TakeDamage(this, this, TD_RADIATION_DAMAGE, DMG_GENERIC);
2484 bDuration = TD_RADIATION_DURATION;
2485 break;
2486 case itbd_DrownRecover:
2487 // NOTE: this hack is actually used to RESTORE health after the player has been drowning and finally takes a breath
2488 if (m_idrowndmg > m_idrownrestored)
2489 {
2490 int idif = min(m_idrowndmg - m_idrownrestored, 10);
2491 TakeHealth(idif, DMG_GENERIC);
2492 m_idrownrestored += idif;
2493 }
2494 bDuration = 4;// get up to 5*10 = 50 points back
2495 break;
2496 case itbd_Acid:
2497 //TakeDamage(pev, pev, TD_ACID_DAMAGE, DMG_GENERIC);
2498 bDuration = TD_ACID_DURATION;
2499 break;
2500 case itbd_SlowBurn:
2501 //TakeDamage(pev, pev, TD_SLOWBURN_DAMAGE, DMG_GENERIC);
2502 bDuration = TD_SLOWBURN_DURATION;
2503 break;
2504 case itbd_SlowFreeze:
2505 //TakeDamage(pev, pev, TD_SLOWFREEZE_DAMAGE, DMG_GENERIC);
2506 bDuration = TD_SLOWFREEZE_DURATION;
2507 break;
2508 default: bDuration = 0; break;
2509 }
2510
2511 if (m_rgbTimeBasedDamage[i])
2512 {
2513 if (m_rgItems[ITEM_ANTIDOTE])// use up an antitoxin on poison or nervegas after a few seconds of damage
2514 {
2515 if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < TD_NERVEGAS_DURATION)) ||
2516 ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < TD_POISON_DURATION)))
2517 {
2518 m_rgbTimeBasedDamage[i] = 0;
2519 m_rgItems[ITEM_ANTIDOTE]--;
2520 SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK);
2521 }
2522 }
2523 // decrement damage duration, detect when done.
2524 if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0)
2525 {
2526 m_rgbTimeBasedDamage[i] = 0;
2527 ClearBits(m_bitsDamageType, DMG_PARALYZE << i);// if we're done, clear damage bits
2528 }
2529 }
2530 else// first time taking this damage type - init damage duration
2531 m_rgbTimeBasedDamage[i] = bDuration;
2532 }
2533 }
2534}
2535
2536//-----------------------------------------------------------------------------
2537// Purpose: Update Geiger counter (regardless of damage)
2538//-----------------------------------------------------------------------------
2539void CBasePlayer::UpdateGeigerCounter(void)
2540{
2541 //should be checked if (!HasSuit())// XDM3038: don't flood the net
2542 // return;
2543
2544 // delay per update ie: don't flood net with these msgs
2545 if (gpGlobals->time > m_flgeigerDelay)
2546 {
2547 m_flgeigerDelay = gpGlobals->time + GEIGER_DELAY;
2548
2549 // send range to radition source to client only when it changes
2550 if (m_iGeigerRange != m_iGeigerRangePrev)
2551 {
2552 if (m_iGeigerRange <= GEIGER_MAXDIST || m_iGeigerRangePrev <= GEIGER_MAXDIST)// don't send range longer than we care about
2553 {
2554 MESSAGE_BEGIN((m_iGeigerRange > GEIGER_MAXDIST || m_iGeigerRangePrev > GEIGER_MAXDIST)?MSG_ONE:MSG_ONE_UNRELIABLE, gmsgGeigerRange, nullptr, edict());// only send reliably start/end messages
2555 WRITE_BYTE(m_iGeigerRange >> 2);// /= 4
2556 MESSAGE_END();
2557 }
2558 m_iGeigerRangePrev = m_iGeigerRange;
2559 }
2560 else
2561 m_iGeigerRange += GEIGER_MAXDIST/4;// try increasing known radius because triggers only can decrease it
2562 }
2563}
2564
2565//-----------------------------------------------------------------------------
2566// Purpose: Play queued suit messages
2567//-----------------------------------------------------------------------------
2568void CBasePlayer::CheckSuitUpdate(void)
2569{
2570 if (!HasSuit())
2571 return;
2572
2573 // if in range of radiation source, ping geiger counter
2574 UpdateGeigerCounter();
2575
2576 if (IsMultiplayer())
2577 return;// don't bother updating HEV voice in multiplayer.
2578
2579 if (gpGlobals->time >= m_flSuitUpdate && m_flSuitUpdate > 0)
2580 {
2581 int i;
2582 int isentence = 0;
2583 int isearch = m_iSuitPlayNext;
2584 ASSERT(isearch >= 0 && isearch < CSUITPLAYLIST);
2585 if (isearch >= CSUITPLAYLIST)
2586 {
2587 conprintf(2, "CBasePlayer::CheckSuitUpdate() bad index: m_iSuitPlayNext = %d!\n", m_iSuitPlayNext);
2588 return;
2589 }
2590
2591 // play a sentence off of the end of the queue
2592 for (i = 0; i < CSUITPLAYLIST; ++i)
2593 {
2594 isentence = m_rgSuitPlayList[isearch];
2595 if (isentence)
2596 break;
2597
2598 if (++isearch == CSUITPLAYLIST)
2599 isearch = 0;
2600 }
2601 if (isentence)
2602 {
2603 m_rgSuitPlayList[isearch] = 0;
2604 if (isentence > 0)// play sentence number
2605 {
2606 char sentence[CBSENTENCENAME_MAX+1];
2607 strcpy(sentence, "!");
2608 strcat(sentence, gszallsentencenames[isentence]);
2609 EMIT_SOUND_SUIT(edict(), sentence);
2610 }
2611 else
2612 EMIT_GROUPID_SUIT(edict(), -isentence);// play sentence group
2613
2614 m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME;
2615 }
2616 else// queue is empty, don't check
2617 m_flSuitUpdate = 0;
2618 }
2619}
2620
2621//-----------------------------------------------------------------------------
2622// Purpose: add sentence to suit playlist queue. if fgroup is true, then name is a sentence group (HEV_AA), otherwise name is a specific sentence name ie: !HEV_AA0.
2623// If iNoRepeat is specified in seconds, then we won't repeat playback of this word or sentence for at least that number of seconds.
2624// Input : *name -
2625// fgroup -
2626// iNoRepeatTime -
2627//-----------------------------------------------------------------------------
2628void CBasePlayer::SetSuitUpdate(const char *name, int fgroup, int iNoRepeatTime)
2629{
2630 if (!HasSuit())
2631 return;
2632
2633 if (IsMultiplayer())
2634 return;// due to static channel design, etc. We don't play HEV sounds in multiplayer right now.
2635
2636 int i;
2637 if (name == nullptr)// clear out the queue
2638 {
2639 for (i = 0; i < CSUITPLAYLIST; ++i)
2640 m_rgSuitPlayList[i] = 0;
2641 return;
2642 }
2643
2644 int isentence;
2645 if (fgroup)// get sentence or group number
2646 isentence = -SENTENCEG_GetIndex(name);// mark group number as negative
2647 else
2648 {
2649 isentence = SENTENCEG_Lookup(name, nullptr);
2650 if (isentence < 0)
2651 return;
2652 }
2653
2654 int iempty = -1;
2655 // check norepeat list - this list lets us cancel the playback of words or sentences that have already been played within a certain time.
2656 for (i = 0; i < CSUITNOREPEAT; ++i)
2657 {
2658 if (isentence == m_rgiSuitNoRepeat[i])
2659 {
2660 // this sentence or group is already in the norepeat list
2661 if (m_rgflSuitNoRepeatTime[i] < gpGlobals->time)
2662 {
2663 // norepeat time has expired, clear it out
2664 m_rgiSuitNoRepeat[i] = 0;
2665 m_rgflSuitNoRepeatTime[i] = 0.0;
2666 iempty = i;
2667 break;
2668 }
2669 else
2670 {
2671 // don't play, still marked as norepeat
2672 return;
2673 }
2674 }
2675 // keep track of empty slot
2676 if (!m_rgiSuitNoRepeat[i])
2677 iempty = i;
2678 }
2679
2680 // sentence is not in norepeat list, save if norepeat time was given
2681 if (iNoRepeatTime)
2682 {
2683 if (iempty < 0)
2684 iempty = RANDOM_LONG(0, CSUITNOREPEAT-1);// pick random slot to take over
2685
2686 m_rgiSuitNoRepeat[iempty] = isentence;
2687 m_rgflSuitNoRepeatTime[iempty] = gpGlobals->time + iNoRepeatTime;
2688 }
2689
2690 // find empty spot in queue, or overwrite last spot
2691 m_rgSuitPlayList[m_iSuitPlayNext++] = isentence;
2692 if (m_iSuitPlayNext >= CSUITPLAYLIST)
2693 m_iSuitPlayNext = 0;
2694
2695 if (m_flSuitUpdate <= gpGlobals->time)
2696 {
2697 if (m_flSuitUpdate == 0)// play queue is empty, start sooner
2698 m_flSuitUpdate = gpGlobals->time + 0.1;
2699 else
2700 m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME;
2701 }
2702}
2703
2704//-----------------------------------------------------------------------------
2705// Purpose: Small ID text shown when aiming at someone
2706//-----------------------------------------------------------------------------
2707void CBasePlayer::InitStatusBar(void)
2708{
2709 m_StatusBarString[0] = '\0';
2710 for (size_t i = 0; i < SBAR_NUMVALUES; ++i)
2711 m_iStatusBarValues[i] = 0;
2712
2713 m_flStatusBarDisappearDelay = 0;
2714 m_flNextSBarUpdateTime = gpGlobals->time + 0.2;
2715}
2716
2717//-----------------------------------------------------------------------------
2718// Purpose: StatusBar - small text label shown when player is facing someone
2719// Warning: Protocol is XDM-specific
2720//-----------------------------------------------------------------------------
2721void CBasePlayer::UpdateStatusBar(void)
2722{
2723 if (m_flNextSBarUpdateTime > gpGlobals->time)
2724 return;
2725
2726 if (IsBot())
2727 return;
2728
2729 size_t i;
2730 short newSBarValues[SBAR_NUMVALUES] = {0,0,0,0};
2731 char newSBarString[SBAR_STRING_SIZE];
2732 // restore old values to compare to
2733 strcpy(newSBarString, m_StatusBarString);// <-
2734
2735 // Select status bar target
2736 CBaseEntity *pEntity = nullptr;
2737 if (g_pGameRules && g_pGameRules->IsGameOver() && g_pGameRules->GetIntermissionActor1())// XDM3037: display winner name during intermission
2738 {
2739 pEntity = g_pGameRules->GetIntermissionActor1();// same m_hObserverTarget
2740 m_flStatusBarDisappearDelay = g_pGameRules->GetIntermissionEndTime();
2741 }
2742 else if (IsAlive())
2743 {
2744 if (IsMultiplayer() && m_hAutoaimTarget.Get())// XDM3037: display autoaim ID string
2745 {
2746 pEntity = m_hAutoaimTarget;
2747 m_flStatusBarDisappearDelay = gpGlobals->time + 1.0;
2748 }
2749 else// Find an ID Target infront of me
2750 {
2751 TraceResult tr;
2752 UTIL_MakeVectors(pev->v_angle + pev->punchangle);
2753 Vector vecSrc(EyePosition());
2754 Vector vecEnd(vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE));
2755 SetBits(gpGlobals->trace_flags, FTRACE_SIMPLEBOX);// XDM3038b: improves performance
2756 UTIL_TraceLine(vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr);
2757 ClearBits(gpGlobals->trace_flags, FTRACE_SIMPLEBOX);
2758 if (tr.flFraction < 1.0 && !FNullEnt(tr.pHit))// FNullEnt invalidates the world too!
2759 {
2760 pEntity = CBaseEntity::Instance(tr.pHit);
2761 m_flStatusBarDisappearDelay = gpGlobals->time + 1.0;
2762 }
2763 }
2764 }
2765 else// XDM3037: if player is dead, display killer's name
2766 pEntity = m_hLastKiller;// EHANDLE may get invalidated while getting value, which is good
2767
2768 // Fill the data to send
2769 int numlines = 0;
2770 bool bSendString = false;
2771 bool bSendData = false;
2772 if (pEntity)
2773 {
2774 if (pEntity->IsPlayer() || (pEntity->IsMonster() && !pEntity->IsProjectile() && pEntity->IsAlive()))// XDM3039: only if monster is alive
2775 {
2776 newSBarValues[SBAR_ID_TARGETINDEX] = ENTINDEX(pEntity->edict());
2777 // allies and medics get to see the targets health
2778 int rs = g_pGameRules?g_pGameRules->PlayerRelationship(this, pEntity):R_NO;
2779 if (rs == GR_TEAMMATE || rs == GR_ALLY)
2780 {
2781 strcpy(newSBarString, "#SB_AL\0");// %c0%i0: (ALLY) %n0\n%c0 Health: %d1\n%c0 Armor: %d2
2782 newSBarValues[SBAR_ID_TARGETHEALTH] = (short)pEntity->pev->health;// XDM
2783 newSBarValues[SBAR_ID_TARGETARMOR] = (short)pEntity->pev->armorvalue;
2784 }
2785 else if (/*g_pGameRules && g_pGameRules->IsCoOp() && */pEntity->IsMonster())// XDM3038b: uncomment to use special status for monsters
2786 {
2787 strcpy(newSBarString, "#SB_MO\0");// %i0: %n0\n%c0 Health: %d1\n%c0 Armor: %d2
2788 newSBarValues[SBAR_ID_TARGETHEALTH] = (short)pEntity->pev->health;// XDM
2789 newSBarValues[SBAR_ID_TARGETARMOR] = (short)pEntity->pev->armorvalue;
2790 }
2791 else
2792 {
2793 strcpy(newSBarString, "#SB_EN\0");// %c0%i0: %n0
2794 newSBarValues[SBAR_ID_TARGETHEALTH] = 0;
2795 newSBarValues[SBAR_ID_TARGETARMOR] = 0;
2796 }
2797 numlines = 1;
2798 //m_flStatusBarDisappearDelay = gpGlobals->time + 1.0f;
2799 // check for changes
2800 if (strcmp(newSBarString, m_StatusBarString) != 0)
2801 bSendString = true;
2802 // check for changes
2803 for (i = 0; i < SBAR_NUMVALUES; ++i)// XDM3038a
2804 {
2805 if (newSBarValues[i] != m_iStatusBarValues[i])
2806 {
2807 bSendData = true;
2808 break;// one is enough
2809 }
2810 }
2811 }
2812 }
2813 else// no entity in sight
2814 {
2815 if (m_flStatusBarDisappearDelay > gpGlobals->time)
2816 {
2817 // hold the values for a short amount of time after viewing the object
2818 //for (i = 0; i < SBAR_NUMVALUES; ++i)
2819 // newSBarValues[i] = m_iStatusBarValues[i];
2820 return;// XDM3038a
2821 }
2822 else// timeout reached
2823 {
2824 numlines = SBAR_MSG_CLEAR;// clear
2825 m_StatusBarString[0] = '\0';// clear so this entity may be redetected next time
2826 bSendString = true;
2827 }
2828 }
2829
2830 if (bSendString || bSendData)
2831 {
2832 MESSAGE_BEGIN(MSG_ONE, gmsgStatusData, nullptr, edict());
2833 WRITE_BYTE(numlines);// num lines (255 means clear all)
2834 if (numlines > 0 && numlines < 255)// there is actual string to send
2835 {
2836 WRITE_STRING(newSBarString);// string
2837 strcpy(m_StatusBarString, newSBarString);
2838 }
2839 //conprintf(1, "UpdateStatusBar(%d): +string \"%s\"\n", entindex(), sbuf0);
2840 if (bSendData)
2841 {
2842 for (i = 0; i < SBAR_NUMVALUES; ++i)// XDM3038a
2843 {
2844 if (newSBarValues[i] != m_iStatusBarValues[i])
2845 {
2846 WRITE_BYTE(i);
2847 WRITE_SHORT(newSBarValues[i]);
2848 m_iStatusBarValues[i] = newSBarValues[i];
2849 //conprintf(1, "UpdateStatusBar(%d): +value %d %d\n", entindex(), i, newSBarValues[i]);
2850 }
2851 }
2852 }
2853 MESSAGE_END();
2854 }
2855 m_flNextSBarUpdateTime = gpGlobals->time + 0.25;
2856}
2857
2858//-----------------------------------------------------------------------------
2859// Purpose: Updates virtual AI sounds this player produces
2860// NOTE: We don't blindly summing up all sounds, but rather choose the loudest one
2861//-----------------------------------------------------------------------------
2862void CBasePlayer::UpdatePlayerSound(void)
2863{
2864 CSound *pSound = CSoundEnt::SoundPointerForIndex(CSoundEnt::ClientSoundIndex(edict()));// XDM: may return null if monsters are not allowed
2865 if (pSound)
2866 {
2867 pSound->m_iType = bits_SOUND_NONE;
2868 if (m_fNoPlayerSound)// debugging only
2869 {
2870 pSound->m_iVolume = 0;
2871 return;
2872 }
2873 }
2874 // Calculate volume of sounds produced by player body
2875 int iBodyVolume = 0;
2876 if (FBitSet(pev->flags, FL_ONGROUND))
2877 {
2878 iBodyVolume = pev->velocity.Length();
2879 if (iBodyVolume > HUMAN_MAX_BODY_VOLUME)// clamp the noise that can be made by the body, in case a push trigger, weapon recoil, or anything shoves the player abnormally fast.
2880 iBodyVolume = HUMAN_MAX_BODY_VOLUME;
2881 }
2882 else
2883 iBodyVolume = 0;
2884
2885 if (FBitSet(pev->button, IN_JUMP))
2886 iBodyVolume += HUMAN_JUMP_VOLUME;
2887
2888 if (iBodyVolume > m_iTargetVolume)// Test and apply
2889 m_iTargetVolume = iBodyVolume;
2890
2891 // Calculate volume of sounds produced by player weapons
2892 if (m_iWeaponVolume > 0)
2893 {
2894 if (m_iWeaponVolume > m_iTargetVolume)// Test and apply
2895 {
2896 m_iTargetVolume = m_iWeaponVolume;
2897 if (pSound)
2898 SetBits(pSound->m_iType, bits_SOUND_COMBAT);// weapon is louder than the player, add combat flag
2899 }
2900 m_iWeaponVolume = (int)((float)m_iWeaponVolume - 250.0f * gpGlobals->frametime);// decrease weapon volume over time so bits_SOUND_COMBAT stays set for a while
2901 }
2902 if (m_iWeaponVolume < 0)
2903 m_iWeaponVolume = 0;
2904
2905 // When target is less than the current volume, current volume works itself towards target volume over time.
2906 // This gives monsters a much better chance to hear a sound, especially if they don't listen every frame.
2907 int iVolume = 0;
2908 if (pSound)
2909 iVolume = pSound->m_iVolume;
2910
2911 if (m_iTargetVolume > iVolume)
2912 iVolume = m_iTargetVolume;
2913 else if (iVolume > m_iTargetVolume)
2914 {
2915 iVolume = (int)((float)iVolume - 250.0f * gpGlobals->frametime);
2916 if (iVolume < m_iTargetVolume)
2917 iVolume = m_iTargetVolume;// XDM3039: HLFIX
2918 }
2919
2920 if (gpGlobals->time > m_flStopExtraSoundTime)// time to "stop" extra sounds
2921 m_iExtraSoundTypes = 0;
2922
2923 if (pSound)
2924 {
2925 pSound->m_vecOrigin = pev->origin;
2926 SetBits(pSound->m_iType, bits_SOUND_PLAYER | m_iExtraSoundTypes);
2927 pSound->m_iVolume = iVolume;
2928 }
2929
2930 // keep track of virtual muzzle flash
2931 if (m_iWeaponFlash > 0)
2932 m_iWeaponFlash = (int)((float)m_iWeaponFlash - 256.0f * gpGlobals->frametime);// XDM3038c: changed order of type casting
2933 if (m_iWeaponFlash < 0)
2934 m_iWeaponFlash = 0;
2935
2936 // Below are a couple of useful little bits that make it easier to determine just how much noise the player is making.
2937 //UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * iVolume, g_vecZero, 255, 25 );
2938 //conprintf(1, "%d/%d\n", iVolume, m_iTargetVolume );
2939}
2940
2941//-----------------------------------------------------------------------------
2942// Purpose: "impulse %d" command handler
2943//-----------------------------------------------------------------------------
2944void CBasePlayer::ImpulseCommands(void)
2945{
2946 if (pev->impulse == 204)// Demo recording, update client dll specific data again.
2947 {
2948 ForceClientDllUpdate();
2949 return;
2950 }
2951
2952 switch (pev->impulse)
2953 {
2954 case 100:
2955 {
2956 if (!m_fFrozen)
2957 {
2958 if (FlashlightIsOn())
2959 FlashlightTurnOff();
2960 else
2961 FlashlightTurnOn();
2962 }
2963 }
2964 break;
2965 case 120:
2966 {
2967 if (!m_fFrozen && m_rgItems[ITEM_FLARE] > 0)// XDM3035
2968 {
2969 UTIL_MakeVectors(pev->v_angle);
2970 Vector vecSpot(gpGlobals->v_forward); vecSpot *= 32.0f; vecSpot += pev->origin;//pev->origin + gpGlobals->v_forward * 32.0f;
2971 if (UTIL_PointContents(vecSpot) != CONTENTS_SOLID)
2972 {
2973 CBaseEntity *pFlare = Create("flare", vecSpot, g_vecZero, pev->velocity + gpGlobals->v_forward * 300.0f, edict(), SF_NORESPAWN);
2974 if (pFlare && !FBitSet(pFlare->pev->flags, FL_KILLME))
2975 {
2976 m_rgItems[ITEM_FLARE]--;
2977 if (m_rgItems[ITEM_FLARE] > 0)
2978 ClientPrint(pev, HUD_PRINTTALK, UTIL_VarArgs("#ITEMSLEFT%d %d\n", ITEM_FLARE, m_rgItems[ITEM_FLARE]));
2979 else
2980 ClientPrint(pev, HUD_PRINTTALK, UTIL_VarArgs("#ITEMSLEFTNONE%d", ITEM_FLARE));
2981 }
2982 }
2983 }
2984 }
2985 break;
2986 case 121:// ThrowNuclearDevice
2987 {
2988 if (!m_fFrozen && m_flNextAttack <= UTIL_WeaponTimeBase())
2989 {
2990 if (m_pActiveItem)
2991 {
2992 if (!m_pActiveItem->CanHolster())// XDM
2993 return;
2994
2995 m_pActiveItem->Holster();
2996 }
2997 EMIT_SOUND_DYN(edict(), CHAN_ITEM, DEFAULT_PICKUP_SOUND_CONTAINER, VOL_NORM, ATTN_STATIC, 0, PITCH_NORM);
2998 m_flThrowNDDTime = gpGlobals->time + 2.0f;
2999 m_flNextAttack = UTIL_WeaponTimeBase() + 2.5f;
3000 }
3001 }
3002 break;
3003 case 201:// paint decal
3004 {
3005 if (!m_fFrozen && m_flNextDecalTime <= gpGlobals->time)
3006 {
3007 TraceResult tr;
3008 UTIL_MakeVectors(pev->v_angle);
3009 UTIL_TraceLine(GetGunPosition(), GetGunPosition() + gpGlobals->v_forward * 128.0f, ignore_monsters, edict(), &tr);
3010 if (tr.flFraction != 1.0)// line hit something, so paint a decal
3011 {
3012 int nFrames = GetCustomDecalFrames();
3013 if (nFrames == -1)// No customization present.
3014 UTIL_DecalTrace(&tr, DECAL_XHL);
3015 else
3016 UTIL_PlayerDecalTrace(&tr, entindex(), nFrames, true);
3017
3018 EMIT_AMBIENT_SOUND(edict(), tr.vecEndPos, "player/sprayer.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM+RANDOM_LONG(-4,4));// XDM3038
3019 m_flNextDecalTime = gpGlobals->time + sv_decalfrequency.value;
3020 m_Stats[STAT_DECALS_COUNT]++;// XDM3037
3021 }
3022 }
3023 }
3024 break;
3025 default: CheatImpulseCommands(pev->impulse); break;
3026 }
3027 pev->impulse = 0;
3028}
3029
3030//-----------------------------------------------------------------------------
3031// Purpose: ImpulseCommands considered cheats
3032// Input : iImpulse -
3033//-----------------------------------------------------------------------------
3034void CBasePlayer::CheatImpulseCommands(const int &iImpulse)
3035{
3036 if (g_psv_cheats->value <= 0.0f)
3037 return;
3038
3039 switch (iImpulse)
3040 {
3041 case 101: ClientPrint(pev, HUD_PRINTCONSOLE, "This impulse is obsolete, use \"giveall\" instead.\n"); break;
3042 case 102: CGib::SpawnRandomGibs(this, 4, true, gpGlobals->v_forward*40.0f); break;
3043 case 103:// was ReportAIState
3044 case 106:
3045 {
3046 CBaseEntity *pEntity = UTIL_FindEntityForward(this);
3047 if (pEntity)
3048 pEntity->ReportState(1);
3049 }
3050 break;
3051 case 104: gGlobalState.DumpGlobals(); break;// dump global state varaibles and global entity names
3052 case 105:
3053 {
3054 if (m_fNoPlayerSound)
3055 {
3056 m_fNoPlayerSound = false;
3057 ClientPrint(pev, HUD_PRINTCONSOLE, "You are audible for AI\n");
3058 }
3059 else
3060 {
3061 m_fNoPlayerSound = true;
3062 ClientPrint(pev, HUD_PRINTCONSOLE, "You are silent for AI\n");
3063 }
3064 }
3065 break;
3066 case 107:
3067 {
3068 Vector start(GetGunPosition());
3069 Vector end(gpGlobals->v_forward); end *= 1024.0f; end += start;
3070 TraceResult tr;
3071 UTIL_TraceLine(start, end, ignore_monsters, edict(), &tr);
3072 if (tr.pHit)
3073 {
3074 const char *pTextureName = TRACE_TEXTURE(tr.pHit, start, end);
3075 if (pTextureName)
3076 ClientPrintF(pev, HUD_PRINTCONSOLE, 0, "Texture: %s, type: %c\n", pTextureName, PM_FindTextureTypeFull(pTextureName));
3077 }
3078 }
3079 break;
3080 case 108:
3081 {
3082 ClientPrintF(pev, HUD_PRINTCONSOLE, 0, "Showing pev->angles (%g, %g, %g)\n", pev->angles.x, pev->angles.y, pev->angles.z);
3083 UTIL_DebugAngles(pev->origin, pev->angles, 3.0, 24);
3084 }
3085 break;
3086 case 109:
3087 {
3088 ClientPrintF(pev, HUD_PRINTCONSOLE, 0, "Showing pev->v_angle (%g, %g, %g)\n", pev->v_angle.x, pev->v_angle.y, pev->v_angle.z);
3089 UTIL_DebugAngles(pev->origin, pev->v_angle, 3.0, 24);
3090 }
3091 break;
3092 case 195:
3093 {
3094 conprintf(0, "Showing shortest paths for entire level to nearest node: node_viewer_fly\n");
3095 Create("node_viewer_fly", pev->origin, pev->angles);
3096 }
3097 break;
3098 case 196:
3099 {
3100 conprintf(0, "Showing shortest paths for entire level to nearest node: node_viewer_large\n");
3101 Create("node_viewer_large", pev->origin, pev->angles);
3102 }
3103 break;
3104 case 197:
3105 {
3106 conprintf(0, "Showing shortest paths for entire level to nearest node: node_viewer_human\n");
3107 Create("node_viewer_human", pev->origin, pev->angles);
3108 }
3109 break;
3110 case 199:// show nearest node and all connections
3111 {
3112 int node = WorldGraph.FindNearestNode(pev->origin, bits_NODE_GROUP_REALM);
3113 conprintf(0, "Showing nearest node (%d) and all connections\n", node);
3114 WorldGraph.ShowNodeConnections(node);
3115 }
3116 break;
3117 case 202:// Random blood splatter
3118 {
3119 TraceResult tr;
3120 UTIL_MakeVectors(pev->v_angle);
3121 UTIL_TraceLine(GetGunPosition(), GetGunPosition() + gpGlobals->v_forward * 128.0f, ignore_monsters, edict(), &tr);
3122 if (tr.flFraction < 1.0f)
3123 UTIL_BloodDecalTrace(&tr, BLOOD_COLOR_RED);
3124 }
3125 break;
3126 }
3127}
3128
3129//-----------------------------------------------------------------------------
3130// Purpose:
3131// Output : Returns true/false
3132//-----------------------------------------------------------------------------
3133bool CBasePlayer::FlashlightIsOn(void)
3134{
3135 return FBitSet(pev->effects, EF_DIMLIGHT);
3136}
3137
3138//-----------------------------------------------------------------------------
3139// Purpose:
3140//-----------------------------------------------------------------------------
3141void CBasePlayer::FlashlightTurnOn(void)
3142{
3143 if (g_pGameRules && !g_pGameRules->FAllowFlashlight())
3144 return;
3145
3146 if (HasSuit())
3147 {
3148 EMIT_SOUND_DYN(edict(), CHAN_ITEM, "items/flashlight1.wav", VOL_NORM, ATTN_IDLE, 0, PITCH_NORM);
3149 SetBits(pev->effects, EF_DIMLIGHT);
3150 FlashlightUpdate(1);
3151 m_flFlashLightTime = gpGlobals->time + FLASHLIGHT_UPDATE_INTERVAL;
3152 }
3153}
3154
3155//-----------------------------------------------------------------------------
3156// Purpose:
3157//-----------------------------------------------------------------------------
3158void CBasePlayer::FlashlightTurnOff(void)
3159{
3160 if (IsAlive() && HasSuit())
3161 EMIT_SOUND_DYN(edict(), CHAN_ITEM, "items/flashlight1.wav", VOL_NORM, ATTN_IDLE, 0, PITCH_NORM);
3162
3163 ClearBits(pev->effects, EF_DIMLIGHT);
3164 FlashlightUpdate(0);
3165 m_flFlashLightTime = gpGlobals->time + FLASHLIGHT_UPDATE_INTERVAL;
3166}
3167
3168//-----------------------------------------------------------------------------
3169// Purpose: Send flashlight state update to client side
3170// Input : state - 1/0
3171//-----------------------------------------------------------------------------
3172void CBasePlayer::FlashlightUpdate(const int &state)
3173{
3174 MESSAGE_BEGIN(((sv_reliability.value > 0)?MSG_ONE:MSG_ONE_UNRELIABLE), gmsgFlashlight, nullptr, edict());
3175 WRITE_BYTE(state);
3176 WRITE_BYTE((int)m_fFlashBattery);
3177 MESSAGE_END();
3178}
3179
3180//-----------------------------------------------------------------------------
3181// Purpose: When recording a demo, we need to have the server tell us the entire client state
3182// so that the client side .dll can behave correctly.
3183// Reset stuff so that the state is transmitted.
3184//-----------------------------------------------------------------------------
3185void CBasePlayer::ForceClientDllUpdate(void)
3186{
3187 m_iClientBattery = -1;
3188 m_iTrain |= TRAIN_NEW;// Force new train message.
3189 m_fInventoryChanged = FALSE;// Force weaponinit messages.
3190 m_fInitHUD = TRUE;// Force HUD gmsgResetHUD message
3191 // Now force all the necessary messages to be sent.
3192 UpdateClientData();
3193}
3194
3195//-----------------------------------------------------------------------------
3196// Purpose: How much ammo of this type? (index)
3197// Input : &iAmmoIndex -
3198// Output : int - count
3199//-----------------------------------------------------------------------------
3200uint32 CBasePlayer::AmmoInventory(const int &iAmmoIndex)
3201{
3202 if (iAmmoIndex < 0 || iAmmoIndex >= MAX_AMMO_SLOTS)
3203 return 0;// TODO: remove this and rewrite with uint32
3204
3205 return m_rgAmmo[iAmmoIndex];
3206}
3207
3208//-----------------------------------------------------------------------------
3209// Purpose: This function is used to find and store all the ammo we have into the ammo vars.
3210//-----------------------------------------------------------------------------
3211void CBasePlayer::TabulateAmmo(void)
3212{
3213/*#if defined(CLIENT_WEAPONS)// big valve hack
3214 ammo_9mm = AmmoInventory(GetAmmoIndexFromRegistry("9mm"));
3215 ammo_357 = AmmoInventory(GetAmmoIndexFromRegistry("357"));
3216 ammo_argrens = AmmoInventory(GetAmmoIndexFromRegistry("ARgrenades"));
3217 ammo_bolts = AmmoInventory(GetAmmoIndexFromRegistry("bolts"));
3218 ammo_buckshot = AmmoInventory(GetAmmoIndexFromRegistry("buckshot"));
3219 ammo_rockets = AmmoInventory(GetAmmoIndexFromRegistry("rockets"));
3220 ammo_uranium = AmmoInventory(GetAmmoIndexFromRegistry("uranium"));
3221 ammo_hornets = AmmoInventory(GetAmmoIndexFromRegistry("Hornets"));
3222#endif*/
3223 if (m_pActiveItem && m_pActiveItem->GetWeaponPtr())
3224 {
3225 if (m_pActiveItem->GetWeaponPtr()->m_fInReload > 0)
3226 SetAnimation(PLAYER_RELOAD);
3227 }
3228}
3229
3230//-----------------------------------------------------------------------------
3231// Purpose: Give Ammo by ID
3232// Input : iAmount - how much do we try to add
3233// &iIndex -
3234// Output : Returns the amount of ammo actually added
3235//-----------------------------------------------------------------------------
3236uint32 CBasePlayer::GiveAmmo(const uint32 &iAmount, const int &iIndex)
3237{
3238 DBG_ITM_PRINT("CBasePlayer(%d)::GiveAmmo(%u, %d)\n", entindex(), iAmount, iIndex);//, iMax);
3239
3240 if (iIndex <= AMMOINDEX_NONE || iIndex >= MAX_AMMO_SLOTS)
3241 return 0;
3242
3243 uint32 iMax = MaxAmmoCarry(iIndex);
3244 uint32 iAmmoInInventory = AmmoInventory(iIndex);
3245#if defined (_DEBUG_ITEMS)
3246 ASSERT(iAmmoInInventory <= iMax);// WTF
3247#endif
3248
3249 if (iAmmoInInventory >= iMax)// XDM3035b: check here!
3250 return 0;
3251
3252 //never used if (g_pGameRules && !g_pGameRules->CanHaveAmmo(this, iIndex))
3253 // return 0;// game rules say I can't have any more of this ammo type.
3254
3255 uint32 iAdd = min(iMax - iAmmoInInventory, iAmount);// fill inventory to MAX or take what iAmount can offer
3256 if (iAdd <= 0)
3257 return 0;
3258
3259 //conprintf(1, "CBasePlayer::GiveAmmo(%d %d %d max)\n", iAmount, iIndex, iMax);
3260
3261 m_rgAmmo[iIndex] += iAdd;
3262 // BAD, leaves old values on client side if (g_SilentItemPickup && m_rgAmmoLast[iIndex] > 0)// XDM3038b: don't notify when asked, but don't leave client thinking he has NO AMMO or weapon selection will not work!!
3263 // m_rgAmmoLast[iIndex] = m_rgAmmo[iIndex];
3264
3265 /* XDM3038a: client detects difference automatically
3266 if (gmsgAmmoPickup)// make sure the ammo messages have been linked first
3267 {
3268 //conprintf(2, "SV: gmsgAmmoPickup: %d (%s) +%d\n", iIndex, g_AmmoInfoArray[iIndex].name, iAdd);// XDM
3269 MESSAGE_BEGIN(MSG_ONE, gmsgAmmoPickup, nullptr, edict());
3270 WRITE_BYTE(iIndex);// ammo ID
3271 WRITE_BYTE(iAdd);// amount
3272 MESSAGE_END();
3273 }*/
3274
3275 TabulateAmmo();
3276 return iAdd;
3277}
3278
3279//-----------------------------------------------------------------------------
3280// Purpose: Add a weapon to the player (Item == Weapon == Selectable Object)
3281// XDM3035a: NOTE: add lots of checks here because we may get corrupted pointers/objects
3282// Input : *pItem -
3283// Output : ITEM_ADDRESULT
3284//-----------------------------------------------------------------------------
3285int CBasePlayer::AddPlayerItem(CBasePlayerItem *pItem)
3286{
3287 DBG_ITM_PRINT("CBasePlayer(%d)::AddPlayerItem(%d)(id %d) START\n", entindex(), pItem?pItem->entindex():0, pItem?pItem->GetID():0);
3288
3289 if (pItem == nullptr)
3290 return ITEM_ADDRESULT_NONE;
3291
3292 if (pItem->pev == nullptr)
3293 {
3294 DBG_FORCEBREAK
3295 conprintf(0, "CBasePlayer(%d)::AddPlayerItem(%d) corrupted item!\n", entindex(), pItem->GetID());
3296 return ITEM_ADDRESULT_NONE;
3297 }
3298 int id = pItem->GetID();
3299 if (id == WEAPON_NONE)
3300 {
3301 conprintf(1, "CBasePlayer(%d)::AddPlayerItem(%d) tried to add WEAPON_NONE!\n", entindex(), id);
3302 DBG_FORCEBREAK
3303 return ITEM_ADDRESULT_NONE;
3304 }
3305
3306 //something's not right here! recheck step-by-step!
3307 //somehow after removing pItem it appears in our inventory!
3308 CBasePlayerItem *pExisting = GetInventoryItem(id);// XDM: !!! we may already have item of this kind!
3309 if (pExisting)// XDM3035b
3310 {
3311 if (pExisting == pItem)// Why? Why does this keep happening?
3312 {
3313 conprintf(1, "CBasePlayer(%d)::AddPlayerItem(%d)(id %d): Error detected: pExisting == pItem!\n", entindex(), pItem->entindex(), id);
3314#if defined (_DEBUG_ITEMS)
3315 DBG_FORCEBREAK
3316#endif
3317 pItem->AttachTo(this);// prevent further touching!!
3318 return ITEM_ADDRESULT_NONE;// pretend we did not touch it, so DefaultTouch() won't kill it! right?
3319 }
3320
3321 if (pItem->AddDuplicate(pExisting))
3322 {
3323 if (HasPlayerItem(pItem))
3324 {
3325 conprintf(1, "CBasePlayer(%d)::AddPlayerItem(%d)(id %d) duplicate pItem got into inventory!!\n", entindex(), pItem->entindex(), id);
3326 DBG_FORCEBREAK
3327 }
3328 return ITEM_ADDRESULT_EXTRACTED;// EXTRACTED AMMO
3329 }
3330#if defined (_DEBUG)
3331 if (GetInventoryItem(id) == nullptr)
3332 {
3333 conprintf(1, "CBasePlayer(%d)::AddPlayerItem(%d)(id %d) KERNEL PANIC!!!!!!!!\n", entindex(), pItem->entindex(), pItem->GetID());
3334 DBG_FORCEBREAK
3335 }
3336#endif
3337 DBG_ITM_PRINT("CBasePlayer(%d)::AddPlayerItem(%d)(id %d) step 1 ITEM_ADDRESULT_NONE\n", entindex(), pItem->entindex(), pItem->GetID());
3338 return ITEM_ADDRESULT_NONE;// NOT EXTRACTED
3339 }
3340 else if (pItem->AddToPlayer(this))
3341 {
3342 // Don't disable this!
3343 SetBits(pev->weapons, 1<<id);// XDM3035a: NOTE: External bots use this!
3344 m_rgpWeapons[id] = pItem;// XDM3038: pItem->GetID()] = pItem;
3345//#if defined (_DEBUG_ITEMS)
3346// m_rgpWeapons[id].m_debuglevel = 2;
3347//#endif
3348 //useless if (g_pGameRules) g_pGameRules->PlayerGotWeapon(this, pItem);
3349 //pItem->CheckRespawn();
3350 /* XDM3038a: OBSOLETE MESSAGE_BEGIN(MSG_ONE, gmsgWeapPickup, nullptr, edict());
3351 WRITE_BYTE(id);// XDM3038: pItem->GetID());
3352 MESSAGE_END();*/
3353 DBG_ITM_PRINT("CBasePlayer(%d)::AddPlayerItem(%d)(id %d) step 2 ITEM_ADDRESULT_PICKED\n", entindex(), pItem->entindex(), pItem->GetID());
3354 return ITEM_ADDRESULT_PICKED;// PICKED UP
3355 }
3356 DBG_ITM_PRINT("CBasePlayer(%d)::AddPlayerItem(%d)(id %d) step 3 ITEM_ADDRESULT_NONE!\n", entindex(), pItem->entindex(), pItem->GetID());
3357 return ITEM_ADDRESULT_NONE;// NOT PICKED
3358}
3359
3360//-----------------------------------------------------------------------------
3361// Purpose: Clears item from player, but does not destroy it!
3362// Warning: Don't corrupt the item! Also, modify only player data here.
3363// Input : *pItem -
3364// Output : Returns true on success, false on failure.
3365//-----------------------------------------------------------------------------
3366bool CBasePlayer::RemovePlayerItem(CBasePlayerItem *pItem)
3367{
3368 DBG_ITM_PRINT("CBasePlayer(%d)::RemovePlayerItem(%d)(id %d)\n", entindex(), pItem?pItem->entindex():0, pItem?pItem->GetID():0);
3369 if (pItem == nullptr)
3370 return false;
3371
3372 if (pItem->GetID() == WEAPON_NONE)
3373 {
3374 conprintf(1, "RemovePlayerItem(%s) ERROR: tried to remove item with ID WEAPON_NONE!\n", STRING(pItem->pev->classname));
3375 DBG_FORCEBREAK
3376 return false;
3377 }
3378
3379 if (m_pActiveItem == pItem)
3380 {
3381 ResetAutoaim();
3382 //if (pItem->CanHolster())// XDM3038c if (AmmoInventory(pItem->PrimaryAmmoIndex()) != 0)// CAN be -1!! Prevent recursion when removing item
3383 pItem->Holster();
3384
3385 m_pActiveItem = nullptr;
3386 pev->viewmodel = iStringNull;
3387 pev->weaponmodel = iStringNull;
3388 }
3389
3390 if (m_pLastItem == pItem)
3391 m_pLastItem = nullptr;
3392
3393 if (m_pNextItem == pItem)// XDM
3394 m_pNextItem = nullptr;
3395
3396 int ID = pItem->GetID();
3397 if (m_rgpWeapons[ID] == pItem || m_rgpWeapons[ID].Get() == nullptr)// EXACTLY the same pointer! Not just same weapon ID XDM3035b: wtf?!?!
3398 {
3399 m_rgpWeapons[ID] = nullptr;
3400 ClearBits(pev->weapons, 1<<ID);// XDM: moved from weapons like CWeaponHandGrenade // take item off hud
3401 pItem->DetachFromHost();
3402 return true;
3403 }
3404 else
3405 {
3406 DBG_FORCEBREAK
3407 conprintf(1, "CBasePlayer(%d)::RemovePlayerItem(%d)(id %d) MISMATCH!! ei %d\n", entindex(), pItem->entindex(), ID, m_rgpWeapons[ID]->entindex());
3408 pItem->DetachFromHost();
3409 //pItem->DestroyItem();
3410 return true;// HACK: to act as normal
3411 }
3412 //return false;
3413}
3414
3415//-----------------------------------------------------------------------------
3416// Purpose: Drop specified item onto floor
3417// Input : *pItem -
3418// Output : Returns true on success, false on failure.
3419//-----------------------------------------------------------------------------
3420bool CBasePlayer::DropPlayerItem(CBasePlayerItem *pItem)
3421{
3422 DBG_ITM_PRINT("CBasePlayer(%d)::DropPlayerItem(%d)(id %d)\n", entindex(), pItem?pItem->entindex():0, pItem?pItem->GetID():0);
3423 if (pItem == nullptr)
3424 return false;
3425 if (IsMultiplayer() && (mp_weaponstay.value > 0.0f))
3426 return false;// no dropping allowed
3427 if (!CanDropItem(pItem))// XDM3038a
3428 return false;
3429
3430 int iAmmoIndex;
3431 bool bDroppedActiveItem;// XDM3038
3432 if (pItem == m_pActiveItem)
3433 bDroppedActiveItem = true;
3434 else
3435 bDroppedActiveItem = false;
3436
3437 if (!RemovePlayerItem(pItem))// m_pActiveItem == nullptr after this!
3438 return false;
3439
3440 CBasePlayerWeapon *pDroppedWeapon = pItem->GetWeaponPtr();
3441 if (pDroppedWeapon)// here we decide how much ammunition should be dropped with this weapon
3442 {
3443 //pDroppedWeapon->m_iDefaultAmmo = 0;// important
3444 pDroppedWeapon->m_iAmmoContained1 = 0;
3445 pDroppedWeapon->m_iAmmoContained2 = 0;
3446 iAmmoIndex = pDroppedWeapon->PrimaryAmmoIndex();//GetAmmoIndexFromRegistry(pItem->pszAmmo1());// safety?
3447 if (iAmmoIndex > AMMOINDEX_NONE)// XDM3037: fixed. this weapon weapon uses ammo, so pack an appropriate amount.
3448 {
3449 uint32 iPack;
3450 if (FBitSet(pDroppedWeapon->iFlags(), ITEM_FLAG_EXHAUSTIBLE))// pack up all the ammo, this weapon is its own ammo type
3451 iPack = m_rgAmmo[iAmmoIndex];
3452 else// pack half of the ammo
3453 iPack = (uint32)(m_rgAmmo[iAmmoIndex]/2);// now we won't lose any ammo due to type conversion
3454
3455 pDroppedWeapon->PackAmmo(iPack, false);
3456 m_rgAmmo[iAmmoIndex] -= iPack;
3457 }
3458 iAmmoIndex = pDroppedWeapon->SecondaryAmmoIndex();//GetAmmoIndexFromRegistry(pItem->pszAmmo2());// safety?
3459 if (iAmmoIndex > AMMOINDEX_NONE)// XDM3038c: secondary ammo too
3460 {
3461 uint32 iPack;
3462 if (FBitSet(pDroppedWeapon->iFlags(), ITEM_FLAG_EXHAUSTIBLE))// pack up all the ammo
3463 iPack = m_rgAmmo[iAmmoIndex];
3464 else// pack half of the ammo
3465 iPack = (uint32)(m_rgAmmo[iAmmoIndex]/2);
3466
3467 pDroppedWeapon->PackAmmo(iPack, true);
3468 m_rgAmmo[iAmmoIndex] -= iPack;
3469 }
3470 }
3471 // XDM3037a: even if it's not a CBasePlayerWeapon
3472 UTIL_MakeVectors(pev->v_angle);//ANGLE_VECTORS
3473 //ClearBits(pDroppedItem->pev->spawnflags, SF_ITEM_NOFALL);
3474 //SetBits(pDroppedItem->pev->spawnflags, SF_NORESPAWN);// should be already set
3475 pItem->pev->gravity = g_pWorld?g_pWorld->pev->gravity:1.0f;// XDM3038: HACK until global gravity policy is established
3476 pItem->pev->origin = GetGunPosition() + gpGlobals->v_forward * 36.0f;
3477 pItem->pev->movetype = MOVETYPE_TOSS;//pItem->FallInit();
3478 pItem->Materialize();
3479 pItem->pev->angles.y = pev->angles.y;
3480 pItem->pev->angles.z = 0.0f;
3481 pItem->pev->velocity = gpGlobals->v_forward * 30.0f + pev->velocity;// weaponbox has player's velocity, then some.
3482 //FAIL! pItem->pev->groupinfo = pev->groupinfo;// XDM3038c: to disable collisions with this player
3483 //UTIL_SetGroupTrace(pev->groupinfo, GROUP_OP_AND);// XDM3038c: TESTME!
3484 pItem->pev->owner = edict();// don't touch me!
3485 if (bDroppedActiveItem && IsAlive())// XDM3038c
3486 SelectNextBestItem(pItem);
3487
3488 m_flNextAttack = UTIL_WeaponTimeBase() + 0.2f;// XDM3038a
3489 return true;// since RemovePlayerItem() worked
3490}
3491
3492//-----------------------------------------------------------------------------
3493// Purpose: drop the named item, or if no name, the active item.
3494// Input : *pszItemName - can be NULL
3495// Output : Returns true on success, false on failure.
3496//-----------------------------------------------------------------------------
3497bool CBasePlayer::DropPlayerItem(const char *pszItemName)
3498{
3499 DBG_ITM_PRINT("CBasePlayer(%d)::DropPlayerItem(%s)\n", entindex(), pszItemName);
3500
3501 if (pszItemName && strlen(pszItemName) == 0)// XDM3038b: NULL
3502 pszItemName = nullptr;
3503
3504 CBasePlayerItem *pWeapon = nullptr;
3505 if (pszItemName)
3506 {
3507 CBasePlayerItem *pItem = nullptr;
3508 for (int i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
3509 {
3510 pItem = GetInventoryItem(i);
3511 if (pItem)
3512 {
3513 if (strcmp(pItem->GetWeaponName(), pszItemName) == 0)// XDM3038a: this should NOT use g_ItemInfoArray!
3514 {
3515 pWeapon = pItem;
3516 break;
3517 }
3518 }
3519 }
3520 }
3521 else
3522 pWeapon = m_pActiveItem;// trying to drop active item
3523
3524 return DropPlayerItem(pWeapon);// pWeapon may still be NULL
3525}
3526
3527//-----------------------------------------------------------------------------
3528// Purpose: Pack up items flagged as quest/important only (not really pack, just drop)
3529// Output : number of those items
3530// Callers: ClientRemoveFromGame, observer think
3531//-----------------------------------------------------------------------------
3532uint32 CBasePlayer::PackImportantItems(void)
3533{
3534 SetBits(m_iGameFlags, EGF_DROPPEDITEMS);// XDM3038c
3535 CBasePlayerItem *pInvItem;
3536 uint32 n = 0;
3537 for (int i = 0; i < PLAYER_INVENTORY_SIZE; ++i)// search for items that should never disappear!
3538 {
3539 pInvItem = GetInventoryItem(i);
3540 if (pInvItem)
3541 {
3542 if (pInvItem->iFlags() & ITEM_FLAG_IMPORTANT)
3543 {
3544 // Things may go wrong there: DropPlayerItem(pInvItem);
3545 ASSERT(RemovePlayerItem(pInvItem));// -> DetachFromHost
3546#if defined (_DEBUG)
3547 ASSERT(pInvItem->GetItemState() == ITEM_STATE_DROPPED);// XDM3038
3548 ASSERT(FBitSet(pInvItem->pev->spawnflags, SF_NORESPAWN));
3549#endif
3550 pInvItem->pev->movetype = MOVETYPE_TOSS;//pInvItem->FallInit();
3551 pInvItem->Materialize();
3552 pInvItem->pev->velocity = pev->velocity; pInvItem->pev->velocity *= 1.4f;
3553 pInvItem->pev->angles.y = pev->angles.y;
3554 pInvItem->pev->angles.z = 0.0f;
3555 pInvItem->pev->avelocity = pev->avelocity;// XDM3038
3556 pInvItem->m_flRemoveTime = 0;// don't remove
3557 ++n;
3558 }
3559 }
3560 }
3561 return n;
3562}
3563
3564// PackAmmoFlags
3565#define PAF_USED 0x01U
3566#define PAF_REMOVED 0x02U
3567#define PAF_IMPORTANT 0x04U
3568
3569//-----------------------------------------------------------------------------
3570// Purpose: Pack up the appropriate weapons and ammo items, and destroy anything that shouldn't be packed.
3571// Warning: EXHAUSTIBLE items are special case!
3572// UNDONE: this sould really be someting like this->TransferInventory(pWeaponBox);
3573//-----------------------------------------------------------------------------
3574void CBasePlayer::PackDeadPlayerItems(void)
3575{
3576 DBG_PLR_PRINT("CBasePlayer(%d)::PackDeadPlayerItems()\n", entindex());
3577 if (FBitSet(m_iGameFlags, EGF_DROPPEDITEMS))// XDM3038c
3578 return;
3579
3580 // get the game rules
3581 int i, j;
3582 uint32 iNumImportantItems = 0;
3583 CBasePlayerItem *pInvItem;
3584 short iWeaponRules = g_pGameRules?g_pGameRules->DeadPlayerWeapons(this):GR_PLR_DROP_GUN_ACTIVE;
3585 short iAmmoRules = g_pGameRules?g_pGameRules->DeadPlayerAmmo(this):GR_PLR_DROP_AMMO_ACTIVE;
3586
3587 SetBits(m_iGameFlags, EGF_DROPPEDITEMS);// XDM3038c
3588#if defined (USE_EXCEPTIONS)
3589 try
3590 {
3591#endif
3592 // XDM3038c: search for items that should never disappear!
3593 for (i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
3594 {
3595 pInvItem = GetInventoryItem(i);
3596 if (pInvItem)
3597 {
3598 if (pInvItem->iFlags() & ITEM_FLAG_IMPORTANT)
3599 {
3600 ++iNumImportantItems;
3601 if (iWeaponRules != GR_PLR_DROP_GUN_ALL)// will not be packed into box, so drop it now! All of them!
3602 {
3603 // Things may go wrong there: DropPlayerItem(pInvItem);
3604 ASSERT(RemovePlayerItem(pInvItem));// -> DetachFromHost
3605#if defined (_DEBUG)
3606 ASSERT(pInvItem->GetItemState() == ITEM_STATE_DROPPED);// XDM3038
3607 ASSERT(FBitSet(pInvItem->pev->spawnflags, SF_NORESPAWN));
3608#endif
3609 pInvItem->pev->movetype = MOVETYPE_TOSS;//pInvItem->FallInit();
3610 pInvItem->Materialize();
3611 pInvItem->pev->velocity = pev->velocity; pInvItem->pev->velocity *= 1.4f;
3612 pInvItem->pev->angles.y = pev->angles.y;
3613 pInvItem->pev->angles.z = 0.0f;
3614 pInvItem->pev->avelocity = pev->avelocity;// XDM3038
3615 pInvItem->m_flRemoveTime = 0;// don't remove
3616 }
3617 }
3618 }
3619 }
3620
3621 // Nothing to pack. Remove the weapons and return. Don't create a box.
3622 if (iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO)
3623 goto finish;// cleanup
3624
3625 if (m_pActiveItem == nullptr)// player was killed while changing weapons
3626 m_pActiveItem = m_pLastItem;
3627
3628 // XDM3035b: drop real weapon, pack ammo into its clip. TODO: Move to DropPlayerItem()?
3629 if (iWeaponRules == GR_PLR_DROP_GUN_ACTIVE && iAmmoRules == GR_PLR_DROP_AMMO_ACTIVE && m_pActiveItem)
3630 {
3631 CBasePlayerItem *pDroppedItem = m_pActiveItem;
3632 if (!CanPackDeadPlayerItem(pDroppedItem))// XDM3037a: player is holding a weapon he cannot drop
3633 {
3634 pDroppedItem = g_pGameRules?g_pGameRules->GetNextBestWeapon(this, pDroppedItem):nullptr;// try dropping other best weapon
3635 if (pDroppedItem)// found something
3636 {
3637 if (!CanPackDeadPlayerItem(pDroppedItem))// cannot drop best item
3638 {
3639 pDroppedItem = nullptr;
3640 CBasePlayerItem *pTempItem;
3641 for (i = WEAPON_SUIT-1; i > WEAPON_NONE; --i)// XDM3038: desperation could probably lead to endless NextBestWeapon loop, so just iterate numbers to zero
3642 {
3643 pTempItem = GetInventoryItem(i);
3644 if (pTempItem == nullptr)// since we surely know that active item is not droppable
3645 continue;
3646 if (CanPackDeadPlayerItem(pTempItem))
3647 {
3648 pDroppedItem = pTempItem;
3649 break;// found one!
3650 }
3651 }
3652 }
3653 }
3654 }
3655 if (pDroppedItem == nullptr)// XDM3037a: nothing, at all
3656 goto finish;// give up safely
3657
3658 if (RemovePlayerItem(pDroppedItem))// m_pActiveItem == nullptr after this!
3659 {
3660 CBasePlayerWeapon *pDroppedWeapon = pDroppedItem->GetWeaponPtr();
3661 if (pDroppedWeapon)// actually a weapon
3662 {
3663 int iAmmoIndex = GetAmmoIndexFromRegistry(pDroppedItem->pszAmmo1());
3664 if (iAmmoIndex > AMMOINDEX_NONE)// this weapon uses ammo
3665 {
3666 //pDroppedWeapon->m_iDefaultAmmo = 0;// important
3667 pDroppedWeapon->m_iAmmoContained1 = 0;
3668 pDroppedWeapon->m_iAmmoContained2 = 0;
3669 pDroppedWeapon->PackAmmo(m_rgAmmo[iAmmoIndex], false);//, true); don't lose the clip itself here
3670 m_rgAmmo[iAmmoIndex] = 0;
3671 }
3672 iAmmoIndex = GetAmmoIndexFromRegistry(pDroppedItem->pszAmmo2());
3673 if (iAmmoIndex > AMMOINDEX_NONE)// this weapon uses secondary ammo
3674 {
3675 pDroppedWeapon->PackAmmo(m_rgAmmo[iAmmoIndex], true);
3676 m_rgAmmo[iAmmoIndex] = 0;
3677 }
3678 }
3679 // XDM3038a: this was a terrible mistake...
3680#if defined (_DEBUG)
3681 ASSERT(pDroppedItem->pev->impulse == ITEM_STATE_DROPPED);// XDM3038
3682 ASSERT(FBitSet(pDroppedItem->pev->spawnflags, SF_NORESPAWN));
3683#endif
3684 pDroppedItem->pev->movetype = MOVETYPE_TOSS;//pDroppedItem->FallInit();
3685 pDroppedItem->Materialize();
3686 pDroppedItem->pev->velocity = pev->velocity; pDroppedItem->pev->velocity *= 1.4f;
3687 pDroppedItem->pev->angles.y = pev->angles.y;
3688 pDroppedItem->pev->angles.z = 0.0f;
3689 pDroppedItem->pev->avelocity = pev->avelocity;// XDM3038
3690 if (IsMultiplayer())
3691 {
3692 pDroppedItem->pev->owner = edict();// XDM3038c: TESME fall through
3693 pDroppedItem->m_hOwner = this;// XDM3038c: TESME pickup policy
3694
3695 if ((pDroppedItem->iFlags() & ITEM_FLAG_IMPORTANT) || (mp_droppeditemstaytime.value <= 0.0f) || (g_pGameRules && g_pGameRules->IsCoOp() && g_pGameRules->GetGameMode() == COOP_MODE_LEVEL))// XDM3039
3696 {
3697 pDroppedItem->m_flRemoveTime = 0.0;// prevent
3698 //pDroppedItem->pev->takedamage = DAMAGE_NO;
3699 }
3700 else
3701 pDroppedItem->m_flRemoveTime = gpGlobals->time + mp_droppeditemstaytime.value;
3702 }
3703 // BAD ++g_iWeaponBoxCount;// hack
3704 goto finish;// since RemovePlayerItem() worked, we can't go back
3705 }
3706 }// (iWeaponRules == GR_PLR_DROP_GUN_ACTIVE && iAmmoRules == GR_PLR_DROP_AMMO_ACTIVE)
3707
3708 int iPrimaryAmmoIndex, iSecondaryAmmoIndex;
3709 CBasePlayerWeapon *rgpPackWeapons[PLAYER_INVENTORY_SIZE];// holds weapons to pack. Warning: i != ID!!
3710 int iPackAmmo[MAX_AMMO_SLOTS];// holds ammo indexes to pack. Warning: i != ID!!
3711 byte iPackAmmoFlags[MAX_AMMO_SLOTS];// need this to determine which ammo type to include. Warning: i == AmmoID!
3712 unsigned short iPW = 0;// number of weapons packed
3713 unsigned short iPA;// number of ammo packed
3714
3715 memset(rgpPackWeapons, 0, sizeof(rgpPackWeapons));
3716 memset(iPackAmmoFlags, 0, sizeof(iPackAmmoFlags));// !
3717 for (iPA=0; iPA<MAX_AMMO_SLOTS; ++iPA)
3718 iPackAmmo[iPA] = AMMOINDEX_NONE;
3719
3720 iPA = 0;// !
3721 // Go through ammo and make a list of which types to pack
3722 if (iAmmoRules == GR_PLR_DROP_AMMO_ACTIVE)
3723 {
3724 if (m_pActiveItem)// NOTE: ammo amount mey be 0 here
3725 {
3726 CBasePlayerWeapon *pWeapon = m_pActiveItem->GetWeaponPtr();
3727 if (pWeapon)
3728 {
3729 iPrimaryAmmoIndex = pWeapon->PrimaryAmmoIndex();
3730 if (iPrimaryAmmoIndex > AMMOINDEX_NONE) // this is the primary ammo type for the active weapon
3731 iPackAmmo[iPA++] = iPrimaryAmmoIndex;
3732
3733 iSecondaryAmmoIndex = pWeapon->SecondaryAmmoIndex();
3734 if (iSecondaryAmmoIndex > AMMOINDEX_NONE)// this is the secondary ammo type for the active weapon
3735 iPackAmmo[iPA++] = iSecondaryAmmoIndex;
3736 }
3737 }
3738 }
3739 else if (iAmmoRules == GR_PLR_DROP_AMMO_ALL)// NOTE: if this "ammo" is useless because of EXHAUSTIBLE item, it will be removed from this list later
3740 {
3741 for (i = 0; i < MAX_AMMO_SLOTS; ++i)
3742 {
3743 if (m_rgAmmo[i] > 0)
3744 iPackAmmo[iPA++] = i;
3745 }
3746 }
3747
3748 // Go through all of the weapons and make a list of the ones to pack
3749 if (iWeaponRules != GR_PLR_DROP_GUN_NO && !(iWeaponRules == GR_PLR_DROP_GUN_ACTIVE && m_pActiveItem == nullptr))
3750 {
3751 bool bSkipItem;
3752 for (i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
3753 {
3754 pInvItem = GetInventoryItem(i);
3755 if (pInvItem)
3756 {
3757 CBasePlayerWeapon *pItem = pInvItem->GetWeaponPtr();
3758 if (pItem == nullptr)
3759 continue;
3760 if (pItem->GetID() == WEAPON_NONE)// XDM3035
3761 {
3762 DBG_PRINTF("CBasePlayer::PackDeadPlayerItems() ERROR! Got WEAPON_NONE for i = %d!\n", i);
3763 continue;
3764 }
3765 iPrimaryAmmoIndex = pItem->PrimaryAmmoIndex();
3766 iSecondaryAmmoIndex = pItem->SecondaryAmmoIndex();
3767 bSkipItem = pItem->IsRemoving();
3768
3769 // Special case: exhausted weapon, we don't pack its ammo separately, but inside the weapon itself
3770 if (!bSkipItem && FBitSet(pItem->iFlags(), ITEM_FLAG_EXHAUSTIBLE))// DeactivateSatchels() must be called before this point!
3771 {
3772 for (j = 0; j < MAX_AMMO_SLOTS; ++j)// XDM3038c: exhaustible weapon, DO NOT pack any of its ammo! UNDONE: what if some non-exhaustible weapons share ammo with this one?!
3773 {
3774 if (iPackAmmo[j] != AMMOINDEX_NONE && (iPackAmmo[j] == iPrimaryAmmoIndex || iPackAmmo[j] == iSecondaryAmmoIndex))
3775 {
3776 iPackAmmo[j] = AMMOINDEX_NONE;
3777 --iPA;
3778 }
3779 }
3780 if (!pItem->IsUseable())
3781 continue;
3782
3783 if (pItem->HasAmmo(AMMO_ANYTYPE))
3784 {
3785 uint32 iAmmoAmount = AmmoInventory(iPrimaryAmmoIndex);
3786 if (iAmmoAmount > 0)
3787 pItem->PackAmmo(iAmmoAmount, false);
3788
3789 iAmmoAmount = AmmoInventory(iSecondaryAmmoIndex);
3790 if (iAmmoAmount > 0)
3791 pItem->PackAmmo(iAmmoAmount, true);
3792 // don't have to if (iPrimaryAmmoIndex > AMMOINDEX_NONE) m_rgAmmo[iPrimaryAmmoIndex] = 0;
3793 }
3794 else// UNDONE: TESTME: deployed satchels? continue;
3795 conprintf(1, "CBasePlayer(%d)::PackDeadPlayerItems(): error: ITEM_FLAG_EXHAUSTIBLE %s IsUseable() with no ammo!\n", pItem->GetWeaponName());
3796 }
3797
3798 if (!bSkipItem)
3799 bSkipItem = (!CanPackDeadPlayerItem(pItem) || (iWeaponRules == GR_PLR_DROP_GUN_ACTIVE && pItem != m_pActiveItem));// firewalled by game rules, etc. OR it's not the only weapon we're dropping
3800
3801 if (bSkipItem)// game rules decide if we care about disposable items
3802 {
3803 if (iPrimaryAmmoIndex > AMMOINDEX_NONE)
3804 iPackAmmoFlags[iPrimaryAmmoIndex] |= PAF_REMOVED;
3805 if (iSecondaryAmmoIndex > AMMOINDEX_NONE)
3806 iPackAmmoFlags[iSecondaryAmmoIndex] |= PAF_REMOVED;
3807 }
3808 else
3809 {
3810 if (iPrimaryAmmoIndex > AMMOINDEX_NONE)
3811 iPackAmmoFlags[iPrimaryAmmoIndex] |= PAF_USED;
3812 if (iSecondaryAmmoIndex > AMMOINDEX_NONE)
3813 iPackAmmoFlags[iSecondaryAmmoIndex] |= PAF_USED;
3814
3815 rgpPackWeapons[iPW] = pItem;
3816 ++iPW;
3817 }
3818 }
3819 }
3820 }
3821 if (iPA > 0)// Try to exclude all default-added ammo.
3822 {
3823 for (i = 0; i < MAX_AMMO_SLOTS && iPA > 0; ++i)// NOTE: since we can't really know what ammo was added by default, we guess that by related weapons
3824 {
3825 if (iPackAmmo[i] == AMMOINDEX_NONE)// just an empty slot
3826 continue;
3827 if (iPackAmmoFlags[iPackAmmo[i]] == 0)// ammo is not related to any weapons, leave it in
3828 continue;
3829 if (iPackAmmoFlags[iPackAmmo[i]] & PAF_USED)// ammo is used by at least one weapon
3830 continue;
3831 if (iPackAmmoFlags[iPackAmmo[i]] & PAF_REMOVED)// weapon which used this ammo was removed
3832 {
3833 iPackAmmo[i] = AMMOINDEX_NONE;
3834 --iPA;
3835 }
3836 }
3837#if defined (_DEBUG)
3838 if (iPA <= 0)
3839 DBG_PRINTF("CBasePlayer(%d)::PackDeadPlayerItems(): all ammo invalidated.\n", entindex());
3840#endif
3841 }
3842 if (iPW > 0 || iPA > 0)
3843 {
3844 ASSERT(iPA < MAX_AMMO_SLOTS);
3845 ASSERT(iPW < PLAYER_INVENTORY_SIZE);
3846 CWeaponBox *pWeaponBox = CWeaponBox::CreateBox(this, pev->origin, pev->angles);
3847 if (pWeaponBox)
3848 {
3849 DBG_PRINTF("CBasePlayer(%d)::PackDeadPlayerItems(): packing %d weapons and %d ammo types\n", entindex(), iPW, iPA);
3850 uint32 iAmmoAmount;
3851 for (i = 0; i < MAX_AMMO_SLOTS; ++i)// IMPORTANT: pack the AMMO first! // IMPORTANT: non-continuous list!
3852 {
3853 if (iPackAmmo[i] != AMMOINDEX_NONE)
3854 {
3855 iAmmoAmount = AmmoInventory(iPackAmmo[i]);
3856 if (!pWeaponBox->PackAmmo(iPackAmmo[i], iAmmoAmount))//if (!pWeaponBox->PackAmmo(g_AmmoInfoArray[iPackAmmo[i]].name, iAmmoAmount))// when dropping ammo of the active weapon, amount may be 0
3857 {
3858#if defined (_DEBUG)
3859 conprintf(1, "CBasePlayer(%d)::PackDeadPlayerItems() failed to pack ammo id %d (%d rounds) into box!\n", entindex(), iPackAmmo[i], iAmmoAmount);
3860#endif
3861 }
3862 }
3863 }
3864 for (i = 0; i < PLAYER_INVENTORY_SIZE; ++i)// now pack all of the WEAPONS in the list
3865 {
3866 if (rgpPackWeapons[i])
3867 {
3868 if (RemovePlayerItem(rgpPackWeapons[i]))// clear out from player's inventory, but don't destroy it!
3869 {
3870 if (!pWeaponBox->PackWeapon(rgpPackWeapons[i]))
3871 {
3872#if defined (_DEBUG)
3873 conprintf(1, "CBasePlayer(%d)::PackDeadPlayerItems() failed to pack weapon[%d] id %d into box!\n", entindex(), rgpPackWeapons[i]->entindex(), rgpPackWeapons[i]->GetID());
3874#endif
3875 rgpPackWeapons[i]->Killed(this, this, GIB_NEVER);// XDM3038a: IMPORTANT! Box may fail to store some items!
3876 rgpPackWeapons[i] = nullptr;
3877 }
3878 }
3879 else {DBG_PRINTF("CBasePlayer(%d)::PackDeadPlayerItems(): error: weapon id %d failed to detach from player!\n", entindex(), rgpPackWeapons[i]->GetID());}
3880 }
3881 }
3882 if (IsMultiplayer())
3883 {
3884 if ((iNumImportantItems > 0) || (mp_droppeditemstaytime.value <= 0.0f) || (g_pGameRules && g_pGameRules->IsCoOp() && g_pGameRules->GetGameMode() == COOP_MODE_LEVEL))// XDM3039
3885 {
3886 pWeaponBox->m_flRemoveTime = 0.0;// NEVER remove: contains important items!
3887 pWeaponBox->pev->takedamage = DAMAGE_NO;
3888 }
3889 else
3890 pWeaponBox->m_flRemoveTime = gpGlobals->time + mp_droppeditemstaytime.value;
3891 }
3892#if defined (_DEBUG)
3893 pWeaponBox->ValidateContents();
3894#endif
3895 if (pWeaponBox->IsEmpty())// in case something went wrong
3896 pWeaponBox->Destroy();
3897 }
3898 else
3899 conprintf(1, "CBasePlayer(%d)::PackDeadPlayerItems() ERROR! Unable to create a weaponbox!\n", entindex());
3900 }
3901#if defined (USE_EXCEPTIONS)
3902 }
3903 catch(...) {conprintf(1, "ERROR: PlayerDeathThink() exception!\n");}
3904#endif
3905
3906finish:
3907 //m_pActiveItem = nullptr;// don't holster, just remove
3908 RemoveAllItems(true, true);// now strip off everything that wasn't handled by the code above.
3909}
3910
3911//-----------------------------------------------------------------------------
3912// Purpose: Clear inventory array and other containers
3913// Input : bRemoveSuit -
3914// bRemoveImportant -
3915//-----------------------------------------------------------------------------
3916void CBasePlayer::RemoveAllItems(bool bRemoveSuit, bool bRemoveImportant)
3917{
3918 DBG_ITM_PRINT("CBasePlayer(%d)::RemoveAllItems()\n", entindex());
3919
3920 m_pLastItem = nullptr;
3921 m_pNextItem = nullptr;
3922
3923 if (m_pTank != nullptr)// HL20130901
3924 {
3925 m_pTank->Use(this, this, USE_OFF, 0);
3926 m_pTank = nullptr;
3927 }
3928
3929 short i = 0;
3930 for (i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
3931 {
3932 CBasePlayerItem *pItem = GetInventoryItem(i);
3933 if (pItem && (bRemoveImportant || !(pItem->iFlags() & ITEM_FLAG_IMPORTANT)))// XDM3038c
3934 {
3935 if (pItem == m_pActiveItem)// XDM3037a: now we make sure active item is in the inventory and gets removed
3936 {
3937 ResetAutoaim();
3938 m_pActiveItem = nullptr;
3939 }
3940 pItem->DetachFromHost();// testme
3941 pItem->Killed(this, this, GIB_NEVER);// XDM3038a: changed from 'Drop()', this will call RemovePlayerItem() here
3942 m_rgpWeapons[i] = nullptr;
3943 }
3944 }
3945 ASSERT(m_pActiveItem == nullptr);
3946
3947 pev->viewmodel = iStringNull;
3948 pev->weaponmodel = iStringNull;
3949
3950 if (bRemoveSuit)
3951 pev->weapons = 0;// in case we add some other non-weapon entries: ClearBits(pev->weapons, WEAPON_ALLWEAPONS|WEAPON_SUIT);
3952 else
3953 ClearBits(pev->weapons, WEAPON_ALLWEAPONS);
3954
3955 for (i = 0; i < MAX_AMMO_SLOTS; ++i)
3956 m_rgAmmo[i] = 0;
3957
3958 // XDM3037a: TESTME! A bad place for this! UpdateClientData();
3959}
3960
3961//-----------------------------------------------------------------------------
3962// Purpose: Called at the beginning of every frame
3963// Callers: PreThink()
3964//-----------------------------------------------------------------------------
3965void CBasePlayer::ItemPreFrame(void)
3966{
3967 CalcShootSpreadFactor();// do it once per frame
3968 // this prevents player from shooting while changing weapons
3969 if (m_flNextAttack > UTIL_WeaponTimeBase())
3970 return;
3971
3972 if (m_pActiveItem == nullptr)// XDM
3973 {
3974#if defined (USE_EXCEPTIONS)
3975 try
3976 {
3977#endif
3978 if (m_pNextItem)// we have some item queued for selection
3979 {
3980 if (m_pNextItem->GetID() > WEAPON_NONE)// XDM3035a: HACKFIX: somehow bad items get here
3981 {
3982 if (CanDeployItem(m_pNextItem))// XDM3038a
3983 {
3984 m_pActiveItem = m_pNextItem;
3985 //m_pActiveItem->SetOwner(this);// HACKFIX?
3986 DeployActiveItem();// XDM
3987 m_pNextItem = nullptr;
3988 }
3989 }
3990 else
3991 conprintf(1, "CBasePlayer::ItemPreFrame(): Bad next item!\n");
3992 }
3993#if defined (USE_EXCEPTIONS)
3994 }
3995 catch (...)
3996 {
3997 DBG_PRINTF("*** CBasePlayer::ItemPreFrame() exception!\n");
3998 DBG_FORCEBREAK
3999 }
4000#endif
4001 }
4002 else
4003 m_pActiveItem->ItemPreFrame();
4004}
4005
4006//-----------------------------------------------------------------------------
4007// Purpose: Called every frame by the player
4008// Callers: PostThink()
4009//-----------------------------------------------------------------------------
4010void CBasePlayer::ItemPostFrame(void)
4011{
4012 if (m_pTank)// XDM3037: prevents any weapon-related code and unwanted un-holstering
4013 return;
4014
4015 //pev->fuser1 = 0;// XDM3038c
4016 //bool bHaveSpread = false;
4017 if (m_pActiveItem)
4018 {
4019 if (g_pGameRules && !g_pGameRules->FAllowShootingOnLadder(this))
4020 {
4021 if (IsOnLadder())
4022 {
4023 if (!m_pActiveItem->IsHolstered())
4024 {
4025#if defined (_DEBUG_ITEMS)
4026 conprintf(2, "Client %d: %s Holster()\n", entindex(), STRING(m_pActiveItem->pev->classname));
4027#endif
4028 m_pActiveItem->Holster();
4029 }
4030 else// Holster() sets m_flNextAttack
4031 m_flNextAttack = UTIL_WeaponTimeBase() + 0.4f;
4032 }
4033 else
4034 {
4035 if (/*already checked m_pTank == nullptr && */m_pTrain.Get() == nullptr)// not just IsOnTrain()
4036 {
4037 // we already know that IsOnLadder() == false; bool changed_ladder = (IsOnLadder() != FBitSet(m_afPhysicsFlags, PFLAG_ONLADDER));// XDM3038a
4038 bool changed_ladder = FBitSet(m_afPhysicsFlags, PFLAG_ONLADDER);
4039 if (changed_ladder && m_pActiveItem->IsHolstered())
4040 {
4041#if defined (_DEBUG_ITEMS)
4042 conprintf(2, "Client %d: %s Deploy()\n", entindex(), STRING(m_pActiveItem->pev->classname));
4043#endif
4044 DeployActiveItem();// XDM3038a: don't call Deploy() directly!
4045/*#if defined (_DEBUG_ITEMS)
4046 if (!m_pActiveItem->Deploy())
4047 conprintf(2, "Client %d: %s fails to Deploy() after ladder!\n", entindex(), STRING(m_pActiveItem->pev->classname));
4048#else
4049 m_pActiveItem->Deploy();
4050#endif*/
4051 }
4052 }
4053 }
4054 }
4055 if (m_pActiveItem)// XDM3038a: somehow it may change
4056 {
4057 m_pActiveItem->ItemPostFrame();
4058 //bHaveSpread = true;
4059 if (fabs(m_fShootSpreadFactor - pev->fuser1) > 0.05f)// don't send updates too frequently
4060 pev->fuser1 = m_fShootSpreadFactor;// send to client side
4061 }
4062 else
4063 pev->fuser1 = 0;// XDM3038c
4064 }
4065 else if (m_pNextItem == nullptr)// XDM3038a: no current and no queued items
4066 {
4067 if (!IsOnLadder() || g_pGameRules == nullptr || g_pGameRules->FAllowShootingOnLadder(this))// XDM3038
4068 {
4069 if (m_flNextAttack <= gpGlobals->time)// XDM3035: monitor empty hands, but try to not to flood the net
4070 {
4071 int i = 0;
4072 bool bFoundWeapon = false;
4073 for (i = 0; i < PLAYER_INVENTORY_SIZE; ++i)// XDM3037a: flood prevention
4074 {
4075 CBasePlayerItem *pInvItem = GetInventoryItem(i);// XDM3038a
4076 if (pInvItem && pInvItem->CanDeploy() && CanDeployItem(pInvItem))// && pInvItem != m_pActiveItem) <- that's NULL // XDM3038: prevents unnecessary attempts
4077 {
4078 bFoundWeapon = true;// found something usable, doesn't matter how good it is
4079 break;
4080 }
4081 }
4082 if (bFoundWeapon)// have something to select (no matter what)
4083 SelectNextBestItem(nullptr);// call this AFTER sending weapon data! (client message)
4084
4085 m_flNextAttack = UTIL_WeaponTimeBase() + 4.0f;
4086 }
4087 }
4088 pev->fuser1 = 0;// XDM3038c
4089 }
4090 //if (!bHaveSpread)
4091 // pev->fuser1 = 0;// XDM3038c
4092 if (m_pActiveItem == nullptr || !m_pActiveItem->ForceAutoaim())// XDM3039: why is this here in the first place?
4093 if (pev->fov == 0.0f || pev->fov == DEFAULT_FOV)
4094 ResetAutoaim();// XDM3038c
4095}
4096
4097//-----------------------------------------------------------------------------
4098// Purpose: Tracks changes of ammo inventory and sends changed data to client side.
4099// XDM3035: combine into one packet/message
4100// Callers: UpdateClientData()
4101//-----------------------------------------------------------------------------
4102void CBasePlayer::SendAmmoUpdate(void)
4103{
4104#if defined (USE_EXCEPTIONS)
4105 try
4106 {
4107#endif
4108 size_t i = 0;
4109 size_t iSize = 0;
4110#ifdef DOUBLE_MAX_AMMO
4111 uint16 value;
4112#else
4113 byte value;
4114#endif
4115 char pBuffer[MAX_AMMO_SLOTS*(1+sizeof(value)) + 2];// XDM3038a
4116 memset(pBuffer, 0, sizeof(pBuffer));
4117
4118 for (i=0; i < MAX_AMMO_SLOTS; ++i)
4119 {
4120 if (m_rgAmmo[i] != m_rgAmmoLast[i])
4121 {
4122 //ASSERT(m_rgAmmo[i] >= 0);// && m_rgAmmo[i] < 255);
4123 //if (m_rgAmmo[i] < 0) conprintf(1, "* error: Player %s has %d ammo of type %s!\n", STRING(pev->netname), m_rgAmmo[i], g_AmmoInfoArray[i].pszName);
4124 // ammo index
4125 value = i;
4126 memcpy(pBuffer+iSize, &value, sizeof(byte)); ++iSize;
4127 // ammo amount
4128 value = max(min(m_rgAmmo[i], MAX_AMMO), 0);
4129 memcpy(pBuffer+iSize, &value, sizeof(value)); iSize+=sizeof(value);
4130 m_rgAmmoLast[i] = m_rgAmmo[i];
4131 //conprintf(2, "CBasePlayer::SendAmmoUpdate(): sending: %d %d\n", i, m_rgAmmo[i]);
4132 }
4133 }
4134
4135 if (iSize > 0)
4136 {
4137 MESSAGE_BEGIN(MSG_ONE, gmsgUpdAmmo, nullptr, edict());
4138 //WRITE_STRING(pBuffer); nobody trusts valve :( Also, bot code relies on separate WRITE_BYTEs
4139 for (i = 0; i < iSize; ++i)
4140 WRITE_BYTE(pBuffer[i]);// write buffer byte by byte even if the data size differ
4141
4142 MESSAGE_END();
4143 //conprintf(2, "CBasePlayer::SendAmmoUpdate(): sent %d bytes\n", iSize);
4144 }
4145#if defined (USE_EXCEPTIONS)
4146 }
4147 catch (...)
4148 {
4149 DBG_PRINTF("*** CBasePlayer::SendAmmoUpdate() exception!\n");
4150 DBG_FORCEBREAK
4151 }
4152#endif
4153}
4154
4155//-----------------------------------------------------------------------------
4156// Purpose: Send base weapon information (name, ammo type, etc.) and the inventory
4157//-----------------------------------------------------------------------------
4158void CBasePlayer::SendWeaponsUpdate(void)
4159{
4160 // XDM3035c: this part was moved here
4161 //if (m_iLocalWeapons == 0)// || sv_oldweaponupdate.value > 0)// XDM3038a: otherwise GetWeaponData() comes into play
4162 {
4163 int i = 0;
4164 size_t bufferlen = 0;
4165#if defined (USE_EXCEPTIONS)
4166 try
4167 {
4168#endif // USE_EXCEPTIONS
4169 // Update all the items
4170 CBasePlayerItem *pItem = (CBasePlayerItem *)BADPOINTER;
4171 char pBuffer[PLAYER_INVENTORY_SIZE*3 + 2];// WARNING! If protocol ever changes, change this buffer size!
4172 memset(pBuffer, 0, sizeof(pBuffer));
4173 for (i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
4174 {
4175#if defined (USE_EXCEPTIONS)
4176 try
4177 {
4178#endif // USE_EXCEPTIONS
4179 pItem = GetInventoryItem(i);
4180 if (pItem)
4181 bufferlen += pItem->UpdateClientData((char *)(pBuffer + bufferlen));// XDM3035: combined multiple mesages into single
4182#if defined (USE_EXCEPTIONS)
4183 }
4184 catch (...)
4185 {
4186 DBG_PRINTF("*** CBasePlayer::SendWeaponsUpdate() exception in UpdateClientData()! (i = %d, bufferlen = %u)\n", i, bufferlen);
4187 DBG_FORCEBREAK
4188 }
4189#endif // USE_EXCEPTIONS
4190 }
4191 if (bufferlen > 0)
4192 {
4193 ASSERT(bufferlen <= MAX_USER_MSG_DATA);
4194 MESSAGE_BEGIN(MSG_ONE, gmsgUpdWeapons, nullptr, edict());// XDM3035: pack all updates into a single packet!
4195 for (size_t j = 0; j < bufferlen; ++j)
4196 WRITE_BYTE(pBuffer[j]);
4197
4198 MESSAGE_END();
4199 DBG_ITM_PRINT("CBasePlayer(%d)::SendWeaponsUpdate(): sent %d bytes\n", entindex(), bufferlen);
4200 m_pClientActiveItem = m_pActiveItem;// XDM3037
4201 }
4202#if defined (USE_EXCEPTIONS)
4203 }
4204 catch (...)
4205 {
4206 DBG_PRINTF("*** CBasePlayer::SendWeaponsUpdate() exception! (i = %d, bufferlen = %u)\n", i, bufferlen);
4207 DBG_FORCEBREAK
4208 }
4209#endif // USE_EXCEPTIONS
4210 }
4211}
4212
4213//-----------------------------------------------------------------------------
4214// Purpose: XDM3035: cycle through all entities that need to SendClientData()
4215// WARNING: This may cause datagram overflow, so don't send all data in one frame!
4216// m_flInitEntitiesTime slows down data transmission
4217// NOTE: this system does not consider PVS so all players recieve packets from all entities,
4218// but this happens rarely and keeps data well synchronized.
4219//-----------------------------------------------------------------------------
4220void CBasePlayer::SendEntitiesData(void)
4221{
4222 if (m_iInitEntities > 0 && m_flInitEntitiesTime <= gpGlobals->time)
4223 {
4224 edict_t *pEdict = (edict_t *)BADPOINTER;//INDEXENT(m_iInitEntityLast);
4225 short sendcase;// XDM3038b: decide clearly for what reason we're requesting an update
4226 if (FBitSet((int)gpGlobals->serverflags, FSERVER_RESTORE))
4227 sendcase = SCD_CLIENTRESTORE;
4228 else if (m_iInitEntities == 1)//(m_iSpawnState < SPAWNSTATE_INITIALIZED)
4229 sendcase = SCD_CLIENTCONNECT;
4230 else
4231 sendcase = SCD_CLIENTUPDATEREQUEST;
4232
4233 do// some entindexes may be free or some entities send no data, so we may do many iterations here to save time
4234 {
4235 pEdict = INDEXENT(m_iInitEntity);
4236 ++m_iInitEntity;// we don't need this anywhere else here, so increase immediately
4237 if (UTIL_IsValidEntity(pEdict))
4238 {
4239 CBaseEntity *pEntity = CBaseEntity::Instance(pEdict);
4240 if (pEntity && !pEntity->IsPlayer())// XDM3035c: not players
4241 {
4242 if (pEntity->SendClientData(this, MSG_ONE, sendcase) > 0)
4243 {
4244 //DBG_PRINTF("CBasePlayer(%d)::SendClientData(%d) %s %s sent\n", entindex(), m_iInitEntity-1, STRING(pEntity->pev->classname), STRING(pEntity->pev->targetname));
4245 break;// data was sent, exit and wait for next UpdateClientData()
4246 }// otherwise entity doesn't need to send anything
4247 }
4248 }
4249 } while (m_iInitEntity < gpGlobals->maxEntities);
4250
4251 if (m_iInitEntity >= gpGlobals->maxEntities)// last index, finished.
4252 {
4253 m_iInitEntities = 0;// disable
4254 m_iInitEntity = 0;
4255 }
4256 else
4257 m_flInitEntitiesTime = gpGlobals->time + sv_entinitinterval.value;
4258 }
4259}
4260
4261//-----------------------------------------------------------------------------
4262// Purpose: Resends any changed player HUD info to the client. Called every frame.
4263// Also called at start of demo recording and playback by ForceClientDllUpdate to ensure the demo gets messages reflecting all of the HUD state info.
4264// Callers: PlayerPreThink() ForceClientDllUpdate()
4265//-----------------------------------------------------------------------------
4266void CBasePlayer::UpdateClientData(void)
4267{
4268 int myindex = entindex();
4269
4270 if (g_ClientShouldInitialize[myindex] > 0)// XDM3035a
4271 {
4272 m_fInitHUD = TRUE;// XDM3037: instead of gInitHUD
4273 m_iInitEntities = 1;// this should NEVER happen twice per map!
4274 m_iInitEntity = 0;// XDM3035c: include world
4275 m_flInitEntitiesTime = gpGlobals->time + gpGlobals->frametime + 0.5f;// XDM: IMPORTANT!!! HL will crash if we send too much in one frame
4276 g_ClientShouldInitialize[myindex] = 0;
4277 }
4278
4279#if defined (USE_EXCEPTIONS)
4280 try
4281 {
4282#endif
4283 if (m_fInitHUD && g_ServerActive)// once per spawn // XDM3039: make sure InitHUD is only called after ServerActivate()
4284 {
4285 m_fInitHUD = FALSE;
4286 MESSAGE_BEGIN(MSG_ONE, gmsgResetHUD, nullptr, edict());
4287 MESSAGE_END();
4288
4289 if (!m_fGameHUDInitialized)// this section is executed only once per map
4290 {
4291 SendAmmoRegistry(this);
4292 MESSAGE_BEGIN(MSG_ONE, gmsgInitHUD, nullptr, edict());// XDM: this also clears client fog
4293 MESSAGE_END();
4294
4295 FlashlightUpdate(FlashlightIsOn());// XDM3034: update both state and battery
4296
4297 if (g_pGameRules)
4298 g_pGameRules->InitHUD(this);
4299
4300 m_iObserverLastMode = OBS_ROAMING;// HL20130901
4301 //m_iSpawnState = SPAWNSTATE_INITIALIZED;
4302 SetBits(m_iGameFlags, EGF_INITIALIZED);// XDM3039
4303 m_iHUDDistortUpdate = 1;// XDM3038a
4304 m_fGameHUDInitialized = TRUE;
4305 }
4306 InitStatusBar();
4307 }
4308
4309 if (m_iHideHUD != m_iClientHideHUD)
4310 {
4311 if (g_pGameRules && g_pGameRules->FAllowFlashlight())
4312 ClearBits(m_iHideHUD, HIDEHUD_FLASHLIGHT);
4313 else
4314 SetBits(m_iHideHUD, HIDEHUD_FLASHLIGHT);
4315
4316 MESSAGE_BEGIN(MSG_ONE, gmsgHideWeapon, nullptr, edict());
4317 WRITE_BYTE(m_iHideHUD);
4318 MESSAGE_END();
4319 m_iClientHideHUD = m_iHideHUD;
4320 }
4321
4322 if ((int)pev->armorvalue != m_iClientBattery)// update armor, cut to int
4323 {
4324 m_iClientBattery = (int)pev->armorvalue;
4325 //ASSERT(gmsgBattery > 0);
4326 MESSAGE_BEGIN(MSG_ONE, gmsgBattery, nullptr, edict());
4327 WRITE_BYTE(m_iClientBattery);// XDM3038: was WRITE_SHORT, changed to byte because max.battery is sent via gmsgGameMode.
4328 MESSAGE_END();
4329 }
4330#if defined (USE_EXCEPTIONS)
4331 }
4332 catch (...)
4333 {
4334 DBG_PRINTF("*** CBasePlayer::UpdateClientData() exception 1!\n");
4335 DBG_FORCEBREAK
4336 }
4337#endif
4338
4339 if (!m_fInventoryChanged)// XDM3039: fix for LMS. Sends weapon factory data, NOT THE INVENTORY DATA!!!
4340 {
4341 SendWeaponsRegistry(this);
4342 m_fInventoryChanged = TRUE;
4343 }
4344
4345 if (!IsObserver())// XDM
4346 {
4347 // update damage
4348 if (pev->dmg_take/* || pev->dmg_save*/ || m_bitsHUDDamage != m_bitsDamageType)
4349 {
4350 Vector damageOrigin(pev->origin);// when direction undetermined
4351 if (pev->dmg_inflictor)
4352 {
4353 CBaseEntity *pEntity = CBaseEntity::Instance(pev->dmg_inflictor);
4354 if (pEntity)
4355 damageOrigin = pEntity->Center();
4356 }
4357 if (IsAlive())// XDM3038c
4358 {
4359 int visibleDamageBits = m_bitsDamageType & (DMGM_SHOWNHUD | DMGM_VISIBLE);
4360 if (FBitSet(m_bitsDamageType, DMGM_DISARM))// XDM3038a: 20150713
4361 SetBits(visibleDamageBits, DMG_CRUSH);// hack to display "disarmed" icon
4362
4363 if (visibleDamageBits != 0)// XDM3038a: only send types that have visible effects or HUD indication
4364 {
4365 byte a = m_LastHitGroup & 0x000000FF;
4366 if (m_fFrozen)// add frozen bit
4367 a |= 0x80;
4368
4369 MESSAGE_BEGIN(MSG_ONE, gmsgDamage, nullptr, edict());
4370 WRITE_BYTE(a);//pev->dmg_save);// XDM3037: hitgroup
4371 //WRITE_BYTE(pev->dmg_take);// damageTaken
4372 WRITE_LONG(visibleDamageBits);// bitsDamage
4373 WRITE_COORD(damageOrigin.x);// vecFrom
4374 WRITE_COORD(damageOrigin.y);
4375 WRITE_COORD(damageOrigin.z);
4376 MESSAGE_END();
4377 }
4378 }
4379 pev->dmg_take = 0;
4380 pev->dmg_save = 0;
4381 // Clear off non-time-based damage indicators
4382 m_bitsDamageType &= DMGM_TIMEBASED;
4383 m_bitsHUDDamage = m_bitsDamageType;// XDM3037: moved here to avoid redundant messages
4384 }
4385
4386 // Update Flashlight
4387 if ((m_flFlashLightTime > 0.0f) && (m_flFlashLightTime <= gpGlobals->time))
4388 {
4389 if (FlashlightIsOn())
4390 {
4391 if (m_fFlashBattery > 0.0f)
4392 {
4393 m_fFlashBattery -= FLASHLIGHT_UPDATE_INTERVAL * gSkillData.FlashlightDrain;
4394 if (m_fFlashBattery <= 0.0f)
4395 {
4396 m_fFlashBattery = 0.0f;
4397 FlashlightTurnOff();
4398 }
4399 }
4400 m_flFlashLightTime = gpGlobals->time + FLASHLIGHT_UPDATE_INTERVAL;// update in any case
4401 }
4402 else
4403 {
4404 if (m_fFlashBattery < MAX_FLASHLIGHT_CHARGE)
4405 {
4406 m_fFlashBattery += FLASHLIGHT_UPDATE_INTERVAL * gSkillData.FlashlightCharge;
4407 if (m_fFlashBattery > MAX_FLASHLIGHT_CHARGE)
4408 m_fFlashBattery = MAX_FLASHLIGHT_CHARGE;
4409
4410 m_flFlashLightTime = gpGlobals->time + FLASHLIGHT_UPDATE_INTERVAL;
4411 }
4412 else
4413 m_flFlashLightTime = 0.0f;// fully charged, disable updates
4414 }
4415 FlashlightUpdate(FlashlightIsOn());// send the message
4416 }
4417
4418 if (FBitSet(m_iTrain, TRAIN_NEW))
4419 {
4420 ASSERT(gmsgTrain > 0);
4421 MESSAGE_BEGIN(MSG_ONE, gmsgTrain, nullptr, edict());
4422 WRITE_BYTE(m_iTrain);// & 0xFF);// XDM3038a: anything beyond 1 byte is not sent anyway
4423 MESSAGE_END();
4424 ClearBits(m_iTrain, TRAIN_NEW);
4425 }
4426
4427 // XDM3038: HUD effects
4428 if (m_iHUDDistortUpdate > 0)
4429 {
4430 MESSAGE_BEGIN(MSG_ONE, gmsgHUDEffects, nullptr, edict());
4431 WRITE_BYTE(m_iHUDDistortMode);
4432 WRITE_BYTE(m_iHUDDistortValue);
4433 MESSAGE_END();
4434 m_iHUDDistortUpdate = 0;
4435 }
4436
4437 SendAmmoUpdate();// XDM3035c: combined update for all ammo
4438 SendWeaponsUpdate();// Send weapon registry (if needed) and inventory (after ammo)
4439 }// !IsObserver()
4440
4441 if (m_iCurrentMusicTrackCommandIssued == 0 && m_iCurrentMusicTrackStartTime <= gpGlobals->time)// XDM3038c: can be delayed
4442 {
4443 if (m_iCurrentMusicTrackCommand != MPSVCMD_RESET)// server-only
4444 {
4445 MESSAGE_BEGIN(MSG_ONE, gmsgAudioTrack, nullptr, edict());
4446 WRITE_BYTE(m_iCurrentMusicTrackCommand);
4447 WRITE_SHORT((int)(gpGlobals->time - m_iCurrentMusicTrackStartTime));// +-32767sec = 546mins ~= 9h
4448 WRITE_STRING(m_szCurrentMusicTrack);// must allow empty string
4449 MESSAGE_END();
4450 }
4451 // command, track and time must stay preserved to be saved
4452 m_iCurrentMusicTrackCommandIssued = 1;
4453 }
4454
4455#if defined (USE_EXCEPTIONS)
4456 try
4457 {
4458#endif
4459 SendEntitiesData();
4460#if defined (USE_EXCEPTIONS)
4461 }
4462 catch (...)
4463 {
4464 DBG_PRINTF("*** CBasePlayer::UpdateClientData() exception in SendEntitiesData()!\n");
4465 DBG_FORCEBREAK
4466 }
4467#endif
4468
4469 //XDM3037a m_iClientFOV = m_iFOV;
4470
4471 if (g_pGameRules && g_pGameRules->IsMultiplayer())// XDM3035: SAVE CPU! Not for bots
4472 UpdateStatusBar();
4473}
4474
4475//-----------------------------------------------------------------------------
4476// Purpose: XDM3038: update room type from local point of view
4477// Input : *pEmitter -
4478// iRoomtype -
4479// flRange -
4480// Output : Returns true if room type was changed.
4481//-----------------------------------------------------------------------------
4482bool CBasePlayer::UpdateSoundEnvironment(CBaseEntity *pEmitter, const int iRoomtype, const float flRange)
4483{
4484 //conprintf(1, "CBasePlayer(%d)::UpdateSoundEnvironment(%d %d %g)\n", entindex(), pEmitter?pEmitter->entindex():0, iRoomtype, flRange);
4485 if (pEmitter == nullptr)
4486 pEmitter = g_pWorld;// by default, emitter is the world
4487
4488 int iNewRoomType = iRoomtype;
4489 if (pEmitter)
4490 {
4491 m_hEntSndLast = pEmitter;
4492 if (g_pWorld && pEmitter == g_pWorld && g_pWorld->pev != nullptr)// copy world's custom room type
4493 {
4494 iNewRoomType = g_pWorld->m_iRoomType;
4495 m_flSndRange = 0.0f;
4496 }
4497 else
4498 m_flSndRange = flRange;
4499 }
4500 else// wtf? world is invalid?
4501 {
4502 m_hEntSndLast = nullptr;
4503 m_flSndRange = 0.0f;// reset
4504 }
4505
4506 if (m_flSndRoomtype != iNewRoomType)
4507 {
4508 MESSAGE_BEGIN(MSG_ONE, svc_roomtype, nullptr, edict());
4509 WRITE_SHORT(iNewRoomType);
4510 MESSAGE_END();
4511 //conprintf(1, "CBasePlayer(%d)::UpdateSoundEnvironment() CHANGED from %d to %d\n", entindex(), m_flSndRoomtype, iNewRoomType);
4512 m_flSndRoomtype = iNewRoomType;
4513 return true;
4514 }
4515 return false;
4516}
4517
4518//-----------------------------------------------------------------------------
4519// Purpose: Overridden for the player to set the proper physics flags when a barnacle grabs player.
4520// Output : Returns true on success, false on failure.
4521//-----------------------------------------------------------------------------
4522bool CBasePlayer::FBecomeProne(void)
4523{
4524 DBG_PLR_PRINT("CBasePlayer(%d)::FBecomeProne()\n", entindex());
4525 if (CBaseMonster::FBecomeProne())
4526 {
4527 m_afPhysicsFlags |= PFLAG_ONBARNACLE;
4528 m_flFallVelocity = 0.0f;
4529 TrainDetach();// XDM3035
4530 DisableLadder(2.0);// XDM3038c
4531 return true;
4532 }
4533 return false;
4534}
4535
4536//-----------------------------------------------------------------------------
4537// Purpose: Called by Barnacle victims when the barnacle pulls their head into its mouth.
4538// Input : *pBarnacle -
4539//-----------------------------------------------------------------------------
4540void CBasePlayer::BarnacleVictimBitten(CBaseEntity *pBarnacle)
4541{
4542 if (!IsAlive())// Or the player will stay frozen and unable to respawn/load game!
4543 {
4544 Killed(pBarnacle, pBarnacle->GetDamageAttacker(), GIB_ALWAYS);
4545 return;
4546 }
4547
4548 if (FBitSet(pev->flags, FL_DUCKING))
4549 ClearBits(pev->flags, FL_DUCKING);
4550
4551 TrainDetach();// XDM3035: Double check. Just in case.
4552 DisableLadder(1.0);// XDM3038c
4553 m_hEnemy = pBarnacle;// XDM3038c: a little hack
4554 UTIL_ScreenFade(this, Vector(191,0,0), 1.0, 0.1, 160, FFADE_IN);
4555}
4556
4557//-----------------------------------------------------------------------------
4558// Purpose: Overridden for player who has physics flags concerns.
4559//-----------------------------------------------------------------------------
4560void CBasePlayer::BarnacleVictimReleased(void)
4561{
4562 //UTIL_ScreenFade(this, Vector(100,0,0), 1.0, 0.5, 80, FFADE_IN);// XDM
4563 CBaseMonster::BarnacleVictimReleased();// XDM3038c
4564 ClearBits(m_afPhysicsFlags, PFLAG_ONBARNACLE);
4565 pev->movetype = MOVETYPE_WALK;
4566 pev->fixangle = FALSE;
4567 m_flNextAttack = UTIL_WeaponTimeBase();
4568 DisableLadder(0.0);// XDM3038c
4569}
4570
4571//-----------------------------------------------------------------------------
4572// Purpose: Return player light level plus virtual muzzle flash
4573// Output : int
4574//-----------------------------------------------------------------------------
4575int CBasePlayer::Illumination(void)
4576{
4577 int iIllum = CBaseMonster::Illumination() + m_iWeaponFlash;// XDM3035c: parent class
4578 if (iIllum > 255)
4579 return 255;
4580 return iIllum;
4581}
4582
4583//-----------------------------------------------------------------------------
4584// Purpose: Ignore dropped items
4585// Output : int 1
4586//-----------------------------------------------------------------------------
4587int CBasePlayer::ShouldCollide(CBaseEntity *pOther)
4588{
4589 if (pOther->IsPlayerItem())
4590 {
4591 if (pOther->pev->owner == edict())
4592 return 0;
4593 }
4594 return CBaseMonster::ShouldCollide(pOther);
4595}
4596
4597//-----------------------------------------------------------------------------
4598// Purpose: Network decision
4599// Output : Policy
4600//-----------------------------------------------------------------------------
4601/* XDM3039: this is wrong policy_t CBasePlayer::ShouldBeSentTo(CBasePlayer *pClient)
4602{
4603 if (m_iSpawnState < SPAWNSTATE_SPAWNED)// XDM3038c
4604 if (!FBitSet(m_iGameFlags, EGF_SPAWNED))// don't send me to anyone before I spawn
4605 return POLICY_DENY;
4606
4607 return CBaseMonster::ShouldBeSentTo(pClient);
4608}*/
4609
4610//-----------------------------------------------------------------------------
4611// Purpose: Calculate dynamic shooting spread based on various factors
4612//-----------------------------------------------------------------------------
4613void CBasePlayer::CalcShootSpreadFactor(void)
4614{
4615 if (gSkillData.iSkillLevel == SKILL_EASY)
4616 m_fShootSpreadFactor = SPREADFACTOR_DISABLED;
4617 else
4618 {
4619 vec_t speed = pev->velocity.Length();
4620 if (FBitSet(pev->flags, FL_DUCKING))
4621 {
4622 if (speed == 0)
4623 m_fShootSpreadFactor = SPREADFACTOR_CROUCH_IDLE;
4624 else
4625 m_fShootSpreadFactor = SPREADFACTOR_CROUCH_MOVING;
4626 }
4627 else if (speed > PLAYER_MAX_WALK_SPEED)// running
4628 m_fShootSpreadFactor = SPREADFACTOR_RUNNING;
4629 else if (speed > 0.0f)// walking
4630 m_fShootSpreadFactor = SPREADFACTOR_WALKING;// Good, but generates lots of traffic: m_fShootSpreadFactor = SPREADFACTOR_STANDING + (SPREADFACTOR_WALKING-SPREADFACTOR_STANDING) * (speed/PLAYER_MAX_WALK_SPEED);
4631 else// standing
4632 m_fShootSpreadFactor = SPREADFACTOR_STANDING;
4633 }
4634 if (!FBitSet(pev->flags, FL_ONGROUND))
4635 m_fShootSpreadFactor += SPREADFACTOR_INAIR;
4636
4637 if (m_pActiveItem && m_pActiveItem->IsPlayerWeapon())// GetWeaponPtr())
4638 {
4639 float fTimeSinceAttack = UTIL_WeaponTimeBase() - m_pActiveItem->GetLastUseTime();
4640 if (fTimeSinceAttack > 0.0f && fTimeSinceAttack < SPREAD_ATTACK_DECAY_TIME)
4641 m_fShootSpreadFactor += (1.0f - fTimeSinceAttack/SPREAD_ATTACK_DECAY_TIME) * SPREADFACTOR_AFTERSHOT;
4642 }
4643}
4644
4645//-----------------------------------------------------------------------------
4646// Purpose: Set crosshair position to point to enemey
4647// Warning: SUPER SLOW!!!!!!!!!!
4648// Input : flDelta -
4649// Output : direction, not angles!
4650//-----------------------------------------------------------------------------
4651Vector CBasePlayer::GetAutoaimVector(const float &flDelta)
4652{
4653 if (!m_pActiveItem || !m_pActiveItem->ForceAutoaim())// XDM: always check m_pActiveItem!!
4654 {
4655 UTIL_MakeVectors(pev->v_angle + pev->punchangle);
4656 return gpGlobals->v_forward;
4657 }
4658
4659 Vector vecSrc(GetGunPosition());
4660 //bool m_fOldTargeting = m_fOnTarget;
4661 Vector angles(AutoaimDeflection(vecSrc, g_psv_zmax->value, flDelta));
4662
4663 //if (m_fOldTargeting != m_fOnTarget)
4664 // XDM3038 m_pActiveItem->UpdateItemInfo();
4665
4666 NormalizeAngle180(&angles.x);// XDM3037a
4667 NormalizeAngle180(&angles.y);
4668
4669 if (angles.x > 25)
4670 angles.x = 25;
4671 else if (angles.x < -25)// XDM3038a: else
4672 angles.x = -25;
4673
4674 if (angles.y > 12)
4675 angles.y = 12;
4676 else if (angles.y < -12)
4677 angles.y = -12;
4678
4679 m_vecAutoAim = angles * 0.9f;
4680
4681 // Don't send across network if sv_aim is 0
4682 //if (m_pActiveItem->ForceAutoaim() /*|| g_psv_aim->value > 0 */)
4683 //if (m_vecAutoAim.x != m_lastx || m_vecAutoAim.y != m_lasty)
4684 if (m_vecAutoAim != m_vecAutoAimPrev)
4685 {
4686 SET_CROSSHAIRANGLE(edict(), -m_vecAutoAim.x, m_vecAutoAim.y);
4687 m_vecAutoAimPrev = m_vecAutoAim;
4688 //m_lastx = m_vecAutoAim.x;
4689 //m_lasty = m_vecAutoAim.y;
4690 }
4691 //conprintf(1, "%f %f\n", angles.x, angles.y);
4692 UTIL_MakeVectors(pev->v_angle + pev->punchangle + m_vecAutoAim);
4693 return gpGlobals->v_forward;
4694}
4695
4696//-----------------------------------------------------------------------------
4697// Purpose: Main aiming code
4698// Note : When maxEntities went over 4000 old code became useless.
4699// Input : &vecSrc -
4700// flDist -
4701// flDelta -
4702// Output : Vector
4703//-----------------------------------------------------------------------------
4704Vector CBasePlayer::AutoaimDeflection(const Vector &vecSrc, const float &flDist, const float &flDelta)
4705{
4706 CBaseEntity *pEntity = nullptr;
4707 Vector bestdir(gpGlobals->v_forward);
4708 TraceResult tr;
4709
4710 UTIL_MakeVectors(pev->v_angle + pev->punchangle + m_vecAutoAim);
4711
4712 //conprintf(1, "AutoaimDeflection\n");
4713 // try all possible entities
4714 m_fOnTarget = false;
4715
4716 // We have acquired target last frame, check it
4717 if (m_hAutoaimTarget.Get())
4718 {
4719 pEntity = m_hAutoaimTarget;
4720 vec_t fCenterDist;
4721 Vector vecCenter(pEntity->BodyTarget(vecSrc));
4722 Vector dir(vecCenter); dir -= vecSrc; fCenterDist = dir.NormalizeSelf();
4723 vec_t dot = fabs(DotProduct(dir, gpGlobals->v_right)) + fabs(DotProduct(dir, gpGlobals->v_up)) * 0.5f;
4724 dot *= 1.0f + 0.2f * (fCenterDist / flDist);
4725 if (dot <= flDelta)
4726 {
4727 m_fOnTarget = true;
4728 return dir;
4729 }
4730 else// went out of required angle
4731 {
4732 m_hAutoaimTarget = nullptr;
4733 //m_fOnTarget = false;
4734 }
4735 }
4736 // find a new target (direct trace only)
4737 UTIL_TraceLine(vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, ignore_glass, edict(), &tr);
4738 if (tr.pHit)
4739 {
4740 pEntity = CBaseEntity::Instance(tr.pHit);
4741 if (pEntity != nullptr)
4742 {
4743 if (pEntity->pev->takedamage == DAMAGE_AIM && pEntity->IsAlive())// basically aimable
4744 {
4745 // don't look through water
4746 if (!((pev->waterlevel < WATERLEVEL_HEAD && tr.pHit->v.waterlevel >= WATERLEVEL_HEAD) || (pev->waterlevel >= WATERLEVEL_HEAD && tr.pHit->v.waterlevel == WATERLEVEL_NONE)))
4747 {
4748 if (g_pGameRules == nullptr || g_pGameRules->FShouldAutoAim(this, pEntity))// don't shoot at friends
4749 {
4750 m_hAutoaimTarget = pEntity;
4751 m_fOnTarget = true;
4752 return bestdir;// No autoaim for now!
4753 }
4754 }
4755 }
4756 }
4757 }
4758 //conprintf(1, "AutoaimDeflection: NO BEST ENT!\n");
4759 m_hAutoaimTarget = nullptr;
4760 return g_vecZero;
4761}
4762
4763//-----------------------------------------------------------------------------
4764// Purpose: Reset autoaim
4765//-----------------------------------------------------------------------------
4766void CBasePlayer::ResetAutoaim(void)
4767{
4768 DBG_PLR_PRINT("CBasePlayer(%d)::ResetAutoaim()\n", entindex());
4769 if (m_vecAutoAim.x != 0.0f || m_vecAutoAim.y != 0.0f)
4770 {
4771 m_vecAutoAim.Clear();
4772 SET_CROSSHAIRANGLE(edict(), 0, 0);// this is a client message, don't flood with it
4773 }
4774 m_fOnTarget = false;
4775 m_hAutoaimTarget = nullptr;// XDM
4776}
4777
4778//-----------------------------------------------------------------------------
4779// Purpose: UNDONE: Determine real frame limit, 8 is a placeholder.
4780// Note : -1 means no custom frames present.
4781// Input : nFrames -
4782//-----------------------------------------------------------------------------
4783void CBasePlayer::SetCustomDecalFrames(int nFrames)
4784{
4785 if (nFrames > 0 && nFrames < 8)
4786 m_nCustomSprayFrames = nFrames;
4787 else
4788 m_nCustomSprayFrames = -1;
4789}
4790
4791//-----------------------------------------------------------------------------
4792// Purpose: Returns the # of custom frames this player's custom clan logo contains.
4793// Output : int
4794//-----------------------------------------------------------------------------
4795int CBasePlayer::GetCustomDecalFrames(void)
4796{
4797 return m_nCustomSprayFrames;
4798}
4799
4800//-----------------------------------------------------------------------------
4801// Purpose: Does the player already have EXACTLY THIS INSTANCE of item?
4802// Input : *pCheckItem -
4803// Output : Returns true on success, false on failure.
4804//-----------------------------------------------------------------------------
4805bool CBasePlayer::HasPlayerItem(CBasePlayerItem *pCheckItem)
4806{
4807 if (pCheckItem == nullptr)
4808 return false;
4809
4810 if (GetInventoryItem(pCheckItem->GetID()) == pCheckItem)
4811 return true;
4812
4813 return false;
4814}
4815
4816//-----------------------------------------------------------------------------
4817// Purpose: Safe way to access player's inventory
4818// Input : &iID -
4819// Output : CBasePlayerItem *
4820//-----------------------------------------------------------------------------
4821CBasePlayerItem *CBasePlayer::GetInventoryItem(const int &iItemId)
4822{
4823#if defined (_DEBUG)
4824 ASSERT(pev != nullptr);
4825#endif
4826 if (iItemId >= WEAPON_NONE && iItemId < PLAYER_INVENTORY_SIZE)
4827 {
4828 if (m_rgpWeapons[iItemId].Get())
4829 {
4830 CBasePlayerItem *pItem = (CBasePlayerItem *)(CBaseEntity *)m_rgpWeapons[iItemId];
4831 if (pItem->GetHost() != this)
4832 {
4833 conprintf(1, "CBasePlayer(%d)::GetInventoryItem(%d)(ei %d id %d) bad item owner!\n", entindex(), iItemId, pItem->entindex(), pItem->GetID());
4834 DBG_FORCEBREAK
4835 pItem->SetOwner(this);
4836 }
4837
4838#if defined (_DEBUG)
4839 if (UTIL_IsValidEntity(pItem) && (pItem->GetID() > WEAPON_NONE))
4840#endif
4841 return pItem;
4842#if defined(_DEBUG) && defined(_DEBUG_ITEMS)
4843 else
4844 conprintf(1, "CBasePlayer(%d)::GetInventoryItem(%d)(ei %d id %d) got bad item!\n", entindex(), iItemId, m_rgpWeapons[iItemId]->entindex(), pItem->GetID());
4845#endif
4846 }
4847 }
4848 return nullptr;
4849}
4850
4851//-----------------------------------------------------------------------------
4852// Purpose: Can this player attack in any way now?
4853// WARNING: Do not use CBaseMonster version as it uses m_flNextAttack in a different way!
4854// Output : Returns true on success, false on failure.
4855//-----------------------------------------------------------------------------
4856bool CBasePlayer::CanAttack(void)
4857{
4858 if (m_fFrozen)
4859 {
4860 //ALERT(at_debug, "CBasePlayer(%d %s)::CanAttack() Frozen\n", entindex(), STRING(pev->netname));
4861 return false;
4862 }
4863
4864 if (IsOnLadder() && g_pGameRules && !g_pGameRules->FAllowShootingOnLadder(this))// XDM3035: TESTME
4865 {
4866 //ALERT(at_debug, "CBasePlayer(%d %s)::CanAttack() OnLadder\n", entindex(), STRING(pev->netname));
4867 if (m_pActiveItem && !FBitSet(m_pActiveItem->iFlags(), ITEM_FLAG_USEONLADDER))
4868 return false;
4869 }
4870
4871 if (m_flNextAttack > UTIL_WeaponTimeBase())// this prevents player from shooting while changing weapons
4872 {
4873 //ALERT(at_debug, "CBasePlayer(%d %s)::CanAttack() NextAttack\n", entindex(), STRING(pev->netname));
4874 return false;
4875 }
4876
4877 return true;
4878}
4879
4880//-----------------------------------------------------------------------------
4881// Purpose: XDM3038: player-specific layer
4882// Input : *pItem -
4883// Output : Returns true on success, false on failure.
4884//-----------------------------------------------------------------------------
4885bool CBasePlayer::CanDeployItem(CBasePlayerItem *pItem)
4886{
4887 if (pItem == nullptr)
4888 return false;
4889
4890 // XDM3038a: temporary fix of bad viewmodels! if (m_fFrozen)
4891 // return false;
4892 if (m_pTank != nullptr)
4893 return false;
4894
4895 if (m_flNextAttack > UTIL_WeaponTimeBase())// XDM3038a: prevent client cmd from selecting server-holstered weapon
4896 return false;
4897
4898 if (m_fFrozen)
4899 {
4900 //ALERT(at_debug, "CBasePlayer(%d %s)::CanDeployItem() Frozen\n", entindex(), STRING(pev->netname));
4901 return false;
4902 }
4903
4904 if (m_hCarryingObject.Get())
4905 {
4906 if (FBitSet(m_hCarryingObject->pev->spawnflags, SF_CAPTUREOBJECT_HOLSTERWEAPON))
4907 {
4908 //ALERT(at_debug, "CBasePlayer(%d %s)::CanDeployItem() SF_CAPTUREOBJECT_HOLSTERWEAPON\n", entindex(), STRING(pev->netname));
4909 return false;
4910 }
4911 }
4912
4913 if (g_pGameRules && !g_pGameRules->FAllowShootingOnLadder(this))// XDM3035: TESTME
4914 {
4915 if (IsOnLadder() && !FBitSet(pItem->iFlags(), ITEM_FLAG_USEONLADDER))
4916 return false;
4917 }
4918 return IsAlive();
4919}
4920
4921//-----------------------------------------------------------------------------
4922// Purpose: XDM3038: can player manually drop this item? (player-specific layer)
4923// Input : *pItem -
4924// Output : Returns true on success, false on failure.
4925//-----------------------------------------------------------------------------
4926bool CBasePlayer::CanDropItem(CBasePlayerItem *pItem)
4927{
4928 if (pItem == nullptr)
4929 return false;
4930
4931 return g_pGameRules?g_pGameRules->CanDropPlayerItem(this, pItem):true;
4932}
4933
4934//-----------------------------------------------------------------------------
4935// Purpose: XDM3039: explicitly decide if player can pack this item when killed (player-specific layer)
4936// Input : *pItem -
4937// Output : Returns true on success, false on failure.
4938//-----------------------------------------------------------------------------
4939bool CBasePlayer::CanPackDeadPlayerItem(CBasePlayerItem *pItem)
4940{
4941 if (pItem == nullptr)
4942 return false;
4943
4944 return g_pGameRules?g_pGameRules->CanDropDeadPlayerItem(this, pItem):true;
4945}
4946
4947//-----------------------------------------------------------------------------
4948// Purpose: DeployActiveItem
4949//-----------------------------------------------------------------------------
4950void CBasePlayer::DeployActiveItem(void)
4951{
4952 if (m_pActiveItem)
4953 {
4954#if defined (_DEBUG)
4955 DBG_ITM_PRINT("CBasePlayer(%d)::DeployActiveItem()\n", entindex());
4956 if (m_pActiveItem->GetHost() != this)
4957 {
4958 conprintf(1, "CBasePlayer(%d)::DeployActiveItem(ei %d id %d) bad item owner!\n", entindex(), m_pActiveItem->entindex(), m_pActiveItem->GetID());
4959 DBG_FORCEBREAK
4960 }
4961#endif
4962 if (!CanDeployItem(m_pActiveItem))// XDM3038
4963 return;
4964
4965 // m_pActiveItem->SetOwner(this);// WTF? safety
4966 // bad if (m_pLastItem && m_pLastItem->IsHolstered())// XDM3038
4967 // m_pActiveItem->Holster();
4968 // else
4969 m_pActiveItem->Deploy();
4970
4971 // XDM3038 m_pActiveItem->UpdateItemInfo();
4972 }
4973}
4974
4975//-----------------------------------------------------------------------------
4976// Purpose: Used by SelectItem to delay weapon selection
4977// Input : *pItem -
4978//-----------------------------------------------------------------------------
4979void CBasePlayer::QueueItem(CBasePlayerItem *pItem)// XDM
4980{
4981 DBG_ITM_PRINT("CBasePlayer(%d)::QueueItem(%d)(id %d)\n", entindex(), pItem?pItem->entindex():0, pItem?pItem->GetID():0);
4982 if (pItem == nullptr)
4983 return;
4984
4985 ASSERT(pItem->GetID() != WEAPON_NONE);
4986#if defined (_DEBUG)
4987 if (pItem->GetHost() != this)
4988 {
4989 conprintf(1, "CBasePlayer(%d)::QueueItem(ei %d id %d) bad item owner!\n", entindex(), pItem->entindex(), pItem->GetID());
4990 DBG_FORCEBREAK
4991 }
4992#endif
4993 /* XDM3038: fixes fast last/next selection bug if (m_pActiveItem == nullptr)// no active weapon
4994 {
4995 m_pActiveItem = pItem;
4996 //return;// just set this item as active
4997 }
4998 else*/
4999
5000 m_pLastItem = m_pActiveItem;
5001 m_pActiveItem = nullptr;// clear current
5002 m_pNextItem = pItem;// add item to queue
5003
5004 if (m_pLastItem == nullptr)// XDM3038: there was no weapon
5005 m_flNextAttack = UTIL_WeaponTimeBase() + 0.05f;// XDM3038: pick up immediately
5006}
5007
5008//-----------------------------------------------------------------------------
5009// Purpose: Normal method to switch to specified item
5010// Input : *pItem -
5011// Output : Returns true on success, false on failure.
5012//-----------------------------------------------------------------------------
5013bool CBasePlayer::SelectItem(CBasePlayerItem *pItem)
5014{
5015 DBG_ITM_PRINT("CBasePlayer(%d)::SelectItem(%d)(id %d)\n", entindex(), pItem?pItem->entindex():0, pItem?pItem->GetID():0);
5016 if (pItem == nullptr)
5017 return false;
5018 if (!IsAlive())// XDM3038a
5019 return false;
5020 if (pItem->GetID() == WEAPON_NONE)// invalid pointer was passed!!
5021 {
5022 conprintf(1, "CBasePlayer(%d)::SelectItem() tried to select WEAPON_NONE!\n", entindex());
5023 DBG_FORCEBREAK;
5024 return false;
5025 }
5026 if (pItem == m_pNextItem)// XDM3038: already selecting this item, don't interrupt
5027 return true;// IMPORTANT
5028
5029#if defined (_DEBUG)
5030 if (pItem->GetHost() != this)
5031 {
5032 conprintf(1, "CBasePlayer(%d)::SelectItem(ei %d id %d) bad item owner!\n", entindex(), pItem->entindex(), pItem->GetID());
5033 DBG_FORCEBREAK
5034 pItem->SetOwner(this);// HACKFIX
5035 }
5036#endif
5037 if (!pItem->CanDeploy())// XDM
5038 return false;
5039
5040 if (m_pActiveItem)
5041 {
5042 ASSERT(m_pActiveItem->GetHost() == this);
5043 //m_pActiveItem->SetOwner(this);// HACKFIX
5044
5045 if (pItem == m_pActiveItem)// already selected
5046 return true;
5047
5048 if (!m_pActiveItem->IsHolstered() && !m_pActiveItem->CanHolster())// XDM3038
5049 return false;
5050 }
5051
5052 ResetAutoaim();
5053
5054 if (m_pActiveItem)
5055 {
5056 if (m_pActiveItem->IsHolstered())// XDM3038
5057 pItem->pev->impulse = ITEM_STATE_HOLSTERED;// No, we don't want any animations! pItem->Holster();// XDM3038a: this item must start in holstered state
5058 else
5059 m_pActiveItem->Holster();
5060 }
5061 QueueItem(pItem);// XDM
5062 DeployActiveItem();// XDM: QueueItem() sets m_pActiveItem if we have no current weapon
5063 return true;
5064}
5065
5066//-----------------------------------------------------------------------------
5067// Purpose: Select item by ID
5068// Input : iID -
5069// Output : Returns true on success, false on failure.
5070//-----------------------------------------------------------------------------
5071bool CBasePlayer::SelectItem(const int &iID)
5072{
5073//SPAM DBG_PLR_PRINT("CBasePlayer(%d)::SelectItem(%d)\n", entindex(), iID);
5074 if (iID <= WEAPON_NONE || iID >= PLAYER_INVENTORY_SIZE)// ??? allow WEAPON_NONE?
5075 return false;
5076
5077 CBasePlayerItem *pItem = GetInventoryItem(iID);
5078 if (pItem)// do we actually have item with this ID?
5079 return SelectItem(pItem);
5080
5081 return false;
5082}
5083
5084//-----------------------------------------------------------------------------
5085// Purpose: Select item by name
5086// Input : *pstr -
5087// Output : Returns true on success, false on failure.
5088//-----------------------------------------------------------------------------
5089bool CBasePlayer::SelectItem(const char *pstr)
5090{
5091 DBG_PLR_PRINT("CBasePlayer(%d)::SelectItem(\"%s\")\n", entindex(), pstr);
5092 if (pstr == nullptr)
5093 return false;
5094 if (!IsAlive())// XDM3038a
5095 return false;
5096
5097 CBasePlayerItem *pItem = nullptr;
5098 for (int i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
5099 {
5100 pItem = GetInventoryItem(i);
5101 if (pItem)
5102 {
5103 if (strcmp(pItem->GetWeaponName(), pstr) == 0)
5104 {
5105 return SelectItem(pItem);
5106 break;
5107 }
5108 }
5109 }
5110 return false;
5111}
5112
5113//-----------------------------------------------------------------------------
5114// Purpose: Select Next Best Item
5115// Warning: XDM3037: moved to client, a command is set back to the server
5116// Input : *pItem -
5117// Output : CBasePlayerItem
5118//-----------------------------------------------------------------------------
5119CBasePlayerItem *CBasePlayer::SelectNextBestItem(CBasePlayerItem *pItem)
5120{
5121 if (m_pActiveItem && !m_pActiveItem->CanHolster())
5122 return nullptr;// can't put this gun away right now, so can't switch.
5123 if (!IsAlive())// XDM3038a: dont't flood
5124 return nullptr;
5125
5126 DBG_ITM_PRINT("CBasePlayer(%d)::SelectNextBestItem(%d)\n", entindex(), pItem?pItem->GetID():WEAPON_NONE);
5127 if (HasWeapons())// XDM3038a
5128 {
5129 MESSAGE_BEGIN(MSG_ONE, gmsgSelBestItem, nullptr, edict());
5130 WRITE_BYTE(pItem?pItem->GetID():WEAPON_NONE);
5131 MESSAGE_END();
5132 }
5133 return pItem;//nullptr;?
5134}
5135
5136//-----------------------------------------------------------------------------
5137// Purpose: HasWeapons - do I have any weapons at all?
5138// Output : Returns true on success, false on failure.
5139//-----------------------------------------------------------------------------
5140bool CBasePlayer::HasWeapons(void)
5141{
5142 for (int i = 0; i < PLAYER_INVENTORY_SIZE; ++i)
5143 {
5144 if (GetInventoryItem(i))
5145 return true;
5146 }
5147 return false;
5148}
5149
5150//-----------------------------------------------------------------------------
5151// Purpose: XDM3037: allows future flexibility
5152// Output : Returns true on success, false on failure.
5153//-----------------------------------------------------------------------------
5154bool CBasePlayer::HasSuit(void)
5155{
5156 return FBitSet(pev->weapons, (1<<WEAPON_SUIT));// TODO: move suit to m_rgItems ?
5157}
5158
5159//-----------------------------------------------------------------------------
5160// Purpose: GiveNamedItem - gives player an item and DOES NOT let it drop into world
5161// Warning: BUGBUG UNDONE string allocation problem! Does ALLOC_STRING() have any overflow-proof mechanism?? UPD: no.
5162// Warning: BUGBUG UNDONE shittiest mechanism ever! We cannot track items like this!! We don't know if anything was really picked!!!!!!!!!!
5163// XDM3035: modified to return a pointer
5164// XDM3037: changed creation method and string allocation, but may it cause string overflow?? TODO TESTME
5165// XDM3038: modified to return boolean *sigh*
5166// NEW: SF_NOREFERENCE protects the game from being spammed with items added by game rules
5167// Input : *pszName - must reside in memory! Temporary variables are not allowed!
5168// Output : NULL if item was not created or accepted
5169//-----------------------------------------------------------------------------
5170CBaseEntity *CBasePlayer::GiveNamedItem(const char *pszName)
5171{
5172 DBG_PLR_PRINT("CBasePlayer(%d)::GiveNamedItem(%s)\n", entindex(), pszName);
5173 CBaseEntity *pEnt = Create(pszName, pev->origin, pev->angles, g_vecZero, nullptr, SF_DISPOSABLE | SF_NOREFERENCE | SF_NORESPAWN | SF_ITEM_NOFALL);// XDM3039: +disposable
5174 if (pEnt)
5175 {
5176 if (pEnt->IsPickup() && (g_pGameRules == nullptr || g_pGameRules->IsAllowedToSpawn(pEnt)))// XDM3038a: sanity/security check
5177 {
5178 SetBits(pEnt->pev->effects, EF_NODRAW);
5179 pEnt->m_flRemoveTime = gpGlobals->time + 0.002f;// XDM3038a: DANGEROUS! We rely upon that fact that internal code will reset this on pickup!
5180 pEnt->Touch(this);// UNDONE: TODO: make something that would track item existence!
5181
5182 //if (/*pEnt->pev->owner != edict() && */pEnt->m_hOwner != this)// XDM3038: is not owned/carried!
5183 if (pEnt->GetExistenceState() != ENTITY_EXSTATE_CARRIED)// XDM3038c: the most logical way to track entity existence
5184 {
5185 DBG_ITM_PRINT("CBasePlayer(%d)::GiveNamedItem(%s): exstate != CARRIED, removing.\n", entindex(), pszName);
5186 /*if (pEnt->pev->health > 0.0f)// Not marked for removal
5187 {
5188 conprintf(1, "GiveNamedItem(%s) ERROR: pEnt->m_hOwner != this but health > 0!\n", pszName);
5189 DBG_FORCEBREAK
5190 }*/
5191 // Force these flags to be 200% sure
5192 SetBits(pEnt->pev->effects, EF_NODRAW);
5193 SetBits(pEnt->pev->spawnflags, SF_DISPOSABLE|SF_NORESPAWN);// XDM3039: added DISPOSABLE flag
5194 SetBits(pEnt->pev->flags, FL_KILLME);// may be already removed by this moment, so use safe method!
5195 pEnt->SetTouchNull();
5196 pEnt->m_flRemoveTime = gpGlobals->time;// let's see who gets to it first
5197 //pEnt->Destroy();// XDM3038: unsafe?
5198 }
5199 else
5200 return pEnt;
5201 }
5202 else// no illegal stuff allowed
5203 {
5204 DBG_ITM_PRINT("CBasePlayer(%d)::GiveNamedItem(%s): not allowed, removing.\n", entindex(), pszName);
5205 SetBits(pEnt->pev->effects, EF_NODRAW);
5206 SetBits(pEnt->pev->spawnflags, SF_NORESPAWN);
5207 SetBits(pEnt->pev->flags, FL_KILLME);// use safe method!
5208 pEnt->SetTouchNull();
5209 pEnt->m_flRemoveTime = gpGlobals->time;
5210 }
5211 }
5212 return nullptr;
5213}
5214
5215//-----------------------------------------------------------------------------
5216// Purpose: Freeze grenade
5217// Input : freezetime -
5218//-----------------------------------------------------------------------------
5219void CBasePlayer::FrozenStart(float freezetime)
5220{
5221 if (!IsAlive())
5222 return;
5223 DBG_PLR_PRINT("CBasePlayer(%d)::FrozenStart(%g)\n", entindex(), freezetime);
5224
5225 pev->movetype = MOVETYPE_TOSS;
5226 pev->fixangle = 1;
5227
5228 CBaseMonster::FrozenStart(freezetime);// this sets rendercolor and stops burning
5229
5230 m_flNextAttack = UTIL_WeaponTimeBase() + freezetime;// override for player
5231 if (m_pActiveItem)// XDM: crash prevention
5232 {
5233 CBasePlayerWeapon *pWeapon = m_pActiveItem->GetWeaponPtr();
5234 if (pWeapon)
5235 {
5236 ASSERT(pWeapon->GetHost() != nullptr);
5237 pWeapon->m_flNextPrimaryAttack = pWeapon->m_flNextSecondaryAttack = m_flNextAttack;
5238 pWeapon->m_flTimeWeaponIdle = UTIL_WeaponTimeBase();
5239 pWeapon->WeaponIdle();// Force TESTME!!
5240 }
5241 }
5242
5243 //TODO: reduce pev->maxspeed?
5244 DisableLadder(freezetime);// XDM3037
5245
5246 //if (!m_fFrozen)// UNDONE: if already frozen
5247 //CL UTIL_ScreenFade(this, pev->rendercolor, 1.0, 1.0, 127, FFADE_OUT | FFADE_STAYOUT);
5248}
5249
5250//-----------------------------------------------------------------------------
5251// Purpose: Frozen period is ending
5252//-----------------------------------------------------------------------------
5253void CBasePlayer::FrozenEnd(void)
5254{
5255 DBG_PLR_PRINT("CBasePlayer(%d)::FrozenEnd()\n", entindex());
5256 pev->movetype = MOVETYPE_WALK;
5257 pev->fixangle = FALSE;
5258 CBaseMonster::FrozenEnd();
5259 m_flNextAttack = UTIL_WeaponTimeBase();
5260 DisableLadder(0.0);// XDM3038: re-enable
5261
5262 if (m_pActiveItem)// XDM: crash prevention
5263 {
5264 CBasePlayerWeapon *pWeapon = m_pActiveItem->GetWeaponPtr();
5265 if (pWeapon != nullptr)
5266 pWeapon->m_flNextPrimaryAttack = pWeapon->m_flNextSecondaryAttack = m_flNextAttack;
5267 }
5268 UTIL_ScreenFade(this, Vector(63,143,255)/*pev->rendercolor*/, 4, 0, 128, FFADE_IN);// XDM3037: FIXME
5269}
5270
5271//-----------------------------------------------------------------------------
5272// Purpose: called every frame while frozen (freeze grenade)
5273//-----------------------------------------------------------------------------
5274void CBasePlayer::FrozenThink(void)
5275{
5276 // should be already checked if (!m_fFrozen)
5277 // return;
5278
5279 if (m_flUnfreezeTime > 0.0f)
5280 {
5281 if (m_flUnfreezeTime <= gpGlobals->time)
5282 FrozenEnd();// call this before CBaseMonster
5283 else
5284 CBaseMonster::FrozenThink();
5285 }
5286}
5287
5288//-----------------------------------------------------------------------------
5289// Purpose: What force should be applied to this entity upon taking damage
5290// Input : damage -
5291// Output : float
5292//-----------------------------------------------------------------------------
5293float CBasePlayer::DamageForce(const float &damage)
5294{
5295 return PLAYER_DAMAGE_FORCE_MULTIPLIER*CBaseEntity::DamageForce(damage);
5296}
5297
5298//-----------------------------------------------------------------------------
5299// Purpose: Enable client controls (movement, etc.)
5300// Input : fControl -
5301//-----------------------------------------------------------------------------
5302void CBasePlayer::EnableControl(bool fControl)
5303{
5304 DBG_PLR_PRINT("CBasePlayer(%d)::EnableControl(%d)\n", entindex(), fControl);
5305 if (fControl)
5306 {
5307 ClearBits(pev->flags, FL_FROZEN);
5308 pev->view_ofs = VEC_VIEW_OFFSET;// XDM3038: HACK! view_ofs is 0 after SET_VIEW!
5309 }
5310 else
5311 SetBits(pev->flags, FL_FROZEN);
5312}
5313
5314//-----------------------------------------------------------------------------
5315// Purpose: Is this player on ladder?
5316// Output : Returns true if the player is attached to a ladder
5317//-----------------------------------------------------------------------------
5318bool CBasePlayer::IsOnLadder(void)
5319{
5320 return (pev->movetype == MOVETYPE_FLY);
5321}
5322
5323//-----------------------------------------------------------------------------
5324// Purpose: Disable use of ladders (just fall)
5325// Input : time - for this amount of time (0 to reset)
5326//-----------------------------------------------------------------------------
5327void CBasePlayer::DisableLadder(const float &time)
5328{
5329 DBG_PLR_PRINT("CBasePlayer(%d)::DisableLadder(%g)\n", entindex(), time);
5330 if (time > 0.0f)
5331 {
5332 pev->movetype = MOVETYPE_WALK;
5333 ENGINE_SETPHYSKV(edict(), PHYSKEY_IGNORELADDER, "1");
5334 m_flIgnoreLadderStopTime = max(m_flIgnoreLadderStopTime, gpGlobals->time + time);// XDM3037: don't add up, repalce with longest interval
5335 }
5336 else
5337 {
5338 m_flIgnoreLadderStopTime = gpGlobals->time;// just let it end normally
5339 //ENGINE_SETPHYSKV(edict(), PHYSKEY_IGNORELADDER, "0");
5340 //m_flIgnoreLadderStopTime = 0.0f;
5341 }
5342}
5343
5344//-----------------------------------------------------------------------------
5345// Purpose: Is this player on train?
5346// Output : Returns true on success, false on failure.
5347//-----------------------------------------------------------------------------
5348bool CBasePlayer::IsOnTrain(void)// XDM3035
5349{
5350 if (FBitSet(m_afPhysicsFlags, PFLAG_ONTRAIN))
5351 return true;
5352 //if (m_pTrain)// OR or AND?
5353 // return TRUE;
5354 //if (m_iTrain & TRAIN_ACTIVE)
5355 // return TRUE;
5356 // THIS DESTROYS LOGIC! if (pev->flags & FL_ONTRAIN)
5357 // return TRUE;
5358
5359 return false;
5360}
5361
5362//-----------------------------------------------------------------------------
5363// Purpose: Attach player to a train controls
5364// Input : *pTrain -
5365// Output : Returns true on success, false on failure.
5366//-----------------------------------------------------------------------------
5367bool CBasePlayer::TrainAttach(CBaseEntity *pTrain)
5368{
5369 DBG_PLR_PRINT("CBasePlayer(%d)::TrainAttach()\n", entindex());
5370 if (pTrain == nullptr)
5371 return false;
5372
5373 CBaseDelay *pRealTrain = (CBaseDelay *)pTrain;// XDM3035: HACKHACKHACK!!!!!!
5374
5375 if (pRealTrain && pRealTrain->IsLockedByMaster(this))// XDM3037: disabled train
5376 return false;
5377
5378 // old if ((pTrain->pev->euser1 != nullptr) ||
5379 if (pRealTrain->m_hActivator != nullptr && pRealTrain->m_hActivator != this)
5380 {
5381 ClientPrint(pev, HUD_PRINTCENTER, "#TRAIN_OCC", STRING(pRealTrain->m_hActivator->pev->netname));
5382 if (sv_allowstealvehicles.value == 0.0f)// XDM3038: :)
5383 return false;
5384 }
5385
5386 /* XDM3038: just update anyway if (IsOnTrain())// already using
5387 {
5388 //if (pTrain->pev != m_pTrain->pev)// ANOTHER train
5389 // if (!TrainDetach())// which I failed to detach from
5390 return false;// so don't bother
5391 }*/
5392
5393 SetBits(m_afPhysicsFlags, PFLAG_ONTRAIN);
5394 m_iTrain = TrainSpeed((int)pTrain->pev->speed, pTrain->pev->maxspeed);// XDM3038a
5395 SetBits(m_iTrain, TRAIN_NEW);
5396 // old pTrain->pev->euser1 = edict();// become attacker in multiplayer
5397 pRealTrain->m_hActivator = this;// XDM3035: HACKHACKHACK!!!!!!
5398 m_pTrain = pTrain;
5399 //SetBits(pev->flags, FL_ONTRAIN);
5400#if defined (_DEBUG)
5401 conprintf(2, "CBasePlayer::TrainAttach() successful\n");
5402#endif
5403 return true;
5404}
5405
5406//-----------------------------------------------------------------------------
5407// Purpose: Detach player from train controls
5408// Output : Returns true on success, false on failure.
5409//-----------------------------------------------------------------------------
5410bool CBasePlayer::TrainDetach(void)
5411{
5412 DBG_PLR_PRINT("CBasePlayer(%d)::TrainDetach()\n", entindex());
5413 if (m_pTrain)
5414 {
5415 CBaseDelay *pRealTrain = GetControlledTrain();// get this FIRST, while it is valid!
5416 if (pRealTrain)// May get NULL!
5417 {
5418 if (pRealTrain->m_hActivator == this)// new
5419 {
5420 pRealTrain->Use(this, this, USE_OFF, 0);// XDM3038: A MUST!
5421 //pRealTrain->m_hActivator = nullptr;
5422 }
5423 }
5424 //if (m_pTrain->pev->euser1 == edict())
5425 // m_pTrain->pev->euser1 = nullptr;
5426
5427 m_pTrain = nullptr;
5428#if defined (_DEBUG)
5429 conprintf(2, "CBasePlayer::TrainDetach() successful\n");
5430#endif
5431 }
5432 ClearBits(m_afPhysicsFlags, PFLAG_ONTRAIN);
5433 m_iTrain = TRAIN_OFF|TRAIN_NEW;
5434 return true;
5435}
5436
5437//-----------------------------------------------------------------------------
5438// Purpose: For external use
5439// Output : CBaseDelay
5440//-----------------------------------------------------------------------------
5441CBaseDelay *CBasePlayer::GetControlledTrain(void)
5442{
5443 if (m_pTrain.Get())
5444 {
5445 CBaseDelay *pTrain = (CBaseDelay *)(CBaseEntity *)m_pTrain;
5446 // in CoOp this is not true ASSERT(pTrain->m_hActivator == this);
5447 return pTrain;// XDM3037: ...
5448 }
5449 return nullptr;
5450}
5451
5452//-----------------------------------------------------------------------------
5453// Purpose: PreThink OnTrain part
5454//-----------------------------------------------------------------------------
5455void CBasePlayer::TrainPreFrame(void)
5456{
5457 // Train speed control
5458 if (IsOnTrain())//m_afPhysicsFlags & PFLAG_ONTRAIN)
5459 {
5460 // So the correct flags get sent to client asap.
5461 SetBits(pev->flags, FL_ONTRAIN);
5462 CBaseEntity *pTrain = (CBaseEntity *)m_pTrain;// XDM3035b
5463 if (pTrain == nullptr)
5464 {
5465 conprintf(2, "TrainPreFrame(): client %d: acquiring train from ground entity.\n", entindex());
5466 pTrain = CBaseEntity::Instance(pev->groundentity);
5467 TrainAttach(pTrain);// somehow it works
5468 }
5469 if (pTrain == nullptr)
5470 {
5471 TraceResult trainTrace;
5472 // Maybe this is on the other side of a level transition
5473 UTIL_TraceLine(pev->origin, pev->origin + Vector(0.0f,0.0f,HULL_MIN-2.0f), ignore_monsters, edict(), &trainTrace);
5474 if (trainTrace.flFraction != 1.0 && trainTrace.pHit)
5475 pTrain = CBaseEntity::Instance(trainTrace.pHit);
5476
5477 if (pTrain == nullptr || !FBitSet(pTrain->ObjectCaps(), FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev))
5478 {
5479 conprintf(2, "TrainPreFrame() error: client %d: in train mode with no train!\n", entindex());
5480 TrainDetach();// XDM3035b
5481 }
5482 }
5483 else if (!FBitSet(pev->flags, FL_ONGROUND) || FBitSet(pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL) || FBitSet(pev->button, IN_MOVELEFT|IN_MOVERIGHT))
5484 {
5485 // Turn off the train if you jump, strafe, or the train controls go dead
5486 TrainDetach();// XDM3035b
5487 }
5488
5489 //if (IsOnTrain())// still on train?
5490 if (FBitSet(m_afPhysicsFlags, PFLAG_ONTRAIN) && pTrain)// XDM3035: removed returns;
5491 {
5492 float vel = 0.0f;
5493 pev->velocity.Clear();
5494 if (FBitSet(m_afButtonPressed, IN_FORWARD))
5495 {
5496 vel = 1.0f;
5497 pTrain->Use(this, this, USE_SET, vel);
5498 }
5499 else if (FBitSet(m_afButtonPressed, IN_BACK))
5500 {
5501 vel = -1.0f;
5502 pTrain->Use(this, this, USE_SET, vel);
5503 }
5504
5505 if (vel != 0.0f || m_fInitHUD)// XDM3038: update train when updating hud, too
5506 {
5507 m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->maxspeed);
5508 SetBits(m_iTrain, TRAIN_ACTIVE|TRAIN_NEW);
5509 }
5510 }
5511 }
5512 else
5513 {
5514 ClearBits(pev->flags, FL_ONTRAIN);
5515 if (FBitSet(m_iTrain, TRAIN_ACTIVE))
5516 m_iTrain = TRAIN_OFF|TRAIN_NEW; // turn off train
5517 }
5518}
5519
5520//-----------------------------------------------------------------------------
5521// Purpose: Get team player belonged to before becoming spectator
5522// Output : TEAM_ID
5523//-----------------------------------------------------------------------------
5524TEAM_ID CBasePlayer::GetLastTeam(void)
5525{
5526 return pev->team;// playerclass; XDM3038a: OBSOLETE
5527}
5528
5529//-----------------------------------------------------------------------------
5530// Purpose: CoOp: player reached and touched some checkpoint (trigger_autosave)
5531// Warning: Order is important! Last touched must be last in the list!
5532// Input : *pCheckPoint -
5533//-----------------------------------------------------------------------------
5534void CBasePlayer::OnCheckPoint(CBaseEntity *pCheckPoint)
5535{
5536 if (!ASSERT(pCheckPoint != nullptr))
5537 return;
5538
5539 DBG_PLR_PRINT("CBasePlayer(%d)::OnCheckPoint()\n", entindex());
5540 //if (m_iLastCheckPointIndex != pCheckPoint->entindex())
5541 //if (!PassedCheckPoint(pCheckPoint))
5542 size_t i;
5543 size_t iEmpty = MAX_CHECKPOINTS;
5544 for (i=0; i<MAX_CHECKPOINTS; ++i)
5545 {
5546 if (m_hszCheckPoints[i] == pCheckPoint)// already touched
5547 return;
5548 else if (m_hszCheckPoints[i].Get() == nullptr && iEmpty == MAX_CHECKPOINTS)// empty slot and we need it
5549 iEmpty = i;
5550 }
5551 if (iEmpty >= MAX_CHECKPOINTS)
5552 {
5553 conprintf(2, "WARNING: Client %d: has too many registered checkpoints!\n", entindex());
5554 iEmpty = 0;// TESTME: register anyway...
5555 }
5556 m_hszCheckPoints[iEmpty] = pCheckPoint;
5557 m_Stats[STAT_CHECK_POINT_COUNT]++;// XDM3037
5558 if (g_pGameRules)
5559 g_pGameRules->OnPlayerCheckPoint(this, pCheckPoint);
5560}
5561
5562//-----------------------------------------------------------------------------
5563// Purpose: CoOp: has this player ever touched this checkpoint? (trigger_autosave)
5564// Input : *pCheckPoint -
5565// Output : Returns true on success, false on failure.
5566//-----------------------------------------------------------------------------
5567bool CBasePlayer::PassedCheckPoint(CBaseEntity *pCheckPoint)
5568{
5569 if (!ASSERT(pCheckPoint != nullptr))
5570 return false;
5571
5572 for (size_t i=0; i<MAX_CHECKPOINTS; ++i)// array MUST BE CLEARED OUT before using this!
5573 {
5574 if (m_hszCheckPoints[i].Get() && m_hszCheckPoints[i] == pCheckPoint)
5575 return true;
5576 }
5577 return false;
5578}
5579
5580//-----------------------------------------------------------------------------
5581// Purpose: CoOp: get last checkpoint this player touched (trigger_autosave)
5582// Warning: No checks on touch time is done! We rely solely on element order!!!
5583// Output : CBaseEntity *pCheckPoint
5584//-----------------------------------------------------------------------------
5585CBaseEntity *CBasePlayer::GetLastCheckPoint(void)
5586{
5587 size_t i = MAX_CHECKPOINTS;
5588 do// 'for' won't do any good with unsigned values
5589 {
5590 --i;
5591 if (m_hszCheckPoints[i].Get())
5592 return m_hszCheckPoints[i];
5593 } while (i > 0);
5594 return nullptr;
5595}
5596
5597//-----------------------------------------------------------------------------
5598// Purpose: safely clear out array
5599//-----------------------------------------------------------------------------
5600void CBasePlayer::CheckPointsClear(void)
5601{
5602 for (size_t i=0; i<MAX_CHECKPOINTS; ++i)
5603 m_hszCheckPoints[i] = nullptr;
5604
5605 m_vecLastSpawnOrigin.Clear();
5606 m_vecLastSpawnAngles.Clear();
5607 m_iLastSpawnFlags = 0;
5608}
5609
5610//-----------------------------------------------------------------------------
5611// Purpose: User rights
5612// Input : iRightIndex -
5613// Output : Returns true on success, false on failure.
5614//-----------------------------------------------------------------------------
5615bool CBasePlayer::RightsCheck(uint32 iRightIndex)
5616{
5617 return FBitSet(m_iUserRights, 1U << iRightIndex);
5618}
5619
5620//-----------------------------------------------------------------------------
5621// Purpose: User rights - to check multiple rights at once
5622// Input : iRightBits - == (1 << iRightIndex)
5623// Output : Returns true on success, false on failure.
5624//-----------------------------------------------------------------------------
5625bool CBasePlayer::RightsCheckBits(uint32 iRightBits)
5626{
5627 return FBitSet(m_iUserRights, iRightBits);
5628}
5629
5630//-----------------------------------------------------------------------------
5631// Purpose: Add user rights
5632// Input : iRightIndex -
5633//-----------------------------------------------------------------------------
5634void CBasePlayer::RightsAssign(uint32 iRightIndex)
5635{
5636 if (!RightsCheck(iRightIndex))// don't inform server if nothing changes
5637 {
5638 SetBits(m_iUserRights, 1 << iRightIndex);
5639 char *infobuffer = GET_INFO_KEY_BUFFER(edict());// XDM3039: add to userinfo so it will be visible in "user" command output (read only)
5640 char value[16];
5641 _snprintf(value, 16, "%u", m_iUserRights);
5642 SET_CLIENT_KEY_VALUE(entindex(), infobuffer, "rights", value);
5643 UTIL_LogPrintf("\"%s<%i><%s><%s>\" gained rights: \"%s\"\n", STRING(pev->netname), GETPLAYERUSERID(edict()), GETPLAYERAUTHID(edict()), GET_INFO_KEY_VALUE(infobuffer, "team"), g_UserRightsNames[iRightIndex]);
5644 }
5645}
5646
5647//-----------------------------------------------------------------------------
5648// Purpose: Remove user rights
5649// Input : iRightIndex -
5650//-----------------------------------------------------------------------------
5651void CBasePlayer::RightsRemove(uint32 iRightIndex)
5652{
5653 if (RightsCheck(iRightIndex))// don't inform server if nothing changes
5654 {
5655 ClearBits(m_iUserRights, 1 << iRightIndex);
5656 char *infobuffer = GET_INFO_KEY_BUFFER(edict());// XDM3039
5657 char value[16];
5658 _snprintf(value, 16, "%u", m_iUserRights);
5659 SET_CLIENT_KEY_VALUE(entindex(), infobuffer, "rights", value);
5660 UTIL_LogPrintf("\"%s<%i><%s><%s>\" lost rights: \"%s\"\n", STRING(pev->netname), GETPLAYERUSERID(edict()), GETPLAYERAUTHID(edict()), GET_INFO_KEY_VALUE(infobuffer, "team"), g_UserRightsNames[iRightIndex]);
5661 }
5662}
5663
5664//-----------------------------------------------------------------------------
5665// Purpose: UNDONE TODO: we ONLY need to restore player's CURRENT stats in case of sudden disconnect & rejoin
5666// Output : Returns true on success, false on failure.
5667//-----------------------------------------------------------------------------
5668bool CBasePlayer::StatsLoad(void)
5669{
5670 //const char *pID = GETPLAYERAUTHID(edict());
5671 //start_read_file()
5672 //if (file.stattime_and_matchID_and_mapname == current)
5673 //load_from_file("stats/%s.txt", pID)
5674 return true;
5675}
5676
5677//-----------------------------------------------------------------------------
5678// Purpose: TODO: write stats with by Unique ID to a file so it can be restored on reconnect
5679// Output : Returns true on success, false on failure.
5680//-----------------------------------------------------------------------------
5681bool CBasePlayer::StatsSave(void)
5682{
5683 // merge stats with player's GLOBAL stats collected over years on this server?
5684 //const char *pID = GETPLAYERAUTHID(edict());
5685 //merge_from_file("stats/%s.txt", pID)
5686 //save_to_file("stats/%s.txt", pID)
5687 return true;
5688}
5689
5690//-----------------------------------------------------------------------------
5691// Purpose: completely wipe out statistics
5692//-----------------------------------------------------------------------------
5693void CBasePlayer::StatsClear(void)
5694{
5695 memset(m_Stats, 0, sizeof(m_Stats));
5696}
5697
5698//-----------------------------------------------------------------------------
5699// Purpose: XDM3038c: Print current important state parameters.
5700// Warning: Should be accumulative across subclasses.
5701// Warning: Each subclass should first call MyParent::ReportState()
5702// Input : printlevel -
5703//-----------------------------------------------------------------------------
5704void CBasePlayer::ReportState(int printlevel)
5705{
5706 CBaseMonster::ReportState(printlevel);
5707 conprintf(printlevel, "NetName: %s, UserRights: %u, Deaths %u, PhysicsFlags %u, AnimExtention: %s\n", STRING(pev->netname), m_iUserRights, m_iDeaths, m_afPhysicsFlags, m_szAnimExtention);
5708 /* Dumping inventory:\n
5709 for (int i = WEAPON_NONE; i < PLAYER_INVENTORY_SIZE; ++i)
5710 {
5711 CBasePlayerItem *pItem = pTargetPlayer->GetInventoryItem(i);
5712 if (pItem)
5713 pItem->ReportState(printlevel);
5714 }*/
5715}
5716
5717//-----------------------------------------------------------------------------
5718// Purpose: Universal, for internal and external APIs
5719// Input : *pTrackName - track/playlist
5720// iCommand - MUSICPLAYER_CMD enum
5721// fTimeOffset - offset in seconds
5722//-----------------------------------------------------------------------------
5723void CBasePlayer::PlayAudioTrack(const char *pTrackName, int iCommand, const float &fTimeOffset)
5724{
5725 DBG_PRINTF("PlayAudioTrack(\"%s\", cmd %d, ofs %g)\n", pTrackName, iCommand, fTimeOffset);
5726 if ((iCommand == MPSVCMD_PLAYTRACK || iCommand == MPSVCMD_PLAYTRACKLOOP) && (pTrackName == nullptr || pTrackName[0] == '\0'))// some commands are allowed without name
5727 {
5728 ClientPrintF(pev, HUD_PRINTCONSOLE, 2, "SV: PlayAudioTrack(\"%s\", cmd %d, ofs %g) error: no track specified!\n", pTrackName, iCommand, fTimeOffset);
5729 return;
5730 }
5731 else
5732 ClientPrintF(pev, HUD_PRINTCONSOLE, 2, "SV: PlayAudioTrack(\"%s\", cmd %d, ofs %g)\n", pTrackName, iCommand, fTimeOffset);
5733
5734 if (iCommand == MPSVCMD_RESET)// normally this should not be used
5735 {
5736 m_szCurrentMusicTrack[0] = '\0';
5737 m_iCurrentMusicTrackStartTime = gpGlobals->time;
5738 }
5739 else// a real sendable command
5740 {
5741 if ((pTrackName == nullptr || pTrackName[0] == '\0'))// perfectly acceptable!
5742 m_szCurrentMusicTrack[0] = '\0';
5743 else
5744 {
5745 strcpy(m_szCurrentMusicTrack, pTrackName);
5746 m_szCurrentMusicTrack[MAX_ENTITY_STRING_LENGTH-1] = '\0';
5747 }
5748 m_iCurrentMusicTrackCommand = iCommand;
5749 m_iCurrentMusicTrackStartTime = gpGlobals->time - fTimeOffset;// pretend we started playing somewhen in the past :)
5750 }
5751 if (fTimeOffset == 0.0f)
5752 m_iCurrentMusicTrackStartTime += 0.05f;// delay a little bit
5753
5754 m_iCurrentMusicTrackCommandIssued = 0;
5755 // ASYNC: message will be sent in UpdateClientData
5756}