/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// player.h: Class definition of the player.

#include "g_local.h"
#include "bg_local.h"
#include "g_spawn.h"
#include "g_phys.h"
#include "entity.h"
#include "consoleevent.h"
#include "player.h"
#include "worldspawn.h"
#include "weapon.h"
#include "trigger.h"
#include "scriptmaster.h"
#include "scriptexception.h"
#include "navigate.h"
#include "misc.h"
#include "earthquake.h"
#include "gravpath.h"
#include "armor.h"
#include "inventoryitem.h"
#include "gibs.h"
#include "actor.h"
#include "object.h"
#include "characterstate.h"
#include "weaputils.h"
#include "dm_manager.h"
#include "parm.h"
#include "body.h"
#include "playerstart.h"
#include "camera.h"
#include "weapturret.h"
#include "vehicleturret.h"
#include "portableturret.h"
#include "fixedturret.h"

const Vector power_color(0.0, 1.0, 0.0);
const Vector acolor(1.0, 1.0, 1.0);
const Vector bcolor(1.0, 0.0, 0.0);

//
// mohaas 2.0 and above
//
const char *pInstantMsgEng[6][9] = {
    {"Good job team!",
     "Alright!", "We've done it!",
     "Wooohoo!", "Objective achieved.",
     "We've completed an objective.", "We've lost an objective!",
     "The enemy has overrun our objective!", NULL},
    {"Squad, move in!",
     "Squad, fall back!", "Squad, attack right flank!",
     "Squad, attack left flank!", "Squad, hold this position!",
     "Squad, covering fire!", "Squad, regroup!",
     "Squad, split up!", NULL},
    {"Cover me!",
     "I'll cover you!", "Follow me!",
     "You take point.", "Taking Fire!  Need some help!",
     "Get ready to move in on my signal.", "Attack!",
     "Open fire!", NULL},
    {"Yes sir!",
     "No sir!", "Enemy Spotted.",
     "Sniper!", "Grenade! Take Cover!",
     "Area Clear.", "Thanks.",
     "I owe you one.", NULL},
    {"Who wants more?!",
     "Never send boys to do a man's job.", "This is too easy!",
     "You mess with the best, you die like the rest.", "Watch that friendly fire!",
     "Hey!  I'm on your team!", "Come on out you cowards!",
     "Where are you hiding?", NULL},
    // Added in 2.30
    {"Guard our jail!",
     "Capture the enemy jail!", "I'm defending our jail!",
     "I'm attacking the enemy jail!", "Rescue the Prisoners!",
     "The enemy is attacking our jail!"}
};

//
// for mohaa version 1.11 and below
//
const char *pInstantMsgEng_ver6[5][9] = {
    {"Squad, move in!",
     "Squad, fall back!", "Squad, attack right flank!",
     "Squad, attack left flank!", "Squad, hold this position!",
     "Squad, covering fire!", "Squad, regroup!",
     "", ""},
    {
     "Cover me!", "I'll cover you!",
     "Follow me!", "You take point.",
     "You take the lead.", "Taking Fire!  Need some help!",
     "Charge!", "Attack!",
     "Open fire!", },
    {
     "Yes sir!", "No sir!",
     "Enemy Spotted.", "Sniper!",
     "Grenade! Take Cover!", "Area Clear.",
     "Great Shot!", "Thanks.",
     "I owe you one.", },
    {
     "Is that all you've got?", "I think they are all out of real men!",
     "Go on and run, you yellow-bellies!", "They're a bunch of cowards!",
     "Come back when you've had some target practice!", "Come prepared next time!",
     "Try again!", "I've seen French school girls shoot better!",
     "That made a mess.", },
    {"He's going to get us killed!",
     "A lot of good men are going to die because of his poor leadership", "Good riddance!",
     "That guy is going to get us all killed!", "Hey buddy, get down!",
     "Stay out of my foxhole, pal!", "Find your own hiding place!",
     "Get out of my way!", ""}
};

Event EV_Player_DumpState
(
    "state",
    EV_CHEAT,
    NULL,
    NULL,
    "Dumps the player's state to the console.",
    EV_NORMAL
);
Event EV_Player_ForceTorsoState
(
    "forcetorsostate",
    EV_DEFAULT,
    "s",
    "torsostate",
    "Force the player's torso to a certain state",
    EV_NORMAL
);
Event EV_Player_ForceLegsState
(
    "forcelegsstate",
    EV_DEFAULT,
    "s",
    "legsstate",
    "Force the player's legs to a certain state",
    EV_NORMAL
);
Event EV_Player_GiveAllCheat
(
    "wuss",
    EV_CONSOLE | EV_CHEAT,
    NULL,
    NULL,
    "Gives player all weapons.",
    EV_NORMAL
);
Event EV_Player_GiveNewWeaponsCheat
(
    "giveweapon",
    EV_CONSOLE | EV_CHEAT,
    "s",
    "weapon_name",
    "Gives player all weapons.",
    EV_NORMAL
);
Event EV_Player_EndLevel
(
    "endlevel",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the player gets to the end of the level.",
    EV_NORMAL
);
Event EV_Player_DevGodCheat
(
    "dog",
    EV_CHEAT | EV_CONSOLE,
    "I",
    "god_mode",
    "Sets the god mode cheat or toggles it.",
    EV_NORMAL
);
Event EV_Player_FullHeal
(
    "fullheal",
    EV_CHEAT | EV_CONSOLE,
    NULL,
    NULL,
    "Heals player.",
    EV_NORMAL
);
Event EV_Player_DevNoTargetCheat
(
    "notarget",
    EV_CHEAT,
    NULL,
    NULL,
    "Toggles the notarget cheat.",
    EV_NORMAL
);
Event EV_Player_DevNoClipCheat
(
    "noclip",
    EV_CHEAT | EV_CONSOLE,
    NULL,
    NULL,
    "Toggles the noclip cheat.",
    EV_NORMAL
);
Event EV_Player_PrevItem
(
    "invprev",
    EV_CONSOLE,
    NULL,
    NULL,
    "Cycle to player's previous item.",
    EV_NORMAL
);
Event EV_Player_NextItem
(
    "invnext",
    EV_CONSOLE,
    NULL,
    NULL,
    "Cycle to player's next item.",
    EV_NORMAL
);
Event EV_Player_PrevWeapon
(
    "weapprev",
    EV_CONSOLE,
    NULL,
    NULL,
    "Cycle to player's previous weapon.",
    EV_NORMAL
);
Event EV_Player_NextWeapon
(
    "weapnext",
    EV_CONSOLE,
    NULL,
    NULL,
    "Cycle to player's next weapon.",
    EV_NORMAL
);
Event EV_Player_DropWeapon
(
    "weapdrop",
    EV_CONSOLE,
    NULL,
    NULL,
    "Drops the player's current weapon.",
    EV_NORMAL
);
Event EV_Player_Reload
(
    "reload",
    EV_CONSOLE,
    NULL,
    NULL,
    "Reloads the player's weapon",
    EV_NORMAL
);
Event EV_Player_CorrectWeaponAttachments
(
    "correctweaponattachments",
    EV_CONSOLE,
    NULL,
    NULL,
    "makes sure the weapons is properly attached when interrupting a reload",
    EV_NORMAL
);
Event EV_Player_GiveCheat
(
    "give",
    EV_CONSOLE | EV_CHEAT,
    "sI",
    "name amount",
    "Gives the player the specified thing (weapon, ammo, item, etc.) and optionally the amount.",
    EV_NORMAL
);
Event EV_Player_GiveWeaponCheat
(
    "giveweapon",
    EV_CONSOLE | EV_CHEAT,
    "s",
    "weapon_name",
    "Gives the player the specified weapon.",
    EV_NORMAL
);
Event EV_Player_GameVersion
(
    "gameversion",
    EV_CONSOLE,
    NULL,
    NULL,
    "Prints the game version.",
    EV_NORMAL
);
Event EV_Player_Fov
(
    "fov",
    EV_CONSOLE,
    "F",
    "fov",
    "Sets the fov.",
    EV_NORMAL
);
Event EV_Player_Dead
(
    "dead",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the player is dead.",
    EV_NORMAL
);
Event EV_Player_SpawnEntity
(
    "spawn",
    EV_CHEAT,
    "sSSSSSSSS",
    "entityname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4",
    "Spawns an entity.",
    EV_NORMAL
);
Event EV_Player_SpawnActor
(
    "actor",
    EV_CHEAT,
    "sSSSSSSSS",
    "modelname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4",
    "Spawns an actor.",
    EV_NORMAL
);
Event EV_Player_Respawn
(
    "respawn",
    EV_DEFAULT,
    NULL,
    NULL,
    "Respawns the player.",
    EV_NORMAL
);
Event EV_Player_TestThread
(
    "testthread",
    EV_CHEAT,
    "sS",
    "scriptfile label",
    "Starts the named thread at label if provided.",
    EV_NORMAL
);
Event EV_Player_PowerupTimer
(
    "poweruptimer",
    EV_DEFAULT,
    "ii",
    "poweruptimer poweruptype",
    "Sets the powerup timer and powerup type.",
    EV_NORMAL
);
Event EV_Player_UpdatePowerupTimer
(
    "updatepoweruptime",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called once a second to decrement powerup time.",
    EV_NORMAL
);
Event EV_Player_ResetState
(
    "resetstate",
    EV_CHEAT,
    NULL,
    NULL,
    "Reset the player's state table.",
    EV_NORMAL
);
Event EV_Player_WhatIs
(
    "whatis",
    EV_CHEAT,
    "i",
    "entity_number",
    "Prints info on the specified entity.",
    EV_NORMAL
);
Event EV_Player_ActorInfo
(
    "actorinfo",
    EV_CHEAT,
    "i",
    "actor_number",
    "Prints info on the specified actor.",
    EV_NORMAL
);
Event EV_Player_KillEnt
(
    "killent",
    EV_CHEAT,
    "i",
    "entity_number",
    "Kills the specified entity.",
    EV_NORMAL
);
Event EV_Player_KillClass
(
    "killclass",
    EV_CHEAT,
    "sI",
    "classname except_entity_number",
    "Kills all of the entities in the specified class.",
    EV_NORMAL
);
Event EV_Player_RemoveEnt
(
    "removeent",
    EV_CHEAT,
    "i",
    "entity_number",
    "Removes the specified entity.",
    EV_NORMAL
);
Event EV_Player_RemoveClass
(
    "removeclass",
    EV_CHEAT,
    "sI",
    "classname except_entity_number",
    "Removes all of the entities in the specified class.",
    EV_NORMAL
);
Event EV_Player_Jump
(
    "jump",
    EV_DEFAULT,
    "f",
    "height",
    "Makes the player jump.",
    EV_NORMAL
);
Event EV_Player_AnimLoop_Torso
(
    "animloop_torso",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the torso animation has finished.",
    EV_NORMAL
);
Event EV_Player_AnimLoop_Legs
(
    "animloop_legs",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the legs animation has finished.",
    EV_NORMAL
);
Event EV_Player_AnimLoop_Pain // Added in 2.0
(
    "animloop_pain",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the pain animation has finished.",
    EV_NORMAL
);
Event EV_Player_DoUse
(
    "usestuff",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes the player try to use whatever is in front of her.",
    EV_NORMAL
);
Event EV_Player_ListInventory
(
    "listinventory",
    EV_CONSOLE,
    NULL,
    NULL,
    "List of the player's inventory.",
    EV_NORMAL
);
Event EV_Player_ActivateShield
(
    "activateshield",
    EV_DEFAULT,
    NULL,
    NULL,
    "Activates the player's shield",
    EV_NORMAL
);
Event EV_Player_DeactivateShield
(
    "deactivateshield",
    EV_DEFAULT,
    NULL,
    NULL,
    "Deactivates the player's shield",
    EV_NORMAL
);
Event EV_Player_Turn
(
    "turn",
    EV_DEFAULT,
    "f",
    "yawangle",
    "Causes player to turn the specified amount.",
    EV_NORMAL
);
Event EV_Player_TurnUpdate
(
    "turnupdate",
    EV_DEFAULT,
    "ff",
    "yaw timeleft",
    "Causes player to turn the specified amount.",
    EV_NORMAL
);
Event EV_Player_TurnLegs
(
    "turnlegs",
    EV_DEFAULT,
    "f",
    "yawangle",
    "Turns the players legs instantly by the specified amount.",
    EV_NORMAL
);
Event EV_Player_NextPainTime(
    "nextpaintime",
    EV_DEFAULT,
    "f",
    "seconds",
    "Set the next time the player experiences pain (Current time + seconds specified).",
    EV_NORMAL
);

Event EV_Player_FinishUseAnim
(
    "finishuseanim",
    EV_DEFAULT,
    NULL,
    NULL,
    "Fires off all targets associated with a particular useanim.",
    EV_NORMAL
);
Event EV_Player_Holster
(
    "holster",
    EV_CONSOLE,
    NULL,
    NULL,
    "Holsters all wielded weapons, or unholsters previously put away weapons",
    EV_NORMAL
);
Event EV_Player_SafeHolster
(
    "safeholster",
    EV_CONSOLE,
    "b",
    "putaway",
    "Holsters all wielded weapons, or unholsters previously put away weapons\n"
    "preserves state, so it will not holster or unholster unless necessary",
    EV_NORMAL
);
Event EV_Player_SafeZoom
(
    "safezoom",
    EV_DEFAULT,
    "b",
    "zoomin",
    "0 turns off zoom,"
    "and 1 returns zoom to previous setting"
);
Event EV_Player_ZoomOff
(
    "zoomoff",
    EV_DEFAULT,
    NULL,
    NULL,
    "makes sure that zoom is off",
    EV_NORMAL
);
Event EV_Player_StartUseObject
(
    "startuseobject",
    EV_DEFAULT,
    NULL,
    NULL,
    "starts up the useobject's animations.",
    EV_NORMAL
);
Event EV_Player_FinishUseObject
(
    "finishuseobject",
    EV_DEFAULT,
    NULL,
    NULL,
    "Fires off all targets associated with a particular useobject.",
    EV_NORMAL
);
Event EV_Player_WatchActor
(
    "watchactor",
    EV_DEFAULT,
    "e",
    "actor_to_watch",
    "Makes the player's camera watch the specified actor.",
    EV_NORMAL
);
Event EV_Player_StopWatchingActor
(
    "stopwatchingactor",
    EV_DEFAULT,
    "e",
    "actor_to_stop_watching",
    "Makes the player's camera stop watching the specified actor.",
    EV_NORMAL
);
Event EV_Player_SetDamageMultiplier
(
    "damage_multiplier",
    EV_DEFAULT,
    "f",
    "damage_multiplier",
    "Sets the current damage multiplier",
    EV_NORMAL
);
Event EV_Player_WaitForState
(
    "waitForState",
    EV_DEFAULT,
    "s",
    "stateToWaitFor",
    "When set, the player will clear waitforplayer when this state is hit\n"
    "in the legs or torso.",
    EV_NORMAL
);
Event EV_Player_LogStats
(
    "logstats",
    EV_CHEAT,
    "b",
    "state",
    "Turn on/off the debugging playlog",
    EV_NORMAL
);
Event EV_Player_TakePain
(
    "takepain",
    EV_DEFAULT,
    "b",
    "bool",
    "Set whether or not to take pain",
    EV_NORMAL
);
Event EV_Player_SkipCinematic
(
    "skipcinematic",
    EV_CONSOLE,
    NULL,
    NULL,
    "Skip the current cinematic",
    EV_NORMAL
);
Event EV_Player_ResetHaveItem
(
    "resethaveitem",
    EV_CONSOLE,
    "s",
    "weapon_name",
    "Resets the game var that keeps track that we have gotten this weapon",
    EV_NORMAL
);
Event EV_Player_ModifyHeight
(
    "modheight",
    EV_DEFAULT,
    "s",
    "height",
    "change the maximum height of the player\ncan specify 'stand', 'duck' or 'prone'.",
    EV_NORMAL
);
Event EV_Player_ModifyHeightFloat // Added in 2.40
(
    "modheightfloat",
     EV_DEFAULT,
     "ff",
     "height max_z",
     "Specify the view height of the player and the height of his bounding box.",
     EV_NORMAL
);
Event EV_Player_SetMovePosFlags
(
    "moveposflags",
    EV_DEFAULT,
    "sS",
    "position movement",
    "used by the state files to tell the game dll what the player is doing",
    EV_NORMAL
);
Event EV_Player_GetPosition
(
    "getposition",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns the player current position",
    EV_RETURN
);
Event EV_Player_GetMovement
(
    "getmovement",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns the player current movement",
    EV_RETURN
);
Event EV_Player_Score
(
    "score",
    EV_CONSOLE,
    NULL,
    NULL,
    "Show the score for the current deathmatch game",
    EV_NORMAL
);
Event EV_Player_JoinDMTeam
(
    "join_team",
    EV_CONSOLE,
    "s",
    "team",
    "Join the specified team (allies or axis)",
    EV_NORMAL
);
Event EV_Player_AutoJoinDMTeam
(
    "auto_join_team",
    EV_CONSOLE,
    NULL,
    NULL,
    "Join the team with fewer players",
    EV_NORMAL
);
Event EV_Player_PickWeapon
(
    "pickweapon",
    EV_CONSOLE,
    NULL,
    NULL,
    "Pick your weapon.",
    EV_NORMAL
);
Event EV_Player_SetInJail // Added in 2.30
(
    "injail",
    EV_DEFAULT,
    "i",
    "boolean",
    "set to 1 to indicate when player is in jail,"
    "0 when they are free",
    EV_SETTER
);
Event EV_Player_GetInJail // Added in 2.30
(
    "injail",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns 1 if player is in jail,"
    "0 if out",
    EV_GETTER
);
Event EV_Player_GetNationalityPrefix // Added in 2.30
(
    "nationalityprefix",
    EV_DEFAULT,
    NULL,
    NULL,
    "get the three or five letter prefix that denotes the player's nationality",
    EV_GETTER
);
Event EV_Player_IsSpectator // Added in 2.30
(
    "isSpectator",
    EV_DEFAULT,
    NULL,
    NULL,
    "Check to see if player is a spectator (non-zero return value)",
    EV_GETTER
);
Event EV_Player_Spectator
(
    "spectator",
    EV_CONSOLE,
    NULL,
    NULL,
    "Become a spectator",
    EV_NORMAL
);
Event EV_Player_JoinArena
(
    "join_arena",
    EV_CONSOLE,
    "i",
    "arena_id_num",
    "Join the specified arena",
    EV_NORMAL
);
Event EV_Player_LeaveArena
(
    "leave_arena",
    EV_CONSOLE,
    NULL,
    NULL,
    "Leave the current arena",
    EV_NORMAL
);
Event EV_Player_CreateTeam
(
    "create_team",
    EV_CONSOLE,
    NULL,
    NULL,
    "Create a team in the current arena",
    EV_NORMAL
);
Event EV_Player_LeaveTeam
(
    "leave_team",
    EV_CONSOLE,
    NULL,
    NULL,
    "Leave the current team",
    EV_NORMAL
);
Event EV_Player_RefreshArenaUI
(
    "arena_ui",
    EV_CONSOLE,
    NULL,
    NULL,
    "Refresh the arena UI",
    EV_NORMAL
);
Event EV_Player_CallVote
(
    "callvote",
    EV_CONSOLE,
    "ss",
    "arg1 arg2",
    "Player calls a vote",
    EV_NORMAL
);
Event EV_Player_Vote
(
    "vote",
    EV_CONSOLE,
    "s",
    "arg1",
    "Player votes either yes or no",
    EV_NORMAL
);
Event EV_Player_RetrieveVoteOptions // Added in 2.0
(
    "gvo",
    EV_CONSOLE,
    NULL,
    NULL,
    "Retrieves the server's vote options file",
    EV_NORMAL
);
Event EV_Player_PrimaryDMWeapon
(
    "primarydmweapon",
    EV_CONSOLE,
    "s",
    "weaptype",
    "Sets the player's primary DM weapon",
    EV_NORMAL
);
Event EV_Player_DeadBody
(
    "deadbody",
    EV_DEFAULT,
    NULL,
    NULL,
    "Spawn a dead body",
    EV_NORMAL
);
Event EV_Player_Physics_On
(
    "physics_on",
    EV_DEFAULT,
    NULL,
    NULL,
    "turn player physics on.",
    EV_NORMAL
);
Event EV_Player_Physics_Off
(
    "physics_off",
    EV_DEFAULT,
    NULL,
    NULL,
    "turn player physics off.",
    EV_NORMAL
);
Event EV_Player_ArmWithWeapons // Added in 2.30
(
    "armwithweapons",
    EV_DEFAULT,
    NULL,
    NULL,
    "give player their primary and secondary weapons.",
    EV_NORMAL
);
Event EV_Player_GetCurrentDMWeaponType // Added in 2.30
(
    "getcurrentdmweapontype",
    EV_DEFAULT,
    NULL,
    NULL,
    "get the player's current DM weapon type.",
    EV_GETTER
);
Event EV_Player_AttachToLadder
(
    "attachtoladder",
    EV_DEFAULT,
    NULL,
    NULL,
    "Attaches the sentient to a ladder",
    EV_NORMAL
);
Event EV_Player_UnattachFromLadder
(
    "unattachfromladder",
    EV_DEFAULT,
    NULL,
    NULL,
    "Unattaches the sentient from a ladder",
    EV_NORMAL
);
Event EV_Player_TweakLadderPos
(
    "tweakladderpos",
    EV_DEFAULT,
    NULL,
    NULL,
    "Tweaks the player's position on a ladder to be proper",
    EV_NORMAL
);
Event EV_Player_EnsureOverLadder
(
    "ensureoverladder",
    EV_DEFAULT,
    NULL,
    NULL,
    "Ensures that the player is at the proper height when getting off the top of a ladder",
    EV_NORMAL
);
Event EV_Player_EnsureForwardOffLadder
(
    "ensureforwardoffladder",
    EV_DEFAULT,
    NULL,
    NULL,
    "Ensures that the player went forward off the ladder.",
    EV_NORMAL
);
Event EV_Player_JailIsEscaping // Added in 2.30
(
    "isEscaping",
    EV_DEFAULT,
    NULL,
    NULL,
    "Return non-zero if escaping or assisting escape",
    EV_GETTER
);
Event EV_Player_JailEscape // Added in 2.30
(
    "jailescape",
    EV_DEFAULT,
    NULL,
    NULL,
    "Start the escape from jail animation",
    EV_NORMAL
);
Event EV_Player_JailAssistEscape // Added in 2.30
(
    "jailassistescape",
    EV_DEFAULT,
    NULL,
    NULL,
    "Start the assist jail escape animation",
    EV_NORMAL
);
Event EV_Player_JailEscapeStop // Added in 2.30
(
    "jailescapestop",
    EV_DEFAULT,
    NULL,
    NULL,
    "Stop either the escape from jail or assist animation",
    EV_NORMAL
);
Event EV_Player_GetIsDisguised
(
    "is_disguised",
    EV_DEFAULT,
    NULL,
    NULL,
    "zero = not disguised"
    "non-zero = disguised",
    EV_GETTER
);
Event EV_Player_GetHasDisguise
(
    "has_disguise",
    EV_DEFAULT,
    NULL,
    NULL,
    "zero = does not have a disguise,"
    "non - zero = has a disguise",
    EV_GETTER
);
Event EV_Player_SetHasDisguise
(
    "has_disguise",
    EV_DEFAULT,
    "i",
    "is_disguised",
    "zero = does not have a disguise,"
    "non - zero = has a disguise",
    EV_SETTER
);
Event EV_Player_ObjectiveCount
(
    "objective",
    EV_DEFAULT,
    "ii",
    "num_completed out_of",
    "Sets the number of objectives completed and the total number of objectives",
    EV_NORMAL
);
Event EV_Player_Stats
(
    "stats",
    EV_CONSOLE,
    NULL,
    NULL,
    "Display the MissionLog.",
    EV_NORMAL
);
Event EV_Player_Teleport
(
    "tele",
    EV_CHEAT | EV_CONSOLE,
    "v",
    "location",
    "Teleport to location",
    EV_NORMAL
);
Event EV_Player_Face
(
    "face",
    EV_CHEAT | EV_CONSOLE,
    "v",
    "angles",
    "Force angles to specified vector",
    EV_NORMAL
);
Event EV_Player_Coord
(
    "coord",
    EV_CONSOLE,
    NULL,
    NULL,
    "Prints out current location and angles",
    EV_NORMAL
);
Event EV_Player_TestAnim
(
    "testplayeranim",
    EV_CHEAT,
    "fS",
    "weight anim",
    "Plays a test animation on the player",
    EV_NORMAL
);
Event EV_Player_StuffText
(
    "stufftext",
    EV_DEFAULT,
    "s",
    "stuffstrings",
    "Stuffs text to the player's console",
    EV_NORMAL
);
Event EV_Player_DMMessage
(
    "dmmessage",
    EV_CONSOLE,
    "is",
    "mode stuffstrings",
    "sends a DM message to the appropriate players",
    EV_NORMAL
);
Event EV_Player_IPrint
(
    "iprint",
    EV_CONSOLE,
    "sI",
    "string bold",
    "prints a string to the player,"
    "optionally in bold",
    EV_NORMAL
);
Event EV_SetViewangles
(
    "viewangles",
    EV_DEFAULT,
    "v",
    "newAngles",
    "set the view angles of the entity to newAngles.",
    EV_SETTER
);
Event EV_GetViewangles
(
    "viewangles",
    EV_DEFAULT,
    NULL,
    NULL,
    "get the angles of the entity.",
    EV_GETTER
);
Event EV_GetUseHeld
(
    "useheld",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns 1 if this player is holding use,"
    "or 0 if he is not",
    EV_GETTER
);
Event EV_GetFireHeld
(
    "fireheld",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns 1 if this player is holding fire,"
    "or 0 if he is not",
    EV_GETTER
);
Event EV_GetPrimaryFireHeld // Added in 2.30
(
    "primaryfireheld",
     EV_DEFAULT,
     NULL,
     NULL,
     "returns 1 if this player is holding the primary fire, or 0 if not",
     EV_GETTER);
Event EV_GetSecondaryFireHeld // Added in 2.30
(
    "secondaryfireheld",
     EV_DEFAULT,
     NULL,
     NULL,
     "returns 1 if this player is holding the secondary fire, or 0 if not",
     EV_GETTER
);
Event EV_Player_GetReady
(
    "ready",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns 1 if this player is ready,"
    "0 otherwise",
    EV_GETTER
);
Event EV_Player_SetReady
(
    "ready",
    EV_CONSOLE,
    NULL,
    NULL,
    "makes this player ready for the round to start",
    EV_NORMAL
);
Event EV_Player_SetNotReady
(
    "notready",
    EV_CONSOLE,
    NULL,
    NULL,
    "makes this player not ready for the round to start",
    EV_NORMAL
);
Event EV_Player_GetName
(
    "netname",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns player's name",
    EV_GETTER
);
Event EV_Player_GetDMTeam
(
    "dmteam",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns 'allies',"
    "'axis',"
    "'spectator',"
    "or 'freeforall'",
    EV_GETTER
);
Event EV_Player_SetViewModelAnim
(
    "viewmodelanim",
    EV_DEFAULT,
    "sI",
    "name force_restart",
    "Sets the player's view model animation.",
    EV_NORMAL
);
Event EV_Player_DMDeathDrop
(
    "dmdeathdrop",
    EV_DEFAULT,
    NULL,
    NULL,
    "Drops the player inventory in DM after's he's been killed",
    EV_NORMAL
);
Event EV_Player_Stopwatch
(
    "stopwatch",
    EV_DEFAULT,
    "i",
    "duration",
    "Starts a stopwatch for a given duration... use 0 to clear the stopwatch",
    EV_NORMAL
);
Event EV_Player_EnterIntermission
(
    "_enterintermission",
    EV_CODEONLY,
    NULL,
    NULL,
    "CODE USE ONLY",
    EV_NORMAL
);
Event EV_Player_SetPerferredWeapon
(
    "perferredweapon",
    EV_DEFAULT,
    "s",
    "weapon_name",
    "Overrides your preferred weapon that is displayed in the stats screen.",
    EV_NORMAL
);
Event EV_Player_SetVoiceType
(
    "voicetype",
    EV_DEFAULT,
    "s",
    "voice_name",
    "Sets the voice type to use the player.",
    EV_NORMAL
);

Event EV_Player_AddKills // Added in 2.0
(
    "addkills",
    EV_DEFAULT,
    "i",
    "kills",
    "Give or take kills from the player",
    EV_NORMAL
);

Event EV_Player_KillAxis // Added in 2.30
(
    "killaxis",
    EV_CHEAT,
    "f",
    "radius",
    "Kills all of the axis that are in the passed radius, or all of them if radius is 0.",
    EV_NORMAL
);
Event EV_Player_GetTurret // Added in 2.30
(
    "turret",
    EV_DEFAULT,
    NULL,
    NULL,
    "Returns the turret the player is using. NULL if player isn't using a turret.",
    EV_GETTER
);
Event EV_Player_GetVehicle // Added in 2.30
(
    "vehicle",
    EV_DEFAULT,
    NULL,
    NULL,
    "Returns the vehicle the player is using. NULL if player isn't using a vehicle.",
    EV_GETTER
);

////////////////////////////
//
// Added in OPM
//
////////////////////////////
Event EV_Player_AddDeaths
(
    "adddeaths",
    EV_DEFAULT,
    "i",
    "deaths",
    "adds deaths number to player",
    EV_NORMAL
);
Event EV_Player_AdminRights
(
    "adminrights",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns client admin rights",
    EV_GETTER
);
Event EV_Player_IsAdmin
(
    "isadmin",
    EV_DEFAULT,
    NULL,
    NULL,
    "checks if player is logged as admin",
    EV_RETURN
);
Event EV_Player_BindWeap
(
    "bindweap",
    EV_DEFAULT,
    "ei",
    "weapon handnum",
    "binds weapon to player and sets him as weapon owner",
    EV_NORMAL
);
Event EV_Player_Dive
(
    "dive",
    EV_DEFAULT,
    "fF",
    "height airborne_duration",
    "Makes the player dive into prone position.",
    EV_NORMAL
);
Event EV_Player_FreezeControls
(
    "freezecontrols",
    EV_DEFAULT,
    "b",
    "freeze_state",
    "Blocks or unblocks control input from this player.",
    EV_NORMAL
);
Event EV_Player_GetConnState
(
    "getconnstate",
    EV_DEFAULT,
    NULL,
    NULL,
    "gets connection state. [DEPRECATED]",
    EV_RETURN
);
Event EV_Player_GetDamageMultiplier
(
    "damage_multiplier",
    EV_DEFAULT,
    "Gets the current damage multiplier",
    NULL,
    NULL,
    EV_GETTER
);
Event EV_Player_GetKillHandler
(
    "killhandler",
    EV_DEFAULT,
    "s",
    "label",
    "Gets the player's current killed event handler. Returns NIL if no custom killhandler was set.",
    EV_GETTER
);
Event EV_Player_GetKills
(
    "getkills",
    EV_DEFAULT,
    NULL,
    NULL,
    "gets kills number of player",
    EV_RETURN
);
Event EV_Player_GetDeaths
(
    "getdeaths",
    EV_DEFAULT,
    NULL,
    NULL,
    "gets deaths number of player",
    EV_RETURN
);
Event EV_Player_GetLegsState
(
    "getlegsstate",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets the player's current legs state name",
    EV_RETURN
);
Event EV_Player_GetStateFile
(
    "statefile",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets the player's current state file.",
    EV_GETTER
);
Event EV_Player_GetTorsoState
(
    "gettorsostate",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets the player's current torso state name",
    EV_RETURN
);
Event EV_Player_Inventory
(
    "inventory",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns player's inventory",
    EV_GETTER
);
Event EV_Player_InventorySet
(
    "inventory",
    EV_DEFAULT,
    "e",
    "array",
    "Set up the player's inventory",
    EV_SETTER
);
Event EV_Player_LeanLeftHeld
(
    "leanleftheld",
    EV_DEFAULT,
    NULL,
    NULL,
    "Returns 1 if this player is holding lean left key, or 0 if he is not",
    EV_GETTER
);
Event EV_Player_LeanRightHeld
(
    "leanrightheld",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns EV_RETURN if this player is holding lean right key, or 0 if he is not",
    EV_GETTER
);
Event EV_Player_MoveSpeedScale
(
    "moveSpeedScale",
    EV_DEFAULT,
    "f",
    "speed",
    "Sets the player's speed multiplier (default 1.0).",
    EV_SETTER
);
Event EV_Player_MoveSpeedScaleGet
(
    "moveSpeedScale",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets the player's speed multiplier.",
    EV_GETTER
);
Event EV_Player_PlayLocalSound
(
    "playlocalsound",
    EV_DEFAULT,
    "sBF",
    "soundName loop time",
    "Plays a local sound to the player. The sound must be aliased globally. Requires sv_reborn to be set for stereo "
    "sounds.",
    EV_NORMAL
);
Event EV_Player_RunHeld
(
    "runheld",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns 1 if this player is holding run key,"
    "or 0 if he is not",
    EV_GETTER
);
Event EV_Player_SecFireHeld
(
    "secfireheld",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns 1 if this player is holding secondary fire, or 0 if he is not",
    EV_GETTER
);
Event EV_Player_SetAnimSpeed
(
    "setanimspeed",
    EV_DEFAULT,
    "f",
    "speed",
    "set the player's animation speed multiplier (default 1.0).",
    EV_NORMAL
);
Event EV_Player_SetFov
(
    "setfov",
    EV_DEFAULT,
    "f",
    "fov",
    "set the player's fov (default 80).",
    EV_NORMAL
);
Event EV_Player_SetKillHandler
(
    "killhandler",
    EV_DEFAULT,
    "s",
    "label",
    "Replace the player's killed event by a new scripted handler. None or an empty string will revert to the default "
    "killed event handler.",
    EV_SETTER
);
Event EV_Player_SetSpeed
(
    "setspeed",
    EV_DEFAULT,
    "fI",
    "speed index",
    "Sets the player's speed multiplier (default 1.0). Index specify which array value will be used (maximum 4).",
    EV_NORMAL
);
Event EV_Player_SetStateFile
(
    "statefile",
    EV_DEFAULT,
    "S",
    "statefile",
    "Sets the player's current state file (setting NIL, NULL or an empty string will revert to the global statefile).",
    EV_SETTER
);
Event EV_Player_SetTeam
(
    "setteam",
    EV_DEFAULT,
    "s",
    "team_name",
    "sets the player's team without respawning.\n"
    "Available team names are 'none', 'spectator', 'freeforall', 'axis' and 'allies'.",
    EV_NORMAL
);
Event EV_Player_HideEnt
(
    "hideent",
    EV_DEFAULT,
    "e",
    "entity",
    "Hides the specified entity to the player.",
    EV_NORMAL
);
Event EV_Player_ShowEnt
(
    "showent",
    EV_DEFAULT,
    "e",
    "entity",
    "Shows the specified entity to the player.",
    EV_NORMAL
);
Event EV_Player_StopLocalSound
(
    "stoplocalsound",
    EV_DEFAULT,
    "sF",
    "soundName time",
    "Stops the specified sound.",
    EV_NORMAL
);
Event EV_Player_Userinfo
(
    "userinfo",
    EV_DEFAULT,
    NULL,
    NULL,
    "returns userinfo string",
    EV_GETTER
);
Event EV_Player_ViewModelGetAnim
(
    "viewmodelgetanim",
    EV_DEFAULT,
    "B",
    "fullanim",
    "Gets the player's current view model animation.",
    EV_RETURN
);
Event EV_Player_ViewModelAnimFinished
(
    "viewmodelanimfinished",
    EV_DEFAULT,
    NULL,
    NULL,
    "True if the player's current view model finished its animation.",
    EV_RETURN
);
Event EV_Player_ViewModelAnimValid
(
    "viewmodelanimvalid",
    EV_DEFAULT,
    "sB",
    "anim fullanim",
    "True if the view model animation is valid.",
    EV_RETURN
);

#ifdef OPM_FEATURES
Event EV_Player_Earthquake
(
    "earthquake2",
    EV_DEFAULT,
    "ffbbVF",
    "duration magnitude no_rampup no_rampdown location radius",
    "Create a smooth realistic earthquake for a player. Requires sv_reborn to be set.",
    EV_NORMAL
);
Event EV_Player_Replicate
(
    "replicate",
    EV_DEFAULT,
    "s",
    "variable",
    "Replicate a variable to the client (needs patch 1.12).",
    EV_NORMAL
);
Event EV_Player_SetClientFlag
(
    "setclientflag",
    EV_DEFAULT,
    "s",
    "name",
    "Calls a flag to the script client.",
    EV_NORMAL
);
Event EV_Player_SetEntityShader
(
    "setentshader",
    EV_DEFAULT,
    "es",
    "entity shadername",
    "Sets an entity shader for this player. An empty string will revert to the normal entity shader.",
    EV_NORMAL
);
Event EV_Player_SetLocalSoundRate
(
    "setlocalsoundrate",
    EV_DEFAULT,
    "sfF",
    "name rate time",
    "Sets the local sound rate.",
    EV_NORMAL
);
Event EV_Player_SetViewModelAnimSpeed
(
    "setvmaspeed",
    EV_DEFAULT,
    "sf",
    "name speed",
    "Sets the player's animation speed when playing it.",
    EV_NORMAL
);
Event EV_Player_VisionSetBlur
(
    "visionsetblur",
    EV_DEFAULT,
    "fF",
    "level transition_time",
    "Sets the player's blur level. Level is a fraction from 0-1",
    EV_NORMAL
);
Event EV_Player_VisionGetNaked
(
    "visiongetnaked",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets the player's current naked-eye vision.",
    EV_RETURN
);
Event EV_Player_VisionSetNaked
(
    "visionsetnaked",
    EV_DEFAULT,
    "sFF",
    "vision_name transition_time phase",
    "Sets the player's naked-eye vision. Optionally give a transition time from the current vision. If vision_name is "
    "an empty string, it will revert to the current global vision.",
    EV_NORMAL
);
#endif

qboolean TryPush(int entnum, vec3_t move_origin, vec3_t move_end);

/*
==============================================================================

PLAYER

==============================================================================
*/

CLASS_DECLARATION(Sentient, Player, "player") {
    {&EV_Vehicle_Enter,                   &Player::EnterVehicle                 },
    {&EV_Vehicle_Exit,                    &Player::ExitVehicle                  },
    {&EV_Turret_Enter,                    &Player::EnterTurret                  },
    {&EV_Turret_Exit,                     &Player::ExitTurret                   },
    {&EV_Player_EndLevel,                 &Player::EndLevel                     },
    {&EV_Player_PrevItem,                 &Player::SelectPreviousItem           },
    {&EV_Player_NextItem,                 &Player::SelectNextItem               },
    {&EV_Player_PrevWeapon,               &Player::SelectPreviousWeapon         },
    {&EV_Player_NextWeapon,               &Player::SelectNextWeapon             },
    {&EV_Player_DropWeapon,               &Player::DropCurrentWeapon            },
    {&EV_Player_Reload,                   &Player::PlayerReload                 },
    {&EV_Player_CorrectWeaponAttachments, &Player::EventCorrectWeaponAttachments},
    {&EV_Player_GiveCheat,                &Player::GiveCheat                    },
    {&EV_Player_GiveWeaponCheat,          &Player::GiveWeaponCheat              },
    {&EV_Player_GiveAllCheat,             &Player::GiveAllCheat                 },
    {&EV_Player_GiveNewWeaponsCheat,      &Player::GiveNewWeaponsCheat          },
    {&EV_Player_DevGodCheat,              &Player::GodCheat                     },
    {&EV_Player_FullHeal,                 &Player::FullHeal                     },
    {&EV_Player_DevNoTargetCheat,         &Player::NoTargetCheat                },
    {&EV_Player_DevNoClipCheat,           &Player::NoclipCheat                  },
    {&EV_Player_GameVersion,              &Player::GameVersion                  },
    {&EV_Player_DumpState,                &Player::DumpState                    },
    {&EV_Player_ForceTorsoState,          &Player::ForceTorsoState              },
    {&EV_Player_ForceLegsState,           &Player::ForceLegsState               },
    {&EV_Player_Fov,                      &Player::EventSetSelectedFov          },
    {&EV_Kill,                            &Player::Kill                         },
    {&EV_Player_Dead,                     &Player::Dead                         },
    {&EV_Player_SpawnEntity,              &Player::SpawnEntity                  },
    {&EV_Player_SpawnActor,               &Player::SpawnActor                   },
    {&EV_Player_Respawn,                  &Player::Respawn                      },
    {&EV_Player_DoUse,                    &Player::DoUse                        },
    {&EV_Pain,                            &Player::Pain                         },
    {&EV_Killed,                          &Player::Killed                       },
    {&EV_GotKill,                         &Player::GotKill                      },
    {&EV_Player_TestThread,               &Player::TestThread                   },
    {&EV_Player_PowerupTimer,             &Player::SetPowerupTimer              },
    {&EV_Player_UpdatePowerupTimer,       &Player::UpdatePowerupTimer           },
    {&EV_Player_ResetState,               &Player::ResetState                   },
    {&EV_Player_WhatIs,                   &Player::WhatIs                       },
    {&EV_Player_ActorInfo,                &Player::ActorInfo                    },
    {&EV_Player_KillEnt,                  &Player::KillEnt                      },
    {&EV_Player_RemoveEnt,                &Player::RemoveEnt                    },
    {&EV_Player_KillClass,                &Player::KillClass                    },
    {&EV_Player_RemoveClass,              &Player::RemoveClass                  },
    {&EV_Player_AnimLoop_Legs,            &Player::EndAnim_Legs                 },
    {&EV_Player_AnimLoop_Torso,           &Player::EndAnim_Torso                },
    {&EV_Player_AnimLoop_Pain,            &Player::EndAnim_Pain                 },
    {&EV_Player_Jump,                     &Player::Jump                         },
    {&EV_Sentient_JumpXY,                 &Player::JumpXY                       },
    {&EV_Player_ListInventory,            &Player::ListInventoryEvent           },
    {&EV_Player_NextPainTime,             &Player::NextPainTime                 },
    {&EV_Player_Turn,                     &Player::Turn                         },
    {&EV_Player_TurnUpdate,               &Player::TurnUpdate                   },
    {&EV_Player_TurnLegs,                 &Player::TurnLegs                     },
    {&EV_Player_FinishUseAnim,            &Player::FinishUseAnim                },
    {&EV_Player_Holster,                  &Player::HolsterToggle                },
    {&EV_Player_SafeHolster,              &Player::Holster                      },
    {&EV_Player_SafeZoom,                 &Player::SafeZoomed                   },
    {&EV_Player_ZoomOff,                  &Player::ZoomOffEvent                 },
    {&EV_Player_StartUseObject,           &Player::StartUseObject               },
    {&EV_Player_FinishUseObject,          &Player::FinishUseObject              },
    {&EV_Player_WatchActor,               &Player::WatchActor                   },
    {&EV_Player_StopWatchingActor,        &Player::StopWatchingActor            },
    {&EV_Player_SetDamageMultiplier,      &Player::SetDamageMultiplier          },
    {&EV_Player_WaitForState,             &Player::WaitForState                 },
    {&EV_Player_LogStats,                 &Player::LogStats                     },
    {&EV_Player_TakePain,                 &Player::SetTakePain                  },
    {&EV_Player_SkipCinematic,            &Player::SkipCinematic                },
    {&EV_Player_ResetHaveItem,            &Player::ResetHaveItem                },
    {&EV_Show,                            &Player::PlayerShowModel              },
    {&EV_Player_ModifyHeight,             &Player::ModifyHeight                 },
    {&EV_Player_ModifyHeightFloat,        &Player::ModifyHeightFloat            },
    {&EV_Player_SetMovePosFlags,          &Player::SetMovePosFlags              },
    {&EV_Player_GetPosition,              &Player::GetPositionForScript         },
    {&EV_Player_GetMovement,              &Player::GetMovementForScript         },
    {&EV_Player_Teleport,                 &Player::EventTeleport                },
    {&EV_Player_Face,                     &Player::EventFace                    },
    {&EV_Player_Coord,                    &Player::EventCoord                   },
    {&EV_Player_TestAnim,                 &Player::EventTestAnim                },
    {&EV_Player_JailIsEscaping,           &Player::EventGetIsEscaping           },
    {&EV_Player_JailEscapeStop,           &Player::EventJailEscapeStop          },
    {&EV_Player_JailAssistEscape,         &Player::EventJailAssistEscape        },
    {&EV_Player_JailEscape,               &Player::EventJailEscape              },
    {&EV_Player_Score,                    &Player::Score                        },
    {&EV_Player_JoinDMTeam,               &Player::Join_DM_Team                 },
    {&EV_Player_AutoJoinDMTeam,           &Player::Auto_Join_DM_Team            },
    {&EV_Player_LeaveTeam,                &Player::Leave_DM_Team                },
    {&EV_Player_IsSpectator,              &Player::GetIsSpectator               },
    {&EV_Player_GetNationalityPrefix,     &Player::GetNationalityPrefix         },
    {&EV_Player_SetInJail,                &Player::EventSetInJail               },
    {&EV_Player_GetInJail,                &Player::EventGetInJail               },
    {&EV_Player_Spectator,                &Player::Spectator                    },
    {&EV_Player_PickWeapon,               &Player::PickWeaponEvent              },
    {&EV_Player_CallVote,                 &Player::CallVote                     },
    {&EV_Player_Vote,                     &Player::Vote                         },
    {&EV_Player_RetrieveVoteOptions,      &Player::RetrieveVoteOptions          },
    {&EV_Player_PrimaryDMWeapon,          &Player::EventPrimaryDMWeapon         },
    {&EV_Player_DeadBody,                 &Player::DeadBody                     },
    {&EV_Player_ArmWithWeapons,           &Player::ArmWithWeapons               },
    {&EV_Player_GetCurrentDMWeaponType,   &Player::EventGetCurrentDMWeaponType  },
    {&EV_Player_Physics_On,               &Player::PhysicsOn                    },
    {&EV_Player_Physics_Off,              &Player::PhysicsOff                   },
    {&EV_Player_AttachToLadder,           &Player::AttachToLadder               },
    {&EV_Player_UnattachFromLadder,       &Player::UnattachFromLadder           },
    {&EV_Player_TweakLadderPos,           &Player::TweakLadderPos               },
    {&EV_Player_EnsureOverLadder,         &Player::EnsureOverLadder             },
    {&EV_Player_EnsureForwardOffLadder,   &Player::EnsureForwardOffLadder       },
    {&EV_Damage,                          &Player::ArmorDamage                  },
    {&EV_Player_GetIsDisguised,           &Player::GetIsDisguised               },
    {&EV_Player_GetHasDisguise,           &Player::GetHasDisguise               },
    {&EV_Player_SetHasDisguise,           &Player::SetHasDisguise               },
    {&EV_Player_ObjectiveCount,           &Player::SetObjectiveCount            },
    {&EV_Player_Stats,                    &Player::Stats                        },
    {&EV_Player_StuffText,                &Player::EventStuffText               },
    {&EV_Player_DMMessage,                &Player::EventDMMessage               },
    {&EV_Player_IPrint,                   &Player::EventIPrint                  },
    {&EV_SetViewangles,                   &Player::SetViewangles                },
    {&EV_GetViewangles,                   &Player::GetViewangles                },
    {&EV_GetUseHeld,                      &Player::EventGetUseHeld              },
    {&EV_GetFireHeld,                     &Player::EventGetFireHeld             },
    {&EV_GetPrimaryFireHeld,              &Player::EventGetPrimaryFireHeld      },
    {&EV_GetSecondaryFireHeld,            &Player::EventGetSecondaryFireHeld    },
    {&EV_Player_GetReady,                 &Player::EventGetReady                },
    {&EV_Player_SetReady,                 &Player::EventSetReady                },
    {&EV_Player_SetNotReady,              &Player::EventSetNotReady             },
    {&EV_Player_GetDMTeam,                &Player::EventGetDMTeam               },
    {&EV_Player_GetName,                  &Player::EventGetNetName              },
    {&EV_Player_SetViewModelAnim,         &Player::EventSetViewModelAnim        },
    {&EV_Player_DMDeathDrop,              &Player::EventDMDeathDrop             },
    {&EV_Player_Stopwatch,                &Player::EventStopwatch               },
    {&EV_Player_EnterIntermission,        &Player::EventEnterIntermission       },
    {&EV_Player_SetPerferredWeapon,       &Player::EventSetPerferredWeapon      },
    {&EV_Player_SetVoiceType,             &Player::EventSetVoiceType            },
    {&EV_Player_AddKills,                 &Player::EventAddKills                },
    {&EV_Player_KillAxis,                 &Player::EventKillAxis                },
    {&EV_Player_GetTurret,                &Player::EventGetTurret               },
    {&EV_Player_GetVehicle,               &Player::EventGetVehicle              },

    {&EV_Player_AddDeaths,                &Player::AddDeaths                    },
    {&EV_Player_AdminRights,              &Player::AdminRights                  },
    {&EV_Player_BindWeap,                 &Player::BindWeap                     },
    {&EV_Player_Dive,                     &Player::Dive                         },
    {&EV_Player_DMMessage,                &Player::EventDMMessage               },
    {&EV_Player_FreezeControls,           &Player::FreezeControls               },
    {&EV_Player_SetTeam,                  &Player::EventSetTeam                 },
    {&EV_Player_GetConnState,             &Player::GetConnState                 },
    {&EV_Player_GetDamageMultiplier,      &Player::GetDamageMultiplier          },
    {&EV_Player_GetDeaths,                &Player::GetDeaths                    },
    {&EV_Player_GetKillHandler,           &Player::GetKillHandler               },
    {&EV_Player_GetKills,                 &Player::GetKills                     },
    {&EV_Player_GetLegsState,             &Player::GetLegsState                 },
    {&EV_Player_GetStateFile,             &Player::GetStateFile                 },
    {&EV_Player_GetTorsoState,            &Player::GetTorsoState                },
    {&EV_Player_HideEnt,                  &Player::HideEntity                   },
    {&EV_Player_Inventory,                &Player::Inventory                    },
    {&EV_Player_InventorySet,             &Player::InventorySet                 },
    {&EV_Player_IsSpectator,              &Player::GetIsSpectator               },
    {&EV_Player_IsAdmin,                  &Player::IsAdmin                      },
    {&EV_Player_LeanLeftHeld,             &Player::LeanLeftHeld                 },
    {&EV_Player_LeanRightHeld,            &Player::LeanRightHeld                },
    {&EV_Player_MoveSpeedScale,           &Player::SetSpeed                     },
    {&EV_Player_MoveSpeedScaleGet,        &Player::GetMoveSpeedScale            },
    {&EV_Player_PlayLocalSound,           &Player::PlayLocalSound               },
    {&EV_Player_RunHeld,                  &Player::RunHeld                      },
    {&EV_Player_SecFireHeld,              &Player::SecFireHeld                  },
    {&EV_Player_SetAnimSpeed,             &Player::SetAnimSpeed                 },
    {&EV_Player_SetKillHandler,           &Player::SetKillHandler               },
    {&EV_Player_SetSpeed,                 &Player::SetSpeed                     },
    {&EV_Player_SetStateFile,             &Player::SetStateFile                 },
    {&EV_Player_ShowEnt,                  &Player::ShowEntity                   },
    {&EV_Player_Spectator,                &Player::Spectator                    },
    {&EV_Player_StopLocalSound,           &Player::StopLocalSound               },
    {&EV_Player_Userinfo,                 &Player::Userinfo                     },
    {&EV_Player_ViewModelAnimFinished,    &Player::EventGetViewModelAnimFinished},
    {&EV_Player_ViewModelGetAnim,         &Player::EventGetViewModelAnim        },
    {&EV_Player_ViewModelAnimValid,       &Player::EventGetViewModelAnimValid   },
#ifdef OPM_FEATURES
    {&EV_Player_Earthquake,               &Player::EventEarthquake              },
    {&EV_Player_SetClientFlag,            &Player::SetClientFlag                },
    {&EV_Player_SetEntityShader,          &Player::SetEntityShader              },
    {&EV_Player_SetLocalSoundRate,        &Player::SetLocalSoundRate            },
    {&EV_Player_SetViewModelAnimSpeed,    &Player::SetVMASpeed                  },
    {&EV_Player_VisionGetNaked,           &Player::VisionGetNaked               },
    {&EV_Player_VisionSetBlur,            &Player::VisionSetBlur                },
    {&EV_Player_VisionSetNaked,           &Player::VisionSetNaked               },
#endif
    {NULL,                                NULL                                  }
};

movecontrolfunc_t Player::MoveStartFuncs[] = {
    NULL, // MOVECONTROL_USER,				// Quake style
    NULL, // MOVECONTROL_LEGS,				// Quake style, legs state system active
    NULL, // MOVECONTROL_USER_MOVEANIM,		// Quake style, legs state system active
    NULL, // MOVECONTROL_ANIM,				// move based on animation, with full collision testing
    NULL, // MOVECONTROL_ABSOLUTE,			// move based on animation, with full collision testing but no turning
    NULL, // MOVECONTROL_HANGING,				// move based on animation, with full collision testing, hanging
    NULL, // MOVECONTROL_ROPE_GRAB
    NULL, // MOVECONTROL_ROPE_RELEASE
    NULL, // MOVECONTROL_ROPE_MOVE
    NULL, // MOVECONTROL_PICKUPENEMY
    &Player::StartPush,        // MOVECONTROL_PUSH
    NULL,                      // MOVECONTROL_CLIMBWALL
    &Player::StartUseAnim,     // MOVECONTROL_USEANIM
    NULL,                      // MOVECONTROL_CROUCH
    &Player::StartLoopUseAnim, // MOVECONTROL_LOOPUSEANIM
    &Player::SetupUseObject,   // MOVECONTROL_USEOBJECT
    NULL,                      // MOVECONTROL_COOLOBJECT
};

Player::Player()
{
    //
    // set the entity type
    //
    entflags |= ECF_PLAYER;

    mCurTrailOrigin   = 0;
    mLastTrailTime    = 0;
    m_pLastSpawnpoint = NULL;

    voted                      = false;
    m_fInvulnerableTimeElapsed = 0;
    m_voiceType                = PVT_NONE_SET;
    m_fTalkTime                = 0;
    num_deaths                 = 0;
    num_kills                  = 0;
    num_won_matches            = 0;
    num_lost_matches           = 0;
    num_team_kills             = 0;
    m_iLastNumTeamKills        = 0;
    m_bTempSpectator           = false;
    m_bSpectator               = false;
    m_bSpectatorSwitching      = false;
    m_bAllowFighting           = false;
    m_bReady                   = false;
    m_iPlayerSpectating        = 0;
    dm_team                    = TEAM_NONE;
    m_fTeamSelectTime          = -30;
    votecount                  = 0;
    m_fNextVoteOptionTime      = 0;
    m_fWeapSelectTime          = 0;

    fAttackerDispTime   = 0;
    m_iInfoClient       = 0;
    m_iInfoClientHealth = 0;
    m_fInfoClientTime   = 0;

    m_bDeathSpectator            = false;
    m_fSpawnTimeLeft             = 0;
    m_bWaitingForRespawn         = 0;
    m_bShouldRespawn             = false;
    last_camera_type             = -1;
    m_fLastInvulnerableTime      = 0;
    m_iInvulnerableTimeRemaining = 0;
    m_fLastVoteTime              = 0;

    //
    // Added in OPM
    //====
#ifdef OPM_FEATURES
    m_bShowingHint = false;
#endif
    m_fpsTiki    = NULL;
    m_bConnected = false;

    m_iInstantMessageTime = 0;
    m_iTextChatTime       = 0;
    //====

    if (LoadingSavegame) {
        return;
    }

    actor_camera_right          = false;
    starting_actor_camera_right = false;
    useanim_numloops            = 1;
    move_right_vel              = 0;

    m_iInfoClient       = -1;
    m_iInfoClientHealth = 0;
    m_fInfoClientTime   = 0.0;

    music_current_volume   = -1;
    music_saved_volume     = -1;
    music_volume_fade_time = -1;

    feetfalling        = false;
    edict->s.eType     = ET_PLAYER;
    buttons            = 0;
    new_buttons        = 0;
    server_new_buttons = 0;
    respawn_time       = -1.0;

    //
    // State
    //
    statemap_Legs      = NULL;
    statemap_Torso     = NULL;
    m_fPartBlends[0]   = 0;
    m_fPartBlends[1]   = 0;
    m_iPartSlot[legs]  = 0;
    m_iPartSlot[torso] = 2;
    partBlendMult[0]   = 0;
    partBlendMult[1]   = 0;
    m_fPainBlend       = 0;
    animdone_Pain      = false;

    m_fLastDeltaTime = level.time;

    camera         = NULL;
    atobject       = NULL;
    atobject_dist  = 0;
    toucheduseanim = NULL;
    useitem_in_use = NULL;

    damage_blood = 0;
    damage_count = 0;
    damage_from  = vec_zero;
    damage_alpha = 0;
    damage_yaw   = 0;

    fAttackerDispTime    = 0;
    pAttackerDistPointer = NULL;
    last_attack_button   = 0;
    attack_blocked       = false;
    canfall              = false;

    move_left_vel     = 0;
    move_right_vel    = 0;
    move_backward_vel = 0;
    move_forward_vel  = 0;
    move_up_vel       = 0;
    move_down_vel     = 0;

    moveresult                = 0;
    animspeed                 = 0;
    airspeed                  = 200.0f;
    weapons_holstered_by_code = false;
    actor_camera              = NULL;

    damage_multiplier = 1.0f;
    take_pain         = true;
    m_bIsInJail       = false;
    dm_team           = TEAM_NONE;
    current_team      = NULL;

    m_bTempSpectator      = false;
    m_bSpectator          = false;
    m_bSpectatorSwitching = false;
    m_bAllowFighting      = false;
    m_bReady              = true;
    m_fTeamSelectTime     = -30;
    m_fTalkTime           = 0;

    num_deaths            = 0;
    num_kills             = 0;
    num_team_kills        = 0;
    m_iLastNumTeamKills   = 0;
    num_won_matches       = 0;
    num_lost_matches      = 0;
    client->ps.voted      = false;
    votecount             = 0;
    m_fLastVoteTime       = 0;
    m_fNextVoteOptionTime = 0;
    m_fWeapSelectTime     = 0;
    m_jailstate           = JAILSTATE_NONE;

    SetSelectedFov(atof(Info_ValueForKey(client->pers.userinfo, "fov")));
    SetFov(selectedfov);

    m_iInZoomMode       = 0;
    m_iNumShotsFired    = 0;
    m_iNumHits          = 0;
    m_iNumGroinShots    = 0;
    m_iNumHeadShots     = 0;
    m_iNumLeftArmShots  = 0;
    m_iNumLeftLegShots  = 0;
    m_iNumRightArmShots = 0;
    m_iNumRightLegShots = 0;
    m_iNumTorsoShots    = 0;

    m_sPerferredWeaponOverride = "";

    SetTargetName("player");

    Init();

    for (int i = 0; i < MAX_TRAILS; i++) {
        mvTrail[i] = Vector(0, 0, 0);
    }

    for (int i = 0; i < MAX_TRAILS; i++) {
        mvTrailEyes[i] = Vector(0, 0, 0);
    }

    client->ps.pm_flags &= ~PMF_NO_HUD;

    m_fLastSprintTime = 0;
    m_bHasJumped      = false;

    m_fLastInvulnerableTime      = 0;
    m_iInvulnerableTimeRemaining = -1;
    m_fSpawnTimeLeft             = 0;
    m_bWaitingForRespawn         = false;
    m_bShouldRespawn             = false;
    m_bDeathSpectator            = false;

    m_vViewPos = vec_zero;

    //
    // Added in OPM
    //====
    m_bFrozen  = false;
    animDoneVM = true;
    m_fVMAtime = 0;
    //====

    for (int i = 0; i < MAX_SPEED_MULTIPLIERS; i++) {
        speed_multiplier[i] = 1.0f;
    }
}

Player::~Player()
{
    int          i, num;
    Conditional *cond;

    num = legs_conditionals.NumObjects();
    for (i = num; i > 0; i--) {
        cond = legs_conditionals.ObjectAt(i);
        delete cond;
    }

    num = torso_conditionals.NumObjects();
    for (i = num; i > 0; i--) {
        cond = torso_conditionals.ObjectAt(i);
        delete cond;
    }

    legs_conditionals.FreeObjectList();
    torso_conditionals.FreeObjectList();

    // Added in 2.11
    //  Make sure to clean turret stuff up
    //  when the player is deleted
    RemoveFromVehiclesAndTurrets();

    entflags &= ~ECF_PLAYER;
}

static qboolean logfile_started = qfalse;

void Player::Init(void)
{
    InitClient();
    InitPhysics();
    InitPowerups();
    InitWorldEffects();
    InitSound();
    InitView();
    InitState();
    InitEdict();
    InitMaxAmmo();
    InitWeapons();
    InitInventory();
    InitHealth();
    InitStats();
    InitModel();
    InitInvulnerable();

    LoadStateTable();

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        InitDeathmatch();
    } else if (!LoadingSavegame) {
        ChooseSpawnPoint();
        JoinNearbySquads();
    }

    // make sure we put the player back into the world
    link();
    logfile_started = qfalse;

    // notify scripts for the spawning player
    parm.other = this;
    parm.owner = this;
    level.Unregister(STRING_PLAYERSPAWN);

    //
    // Added in OPM
    //
    if (!m_bConnected) {
        m_bConnected = true;

        Event *ev = new Event;
        ev->AddEntity(this);
        scriptedEvents[SE_CONNECTED].Trigger(ev);
    }

    Spawned();
}

void Player::InitStats(void)
{
    m_iNumObjectives       = 0;
    m_iObjectivesCompleted = 0;
    m_iNumHitsTaken        = 0;
    m_iNumEnemiesKilled    = 0;
    m_iNumObjectsDestroyed = 0;
}

void Player::InitEdict(void)
{
    // entity state stuff
    setSolidType(SOLID_BBOX);
    if (m_bSpectator) {
        //
        // 2.0: always noclip when spectating
        //
        setMoveType(MOVETYPE_NOCLIP);
    } else {
        setMoveType(MOVETYPE_WALK);
    }

    setSize(Vector(-16, -16, 0), Vector(16, 16, 72));

    edict->clipmask   = MASK_PLAYERSOLID;
    edict->r.ownerNum = ENTITYNUM_NONE;

    // clear entity state values
    edict->s.eFlags   = 0;
    edict->s.wasframe = 0;

    // players have precise shadows
    edict->s.renderfx |= RF_SHADOW_PRECISE | RF_SHADOW;
}

void Player::InitSound(void)
{
    //
    // reset the music
    //
    client->ps.current_music_mood  = mood_normal;
    client->ps.fallback_music_mood = mood_normal;
    ChangeMusic("normal", "normal", false);

    client->ps.music_volume           = 1.0;
    client->ps.music_volume_fade_time = 0.0;
    ChangeMusicVolume(1.0, 0.0);

    music_forced = false;

    // Reset the reverb stuff

    client->ps.reverb_type  = eax_generic;
    client->ps.reverb_level = 0;
    SetReverb(client->ps.reverb_type, client->ps.reverb_level);
}

void Player::InitClient(void)
{
    client_persistant_t saved;

    // deathmatch wipes most client data every spawn
    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        char       userinfo[MAX_INFO_STRING];
        char       dm_primary[MAX_QPATH];
        float      enterTime   = client->pers.enterTime;
        teamtype_t team        = client->pers.teamnum;
        int        round_kills = client->pers.round_kills;

        memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
        memcpy(dm_primary, client->pers.dm_primary, sizeof(dm_primary));
        G_InitClientPersistant(client);
        G_ClientUserinfoChanged(edict, userinfo);

        memcpy(client->pers.dm_primary, dm_primary, sizeof(client->pers.dm_primary));
        client->pers.enterTime   = enterTime;
        client->pers.teamnum     = team;
        client->pers.round_kills = round_kills;
    }

    // clear everything but the persistant data and fov
    saved = client->pers;

    memset(client, 0, sizeof(*client));
    client->pers = saved;

    client->ps.clientNum   = client - game.clients;
    client->lastActiveTime = level.inttime;
    client->ps.commandTime = level.svsTime;

    SetStopwatch(0);

#ifdef OPM_FEATURES
    m_bShowingHint = false;
#endif
}

void Player::InitState(void)
{
    gibbed       = false;
    pain         = 0;
    nextpaintime = 0;

    m_fMineDist      = 1000;
    m_fMineCheckTime = 0;
    m_sDmPrimary     = "";

    knockdown     = false;
    pain_dir      = PAIN_NONE;
    pain_type     = MOD_NONE;
    pain_location = -2;
    takedamage    = DAMAGE_AIM;
    deadflag      = DEAD_NO;
    flags &= ~FL_TEAMSLAVE;
    flags |= (FL_POSTTHINK | FL_THINK | FL_DIE_EXPLODE | FL_BLOOD);
    m_iMovePosFlags = MPF_POSITION_STANDING;

    if (!com_blood->integer) {
        flags &= ~(FL_DIE_EXPLODE | FL_BLOOD);
    }
}

void Player::InitHealth(void)
{
    static cvar_t *pMaxHealth = gi.Cvar_Get("g_maxplayerhealth", "250", 0);
    static cvar_t *pDMHealth  = gi.Cvar_Get("g_playerdmhealth", "100", 0);

    // Don't do anything if we're loading a server game.
    // This is either a loadgame or a restart
    if (LoadingSavegame) {
        return;
    }

    if (g_gametype->integer == GT_SINGLE_PLAYER && !g_realismmode->integer) {
        max_health = pMaxHealth->integer;
    } else if (g_gametype->integer != GT_SINGLE_PLAYER && pDMHealth->integer > 0) {
        max_health = pDMHealth->integer;
    } else {
        // reset the health values
        max_health = 100;
    }

    health = max_health;

    // 2.0:
    //  Make sure to clear the heal rate and the dead flag when respawning
    //
    m_fHealRate = 0;
    edict->s.eFlags &= ~EF_DEAD;

    // Fixed in OPM
    //  This avoid losing weapons when dying and then immediately respawning
    CancelEventsOfType(EV_Player_DMDeathDrop);
    //  And this prevents the player from dying when respawning immediately after getting killed
    CancelEventsOfType(EV_Player_Dead);
}

void Player::InitModel(void)
{
    // 2.0:
    //  Make sure to detach from any object before initializing
    //  To prevent any glitches
    RemoveFromVehiclesAndTurrets();
    UnattachFromLadder(NULL);

    gi.clearmodel(edict);

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        setModel("models/player/" + str(g_playermodel->string) + ".tik");
    } else if (dm_team == TEAM_AXIS) {
        if (Q_stricmpn(client->pers.dm_playergermanmodel, "german", 6)
            && Q_stricmpn(client->pers.dm_playergermanmodel, "axis", 4)
            //
            // 2.30 models
            //
            && Q_stricmpn(client->pers.dm_playergermanmodel, "it", 2)
            && Q_stricmpn(client->pers.dm_playergermanmodel, "sc", 2)) {
            setModel("models/player/german_wehrmacht_soldier.tik");
        } else {
            setModel("models/player/" + str(client->pers.dm_playergermanmodel) + ".tik");
        }
    } else {
        if (Q_stricmpn(client->pers.dm_playermodel, "american", 8)
            && Q_stricmpn(client->pers.dm_playermodel, "allied", 6)) {
            setModel("models/player/american_army.tik");
        } else {
            setModel("models/player/" + str(client->pers.dm_playermodel) + ".tik");
        }
    }

    //
    // Fallback to a default model if not found
    //
    if (!edict->tiki) {
        if (dm_team == TEAM_AXIS) {
            setModel("models/player/german_wehrmacht_soldier.tik");
        } else {
            setModel("models/player/american_army.tik");
        }
    }

    SetControllerTag(HEAD_TAG, gi.Tag_NumForName(edict->tiki, "Bip01 Head"));
    SetControllerTag(TORSO_TAG, gi.Tag_NumForName(edict->tiki, "Bip01 Spine2"));
    SetControllerTag(ARMS_TAG, gi.Tag_NumForName(edict->tiki, "Bip01 Spine1"));
    SetControllerTag(PELVIS_TAG, gi.Tag_NumForName(edict->tiki, "Bip01 Pelvis"));

    if (g_gametype->integer != GT_SINGLE_PLAYER && IsSpectator()) {
        hideModel();
    } else {
        showModel();
    }

    if (GetActiveWeapon(WEAPON_MAIN)) {
        // Show the arms
        edict->s.eFlags &= ~EF_UNARMED;
    } else {
        edict->s.eFlags |= EF_UNARMED;
    }

    edict->s.eFlags &= ~EF_ANY_TEAM;

    if (dm_team == TEAM_ALLIES) {
        edict->s.eFlags |= EF_ALLIES;
    } else if (dm_team == TEAM_AXIS) {
        edict->s.eFlags |= EF_AXIS;
    }

    G_SetClientConfigString(edict);

    client->ps.iViewModelAnim        = 0;
    client->ps.iViewModelAnimChanged = 0;

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        if (dm_team == TEAM_AXIS) {
            if (m_voiceType <= PVT_AXIS_START || m_voiceType >= PVT_AXIS_END) {
                m_voiceType = PVT_AXIS_GERMAN;
            }
        } else {
            if (m_voiceType <= PVT_ALLIED_START || m_voiceType >= PVT_ALLIED_END) {
                m_voiceType = PVT_ALLIED_AMERICAN;
            }
        }
    } else {
        if (dm_team == TEAM_AXIS) {
            if (m_voiceType >= PVT_AXIS_END) {
                m_voiceType = PVT_AXIS_AXIS4;
            }
        } else {
            if (m_voiceType >= PVT_ALLIED_END) {
                m_voiceType = PVT_ALLIED_PILOT;
            }
        }
    }

    InitModelFps();
}

void Player::InitModelFps(void)
{
    char  model_name[MAX_STRING_TOKENS];
    char *model_replace;

    Q_strncpyz(model_name, model.c_str(), sizeof(model_name));
    size_t len = strlen(model_name);

    model_replace = model_name + len - 4;

    Q_strncpyz(model_replace, "_fps.tik", sizeof(model_name) - (model_replace - model_name));

    m_fpsTiki = gi.modeltiki(model_name);
}

void Player::InitPhysics(void)
{
    // Physics stuff
    oldvelocity  = vec_zero;
    velocity     = vec_zero;
    old_v_angle  = v_angle;
    gravity      = 1.0;
    falling      = false;
    mediumimpact = false;
    hardimpact   = false;
    setContents(CONTENTS_BODY);
    mass = 500;
    memset(&last_ucmd, 0, sizeof(last_ucmd));

    client->ps.groundTrace.fraction = 1.0f;

    // Added in OPM
    //  Prevent the player from being stuck
    flags &= ~FL_PARTIAL_IMMOBILE;
}

void Player::InitPowerups(void)
{
    // powerups
    poweruptimer = 0;
    poweruptype  = 0;
}

void Player::InitWorldEffects(void)
{
    // world effects
    next_painsound_time = 0;
}

void Player::InitMaxAmmo(void)
{
    GiveAmmo("pistol", 0, 200);
    GiveAmmo("rifle", 0, 200);
    GiveAmmo("smg", 0, 300);
    GiveAmmo("mg", 0, 500);
    GiveAmmo("grenade", 0, 5);
    GiveAmmo("agrenade", 0, 5);
    GiveAmmo("heavy", 0, 5);
    GiveAmmo("shotgun", 0, 50);

    if (g_target_game >= target_game_e::TG_MOHTT) {
        //
        // Team tactics ammunition
        //
        GiveAmmo("landmine", 0, 5);
    }

    if (g_target_game >= target_game_e::TG_MOHTA) {
        //
        // Team assault ammunition
        //
        GiveAmmo("smokegrenade", 0, 5);
        GiveAmmo("asmokegrenade", 0, 5);
        GiveAmmo("riflegrenade", 0, 3);
    }
}

void Player::InitWeapons(void)
{
    // Don't do anything if we're loading a server game.
    // This is either a loadgame or a restart
    if (LoadingSavegame) {
        return;
    }

    // Added in OPM
    //  This fixes a bug where player can charge then go to spectator or respawn.
    //  The grenade would immediately explode when firing
    charge_start_time = 0;
}

void Player::InitInventory(void) {}

void Player::InitView(void)
{
    // view stuff
    camera  = NULL;
    v_angle = vec_zero;
    SetViewAngles(v_angle);
    viewheight = DEFAULT_VIEWHEIGHT;

    // blend stuff
    damage_blend = vec_zero;
}

void Player::ChooseSpawnPoint(void)
{
    // set up the player's spawn location
    PlayerStart *p = SelectSpawnPoint(this);
    setOrigin(p->origin + Vector(0, 0, 1));
    origin.copyTo(edict->s.origin2);
    edict->s.renderfx |= RF_FRAMELERP;

    if (g_gametype->integer != GT_SINGLE_PLAYER && !IsSpectator()) {
        KillBox(this);
    }

    setAngles(p->angles);
    SetViewAngles(p->angles);
    SetupView();

    VectorCopy(origin, client->ps.vEyePos);
    client->ps.vEyePos[2] += client->ps.viewheight;

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        for (int i = 1; i <= 4; i++) {
            Event *ev = new Event(EV_SetViewangles);
            ev->AddVector(p->angles);
            PostEvent(ev, level.frametime * i);
        }
    }

    if (p->m_bDeleteOnSpawn) {
        delete p;
    } else {
        p->Unregister(STRING_SPAWN);
        m_pLastSpawnpoint = p;
    }
}

void Player::EndLevel(Event *ev)
{
    InitPowerups();
    if (health > max_health) {
        health = max_health;
    }

    if (health < 1) {
        health = 1;
    }
}

void Player::Respawn(Event *ev)
{
    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        bool bOldVoted;

        if (health <= 0.0f) {
            DeadBody(NULL);
            hideModel();
        }

        respawn_time = level.time;

        // This is not present in MOHAA
        ProcessEvent(EV_Player_UnattachFromLadder);
        RemoveFromVehiclesAndTurrets();

        FreeInventory();

        // Save the previous vote value
        bOldVoted = client->ps.voted;
        Init();
        client->ps.voted = bOldVoted;
        client->ps.pm_flags |= PMF_RESPAWNED;

        SetInvulnerable();

        // Clear the center message
        gi.centerprintf(edict, " ");
        m_bShouldRespawn = false;
    } else {
        if (g_lastsave->string && *g_lastsave->string) {
            gi.SendConsoleCommand("loadlastgame\n");
        } else {
            gi.SendConsoleCommand("restart\n");
        }

        logfile_started = qfalse;
    }

    //
    // Added in OPM
    //
    Unregister(STRING_RESPAWN);
}

void Player::SetDeltaAngles(void)
{
    int i;

    // Use v_angle since we may be in a camera
    for (i = 0; i < 3; i++) {
        client->ps.delta_angles[i] = ANGLE2SHORT(v_angle[i]);
    }
}

void Player::Obituary(Entity *attacker, Entity *inflictor, int meansofdeath, int iLocation)
{
    str      s1;
    str      s2;
    qboolean bDispLocation;

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }

    s1            = "x";
    s2            = "x";
    bDispLocation = qfalse;

    if (attacker == this) {
        //
        // Player killed themselves
        //
        switch (meansofdeath) {
        case MOD_SUICIDE:
            s1 = "took himself out of commision";
            break;
        case MOD_LAVA:
            s1 = "was burned to a crisp";
            break;
        case MOD_SLIME:
            s1 = "was melted to nothing";
            break;
        case MOD_FALLING:
            s1 = "cratered";
            break;
        case MOD_EXPLOSION:
            s1 = "blew himself up";
            break;
        case MOD_GRENADE:
            if (G_Random() >= 0.5f) {
                s1 = "played catch with himself";
            } else {
                s1 = "tripped on his own grenade";
            }
            break;
        case MOD_ROCKET:
            s1 = "rocketed himself";
            break;
        case MOD_BULLET:
            if (iLocation > -1) {
                s1 = "shot himself";
            } else {
                s1            = "shot himself in the";
                bDispLocation = qtrue;
            }
            break;
        case MOD_FAST_BULLET:
            if (iLocation == HITLOC_GENERAL || iLocation == HITLOC_MISS) {
                s1 = "shot himself";
            } else {
                s1            = "shot himself in the";
                bDispLocation = qtrue;
            }
            break;
        case MOD_LANDMINE:
            s1 = "was hoist on his own pitard";
            break;
        default:
            s1 = "died";
            break;
        }

        if (bDispLocation && g_obituarylocation->integer) {
            str szConv2 = s2 + " in the " + G_LocationNumToDispString(iLocation);

            if (dedicated->integer) {
                gi.Printf("%s %s\n", client->pers.netname, gi.LV_ConvertString(szConv2.c_str()));
            }

            G_PrintDeathMessage(s1, szConv2.c_str(), "x", client->pers.netname, this, "s");
        } else {
            if (dedicated->integer) {
                gi.Printf("%s %s\n", client->pers.netname, gi.LV_ConvertString(s1.c_str()));
            }

            G_PrintDeathMessage(s1.c_str(), s2.c_str(), "x", client->pers.netname, this, "s");
        }
    } else if (attacker && attacker->client) {
        //
        // Killed by another player
        //
        Weapon *pAttackerWeap = NULL;

        if (attacker->IsSubclassOfPlayer()) {
            pAttackerWeap = ((Player *)attacker)->GetActiveWeapon(WEAPON_MAIN);
        }

        switch (meansofdeath) {
        case MOD_CRUSH:
        case MOD_CRUSH_EVERY_FRAME:
            s1 = "was crushed by";
            break;
        case MOD_TELEFRAG:
            s1 = "was telefragged by";
            break;
        case MOD_LAVA:
        case MOD_FIRE:
        case MOD_ON_FIRE:
            s1 = "was burned up by";
            break;
        case MOD_SLIME:
            s1 = "was melted by";
            break;
        case MOD_FALLING:
            s1 = "was pushed over the edge by";
            break;
        case MOD_EXPLOSION:
            s1 = "was blown away by";
            break;
        case MOD_GRENADE:
            if (G_Random() >= 0.5f) {
                s1 = "tripped on";
                s2 = "'s grenade";
            } else {
                s1 = "is picking";
                s2 = "'s shrapnel out of his teeth";
            }
            break;
        case MOD_ROCKET:
            s1 = "took";
            if (G_Random() >= 0.5f) {
                s2 = "'s rocket right in the kisser";
            } else {
                s2 = "'s rocket in the face";
            }
            break;
        case MOD_IMPACT:
            s1 = "was knocked out by";
            break;
        case MOD_BULLET:
        case MOD_FAST_BULLET:
            s1 = "was shot by";

            if (pAttackerWeap) {
                if (pAttackerWeap->GetWeaponClass() & WEAPON_CLASS_PISTOL) {
                    s1 = "was gunned down by";
                } else if (pAttackerWeap->GetWeaponClass() & WEAPON_CLASS_RIFLE) {
                    if (pAttackerWeap->GetZoom()) {
                        s1 = "was sniped by";
                    } else {
                        s1 = "was rifled by";
                    }
                } else if (pAttackerWeap->GetWeaponClass() & WEAPON_CLASS_SMG) {
                    s1 = "was perforated by";
                    s2 = "'s' SMG";
                } else if (pAttackerWeap->GetWeaponClass() & WEAPON_CLASS_MG) {
                    s1 = "was machine-gunned by";
                }
            }

            if (iLocation > -1) {
                bDispLocation = qtrue;
            }
            break;
        case MOD_VEHICLE:
            s1 = "was run over by";
            break;
        case MOD_IMPALE:
            s1 = "was impaled by";
            break;
        case MOD_BASH:
            if (G_Random() >= 0.5f) {
                s1 = "was bashed by";
            } else {
                s1 = "was clubbed by";
            }
            break;
        case MOD_SHOTGUN:
            if (G_Random() >= 0.5f) {
                s1 = "was hunted down by";
            } else {
                s1 = "was pumped full of buckshot by";
            }
            break;
        case MOD_LANDMINE:
            s1 = "stepped on";
            s2 = "'s landmine";
            break;
        default:
            s1 = "was killed by";
            break;
        }

        if (bDispLocation && g_obituarylocation->integer) {
            str szConv2 = s2 + " in the " + G_LocationNumToDispString(iLocation);

            G_PrintDeathMessage(
                s1.c_str(), szConv2.c_str(), attacker->client->pers.netname, client->pers.netname, this, "p"
            );

            if (dedicated->integer) {
                str szLoc1, szLoc2;

                szLoc1 = gi.LV_ConvertString(s1.c_str());
                if (s2 == 'x') {
                    gi.Printf("%s %s %s\n", client->pers.netname, szLoc1.c_str(), attacker->client->pers.netname);
                } else {
                    szLoc2 = gi.LV_ConvertString(szConv2.c_str());
                    gi.Printf(
                        "%s %s %s%s\n",
                        client->pers.netname,
                        szLoc1.c_str(),
                        attacker->client->pers.netname,
                        szLoc2.c_str()
                    );
                }
            }
        } else {
            G_PrintDeathMessage(
                s1.c_str(), s2.c_str(), attacker->client->pers.netname, client->pers.netname, this, "p"
            );

            if (dedicated->integer) {
                str szLoc1, szLoc2;

                szLoc1 = gi.LV_ConvertString(s1.c_str());
                if (s2 == 'x') {
                    gi.Printf("%s %s %s\n", client->pers.netname, szLoc1.c_str(), attacker->client->pers.netname);
                } else {
                    szLoc2 = gi.LV_ConvertString(s2.c_str());
                    gi.Printf(
                        "%s %s %s%s\n",
                        client->pers.netname,
                        szLoc1.c_str(),
                        attacker->client->pers.netname,
                        szLoc2.c_str()
                    );
                }
            }
        }
    } else {
        //
        // No attacker and not self
        //
        switch (meansofdeath) {
        case MOD_LAVA:
            s1 = "was burned to a crisp";
            break;
        case MOD_SLIME:
            s1 = "was melted to nothing";
            break;
        case MOD_FALLING:
            s1 = "cratered";
            break;
        case MOD_EXPLOSION:
            s1 = "blew up";
            break;
        case MOD_GRENADE:
            s1 = "caught some shrapnel";
            break;
        case MOD_ROCKET:
            s1 = "caught a rocket";
            break;
        case MOD_BULLET:
        case MOD_FAST_BULLET:
            if (iLocation == HITLOC_GENERAL || iLocation == HITLOC_MISS) {
                s1 = "was shot";
            } else {
                s1            = "was shot in the";
                bDispLocation = qtrue;
            }
            break;
        case MOD_LANDMINE:
            s1 = "stepped on a land mine";
            break;
        default:
            s1 = "died";
            break;
        }

        if (bDispLocation && g_obituarylocation->integer) {
            str szConv2 = s2 + " in the " + G_LocationNumToDispString(iLocation);

            G_PrintDeathMessage(s1.c_str(), szConv2.c_str(), "x", client->pers.netname, this, "w");

            if (dedicated->integer) {
                gi.Printf("%s %s\n", client->pers.netname, gi.LV_ConvertString(s1.c_str()));
            }
        } else {
            G_PrintDeathMessage(s1.c_str(), s2.c_str(), "x", client->pers.netname, this, "w");

            if (dedicated->integer) {
                gi.Printf("%s %s\n", client->pers.netname, gi.LV_ConvertString(s1.c_str()));
            }
        }
    }
}

void Player::Dead(Event *ev)
{
    if (deadflag == DEAD_DEAD) {
        return;
    }

    health   = 0;
    deadflag = DEAD_DEAD;

    edict->s.renderfx &= ~RF_SHADOW;
    server_new_buttons = 0;

    CancelEventsOfType(EV_Player_Dead);

    // stop animating
    StopPartAnimating(legs);

    // pause the torso anim
    PausePartAnim(torso);

    partAnim[torso] = "";

    if (m_fPainBlend != 0) {
        // Clear pain blend
        StopAnimating(ANIMSLOT_PAIN);
        edict->s.frameInfo[ANIMSLOT_PAIN].weight = 0;
        m_fPainBlend                             = 0;
        animdone_Pain                            = false;
    }

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        if (dmManager.AllowRespawn()) {
            respawn_time = level.time + 1.0f;
        } else {
            respawn_time = level.time + 2.0f;
        }
    } else if (level.current_map && *level.current_map) {
        G_BeginIntermission(level.current_map, TRANS_LEVEL);
    } else {
        respawn_time = level.time + 1.f;
    }

    ZoomOff();

    if (ShouldForceSpectatorOnDeath()) {
        m_bDeathSpectator = true;

        Spectator();
        SetPlayerSpectateRandom();
    }
}

void Player::Killed(Event *ev)
{
    Entity *attacker;
    Entity *inflictor;
    int     meansofdeath;
    int     location;
    Event  *event;

    //
    // Added in OPM
    // This one is openmohaa-specific
    //  Custom killed event will do the job
    //
    if (m_killedLabel.IsSet()) {
        event = new Event(0, ev->NumArgs());
        for (int i = 1; i <= ev->NumArgs(); i++) {
            event->AddValue(ev->GetValue(i));
        }
        m_killedLabel.Execute(this, event);
        delete event;

        Unregister(STRING_DEATH);
        return;
    }

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        current_team->AddDeaths(this, 1);
    } else {
        AddDeaths(1);
    }

    attacker     = ev->GetEntity(1);
    inflictor    = ev->GetEntity(3);
    meansofdeath = ev->GetInteger(9);
    location     = ev->GetInteger(10);

    if (attacker && inflictor) {
        Obituary(attacker, inflictor, meansofdeath, location);
    }

    RemoveFromVehiclesAndTurrets();

    if (g_gametype->integer != GT_SINGLE_PLAYER && attacker && attacker->IsSubclassOfPlayer()) {
        static_cast<Player *>(attacker)->KilledPlayerInDeathmatch(this, (meansOfDeath_t)meansofdeath);
    }

    deadflag = DEAD_DYING;
    health   = 0;

    event = new Event(EV_Pain, 10);

    event->AddEntity(attacker);
    event->AddFloat(ev->GetFloat(2));
    event->AddEntity(inflictor);
    event->AddVector(ev->GetVector(4));
    event->AddVector(ev->GetVector(5));
    event->AddVector(ev->GetVector(6));
    event->AddInteger(ev->GetInteger(7));
    event->AddInteger(ev->GetInteger(8));
    event->AddInteger(ev->GetInteger(9));
    event->AddInteger(ev->GetInteger(10));

    ProcessEvent(event);

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        if (HasItem("Binoculars")) {
            takeItem("Binoculars");
        }

        PostEvent(EV_Player_DMDeathDrop, 0.1f);
        edict->s.eFlags |= EF_DEAD;
    }

    edict->clipmask = MASK_DEADSOLID;
    setContents(CONTENTS_CORPSE);
    setSolidType(SOLID_NOT);
    setMoveType(MOVETYPE_TOSS);

    angles.x = 0;
    angles.z = 0;
    setAngles(angles);

    //
    // change music
    //
    ChangeMusic("failure", "normal", true);

    takedamage = DAMAGE_NO;

    // Post a dead event just in case
    PostEvent(EV_Player_Dead, 5.0f);
    ZoomOff();

    if (g_voiceChat->integer) {
        if (m_voiceType == PVT_ALLIED_MANON) {
            //
            // manon_death doesn't exist in 2.0 anymore.
            // The code is left just in case
            //
            Sound("manon_death", CHAN_VOICE, -1.0f, 160, NULL, -1.0f, 1, 0, 1, 1200);
        } else {
            Sound("player_death");
        }
    } else {
        Sound("player_death");
    }

    if (m_fPainBlend) {
        //
        // 2.0: No more pain animation after death
        //
        animdone_Pain = true;
    }

    //
    // Added in OPM
    //  Scripted events
    //
    event = new Event(0, 11);

    event->AddEntity(ev->GetEntity(1));
    event->AddFloat(ev->GetFloat(2));
    event->AddEntity(ev->GetEntity(3));
    event->AddVector(ev->GetVector(4));
    event->AddVector(ev->GetVector(5));
    event->AddVector(ev->GetVector(6));
    event->AddInteger(ev->GetInteger(7));
    event->AddInteger(ev->GetInteger(8));
    event->AddInteger(ev->GetInteger(9));
    event->AddInteger(ev->GetInteger(10));
    event->AddEntity(this);

    scriptedEvents[SE_KILL].Trigger(event);

    Unregister(STRING_DEATH);
}

void Player::EventDMDeathDrop(Event *ev)
{
    Weapon   *weapon = GetActiveWeapon(WEAPON_MAIN);
    SpawnArgs args;
    ClassDef *cls;

    if (!m_bDontDropWeapons && weapon && weapon->IsSubclassOfWeapon()) {
        weapon->Drop();
    }

    args.setArg("model", "models/items/dm_50_healthbox.tik");

    cls = args.getClassDef();
    if (cls) {
        Item *item = (Item *)cls->newInstance();
        if (item) {
            if (item->IsSubclassOfItem()) {
                item->setModel("models/items/dm_50_healthbox.tik");

                item->SetOwner(this);
                item->ProcessPendingEvents();
                item->Drop();
            } else {
                // useless and not pickupable, delete it
                delete item;
            }
        }
    }

    FreeInventory();
}

void Player::EventStopwatch(Event *ev)
{
    stopWatchType_t eType = SWT_NORMAL;

    int iDuration = ev->GetInteger(1);
    if (iDuration < 0) {
        ScriptError("duration < 0");
    }

    if (ev->NumArgs() > 1) {
        eType = static_cast<stopWatchType_t>(ev->GetInteger(2));
    } else {
        eType = SWT_NORMAL;
    }

    SetStopwatch(iDuration, eType);
}

void Player::SetStopwatch(int iDuration, stopWatchType_t type)
{
    int  iStartTime;
    char szCmd[256];

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        if (type != SWT_NORMAL) {
            iStartTime = (int)(level.svsFloatTime * 1000.f);
        } else {
            iStartTime = 0;
            if (iDuration) {
                iStartTime = ceil(level.svsFloatTime * 1000.f);
            }
        }

        Com_sprintf(szCmd, sizeof(szCmd), "stopwatch %i %i %i", iStartTime, iDuration, type);
    } else {
        iStartTime = 0;
        if (iDuration) {
            iStartTime = (int)level.svsFloatTime;
        }

        Com_sprintf(szCmd, sizeof(szCmd), "stopwatch %i %i", iStartTime, iDuration);
    }

    gi.SendServerCommand(edict - g_entities, szCmd);
}

void Player::KilledPlayerInDeathmatch(Player *killed, meansOfDeath_t meansofdeath)
{
    DM_Team *pDMTeam;

    pDMTeam = killed->GetDM_Team();

    if (meansofdeath == MOD_TELEFRAG) {
        //
        // Added in OPM
        //  Telefrag isn't the fault of anyone
        //  so don't count any kill
        //
        return;
    }

    if (killed == this) {
        pDMTeam->AddKills(this, -1);
        gi.SendServerCommand(
            edict - g_entities, "print \"" HUD_MESSAGE_WHITE "%s\n\"", gi.LV_ConvertString("You killed yourself")
        );

        return;
    }

    if (pDMTeam == GetDM_Team() && g_gametype->integer >= GT_TEAM) {
        //
        // A teammate was killed
        //
        current_team->AddKills(this, -1);
        num_team_kills++;
    } else {
        current_team->AddKills(this, 1);
    }

    gi.SendServerCommand(
        edict - g_entities,
        "print \"" HUD_MESSAGE_WHITE "%s %s\n\"",
        gi.LV_ConvertString("You killed"),
        killed->client->pers.netname
    );
}

void Player::Pain(Event *ev)
{
    float   damage, yawdiff;
    Entity *attacker;
    int     meansofdeath;
    Vector  dir, pos, attack_angle;
    int     iLocation;

    attacker     = ev->GetEntity(1);
    damage       = ev->GetFloat(2);
    pos          = ev->GetVector(4);
    dir          = ev->GetVector(5);
    meansofdeath = ev->GetInteger(9);
    iLocation    = ev->GetInteger(10);

    if (!damage && !knockdown) {
        return;
    }

    client->ps.stats[STAT_LAST_PAIN] = damage;

    // Determine direction
    attack_angle = dir.toAngles();
    yawdiff      = angles[YAW] - attack_angle[YAW] + 180;
    yawdiff      = AngleNormalize180(yawdiff);

    if (yawdiff > -45 && yawdiff < 45) {
        pain_dir = PAIN_FRONT;
    } else if (yawdiff < -45 && yawdiff > -135) {
        pain_dir = PAIN_LEFT;
    } else if (yawdiff > 45 && yawdiff < 135) {
        pain_dir = PAIN_RIGHT;
    } else {
        pain_dir = PAIN_REAR;
    }

    pain_type     = (meansOfDeath_t)meansofdeath;
    pain_location = iLocation;

    // Only set the regular pain level if enough time since last pain has passed
    if (((level.time > nextpaintime) && take_pain) || IsDead()) {
        pain = damage;
    }

    // add to the damage inflicted on a player this frame
    // the total will be turned into screen blends and view angle kicks
    // at the end of the frame
    damage_blood += damage;
    damage_from += dir * damage;
    damage_yaw = dir.toYaw() * 10.0f;

    if (damage_yaw == client->ps.stats[STAT_DAMAGEDIR]) {
        if (damage_yaw < 1800.0f) {
            damage_yaw += 1.0f;
        } else {
            damage_yaw -= 1.0f;
        }
    }

    if (g_gametype->integer != GT_SINGLE_PLAYER && attacker && attacker->client && attacker != this) {
        gi.MSG_SetClient(attacker->edict - g_entities);
        if (IsDead()) {
            gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_NOTIFY_KILL));
        } else {
            gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_NOTIFY_HIT));
        }
        gi.MSG_EndCGM();
    }

    if (IsDead()) {
        return;
    }

    if (g_voiceChat->integer) {
        if (m_voiceType == PVT_ALLIED_MANON) {
            //
            // Should have been removed since 2.0
            //
            Sound("manon_pain", CHAN_DIALOG, -1, 160, NULL, -1, 1, 0, 1, 1200);
        } else {
            Sound("player_pain");
        }
    } else {
        Sound("player_pain");
    }
}

void Player::DoUse(Event *ev)
{
    gentity_t *hit;
    int        touch[MAX_GENTITIES];
    int        num;
    int        i;
    bool       bWasInTurretOrVehicle;

    if (g_gametype->integer != GT_SINGLE_PLAYER && IsSpectator()) {
        // Prevent using stuff while spectating
        return;
    }

    if (IsDead()) {
        // Dead players mustn't use
        return;
    }

    if (edict->r.svFlags & SVF_NOCLIENT) {
        // Fixed in OPM
        //  Clients that are not sent to other clients cannot use objects.
        //  Some mods make players non-solid, hide them and turn physics off
        //  as a way to spectate other players or for cinematics.
        //  This prevent players to use objects such as doors
        return;
    }

    bWasInTurretOrVehicle = m_pVehicle || m_pTurret;

    if (bWasInTurretOrVehicle) {
        RemoveFromVehiclesAndTurretsInternal();
        return;
    }

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        if ((buttons & BUTTON_ATTACKLEFT) || (buttons & BUTTON_ATTACKRIGHT)) {
            //
            // Added in 2.0
            //  Only allow use if the player isn't holding attack buttons
            //
            return;
        }
    }

    num = getUseableEntities(touch, MAX_GENTITIES, true);

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        // Fixed in 2.0
        //  Since 2.0, the loop stops when the player
        //  uses a turret, this prevents the turret from being deleted
        //  after being attached to the player
        //
        for (i = 0; i < num; i++) {
            hit = &g_entities[touch[i]];

            if (!hit->inuse) {
                continue;
            }

            Event *event = new Event(EV_Use);
            event->AddListener(this);

            hit->entity->ProcessEvent(event);

            if (m_pVehicle || m_pTurret) {
                break;
            }
        }
    } else {
        //
        // Backward compatibility
        // It still allows 1.11 SP to work properly
        // Such as in m1l1 when the player must man the mounted machine gun
        for (i = 0; i < num; i++) {
            hit = &g_entities[touch[i]];

            if (!hit->inuse) {
                continue;
            }

            Event *event = new Event(EV_Use);
            event->AddListener(this);

            hit->entity->ProcessEvent(event);
        }
    }

    if (!bWasInTurretOrVehicle && m_pVehicle) {
        //
        // Added in 2.30
        //  Make the vehicle also invincible if the player is invincible
        //
        if (flags & FL_GODMODE) {
            m_pVehicle->flags |= FL_GODMODE;
        } else {
            m_pVehicle->flags &= ~FL_GODMODE;
        }
    }
}

void Player::TouchStuff(pmove_t *pm)
{
    gentity_t *other;
    Event     *event;
    int        i;
    int        j;

    //
    // clear out any conditionals that are controlled by touching
    //
    toucheduseanim = NULL;

    if (getMoveType() != MOVETYPE_NOCLIP) {
        G_TouchTriggers(this);
    }

    // touch other objects
    for (i = 0; i < pm->numtouch; i++) {
        other = &g_entities[pm->touchents[i]];

        for (j = 0; j < i; j++) {
            gentity_t *ge = &g_entities[j];

            if (ge == other) {
                break;
            }
        }

        if (j != i) {
            // duplicated
            continue;
        }

        // Don't bother touching the world
        if ((!other->entity) || (other->entity == world)) {
            continue;
        }

        event = new Event(EV_Touch);
        event->AddEntity(this);
        other->entity->ProcessEvent(event);

        event = new Event(EV_Touch);
        event->AddEntity(other->entity);
        ProcessEvent(event);
    }
}

void Player::GetMoveInfo(pmove_t *pm)
{
    moveresult = pm->moveresult;

    if (!deadflag || (g_gametype->integer != GT_SINGLE_PLAYER && IsSpectator())) {
        v_angle[0] = pm->ps->viewangles[0];
        v_angle[1] = pm->ps->viewangles[1];
        v_angle[2] = pm->ps->viewangles[2];

        if (moveresult == MOVERESULT_TURNED) {
            angles.y = v_angle[1];
            setAngles(angles);
            SetViewAngles(angles);
        }
    }

    setOrigin(Vector(pm->ps->origin[0], pm->ps->origin[1], pm->ps->origin[2]));

    if (pm->ps->groundEntityNum != ENTITYNUM_NONE) {
        float backoff;
        float change;
        int   i;

        backoff = DotProduct(pm->ps->groundTrace.plane.normal, pm->ps->velocity);

        for (i = 0; i < 3; i++) {
            change = pm->ps->groundTrace.plane.normal[i] * backoff;
            pm->ps->velocity[i] -= change;
        }
    }

    // Set the ground entity
    groundentity = NULL;
    if (pm->ps->groundEntityNum != ENTITYNUM_NONE) {
        groundentity = &g_entities[pm->ps->groundEntityNum];
        airspeed     = 200;

        if (!groundentity->entity || groundentity->entity->getMoveType() == MOVETYPE_NONE) {
            m_vPushVelocity = vec_zero;
        }

        //
        // Fixed in OPM
        //  Disable predictions when the groundentity is moving up/down, looks like shaky otherwise
        if (groundentity->entity && groundentity->entity != this && groundentity->entity->velocity[2] != 0) {
            pm->ps->pm_flags |= PMF_NO_PREDICTION;
        }
    } else if (m_pGlueMaster) {
        // Added in OPM
        //  Use the glue master for the ground entity to make the viewmodel will stay still
        pm->ps->groundEntityNum = m_pGlueMaster->entnum;
    }

    velocity = Vector(pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]);

    if ((client->ps.pm_flags & PMF_FROZEN) || (client->ps.pm_flags & PMF_NO_MOVE)) {
        velocity = vec_zero;
    } else {
        setSize(pm->mins, pm->maxs);
        viewheight = pm->ps->viewheight;
    }

    // water type and level is set in the predicted code
    waterlevel = pm->waterlevel;
    watertype  = pm->watertype;
}

void Player::SetMoveInfo(pmove_t *pm, usercmd_t *ucmd)
{
    Vector move;

    // set up for pmove
    memset(pm, 0, sizeof(pmove_t));

    velocity.copyTo(client->ps.velocity);

    pm->ps = &client->ps;

    if (ucmd) {
        pm->cmd = *ucmd;
    }

    if (sv_drawtrace->integer <= 1) {
        pm->trace = gi.trace;
    } else {
        pm->trace = &G_PMDrawTrace;
    }

    pm->tracemask     = MASK_PLAYERSOLID;
    pm->pointcontents = gi.pointcontents;

    pm->ps->origin[0] = origin.x;
    pm->ps->origin[1] = origin.y;
    pm->ps->origin[2] = origin.z;

    /*
    pm->mins[0] = mins.x;
    pm->mins[1] = mins.y;
    pm->mins[2] = mins.z;

    pm->maxs[0] = maxs.x;
    pm->maxs[1] = maxs.y;
    pm->maxs[2] = maxs.z;
    */

    pm->ps->velocity[0] = velocity.x;
    pm->ps->velocity[1] = velocity.y;
    pm->ps->velocity[2] = velocity.z;

    pm->pmove_fixed = pmove_fixed->integer;
    pm->pmove_msec  = pmove_msec->integer;

    if (pmove_msec->integer < 8) {
        pm->pmove_msec = 8;
    } else if (pmove_msec->integer > 33) {
        pm->pmove_msec = 33;
    }

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        if (g_gametype->integer != GT_SINGLE_PLAYER) {
            //
            // Added in 2.0
            // In multiplayer mode, specify if the player can lean while moving
            //
            if (dmflags->integer & DF_ALLOW_LEAN_MOVEMENT) {
                pm->alwaysAllowLean = qtrue;
            } else {
                pm->alwaysAllowLean = qfalse;
            }
        } else {
            pm->alwaysAllowLean = qfalse;
        }

        pm->leanMax          = 45.f;
        pm->leanAdd          = 6.f;
        pm->leanRecoverSpeed = 8.5f;
        pm->leanSpeed        = 2.f;
    } else {
        pm->alwaysAllowLean = qtrue;
        if (g_gametype->integer != GT_SINGLE_PLAYER) {
            pm->leanMax = 40.f;
        } else {
            // Don't allow lean in single-player, like in the original game
            pm->leanMax = 0;
        }

        pm->leanAdd          = 10.f;
        pm->leanRecoverSpeed = 15.f;
        pm->leanSpeed        = 4.f;
    }

    pm->protocol = g_protocol;

    // Added in OPM
    //  Initialize the ground entity
    pm->ps->groundEntityNum = ENTITYNUM_NONE;
}

pmtype_t Player::GetMovePlayerMoveType(void)
{
    if (getMoveType() == MOVETYPE_NOCLIP || IsSpectator()) {
        return PM_NOCLIP;
    } else if (deadflag) {
        return PM_DEAD;
    } else if (movecontrol == MOVECONTROL_CLIMBWALL) {
        return PM_CLIMBWALL;
    } else {
        return PM_NORMAL;
    }
}

void Player::CheckGround(void)
{
    pmove_t pm;

    SetMoveInfo(&pm, current_ucmd);
    Pmove_GroundTrace(&pm);
    GetMoveInfo(&pm);
}

qboolean Player::AnimMove(Vector& move, Vector *endpos)
{
    Vector  up;
    Vector  down;
    trace_t trace;
    int     mask;
    Vector  start(origin);
    Vector  end(origin + move);

    mask = MASK_PLAYERSOLID;

    // test the player position if they were a stepheight higher
    trace = G_Trace(start, mins, maxs, end, this, mask, true, "AnimMove");
    if (trace.fraction < 1) {
        if ((movecontrol == MOVECONTROL_HANGING) || (movecontrol == MOVECONTROL_CLIMBWALL)) {
            up = origin;
            up.z += move.z;
            trace = G_Trace(origin, mins, maxs, up, this, mask, true, "AnimMove");
            if (trace.fraction < 1) {
                if (endpos) {
                    *endpos = origin;
                }
                return qfalse;
            }

            origin = trace.endpos;
            end    = origin;
            end.x += move.x;
            end.y += move.y;

            trace = G_Trace(origin, mins, maxs, end, this, mask, true, "AnimMove");
            if (endpos) {
                *endpos = trace.endpos;
            }

            return (trace.fraction > 0);
        } else {
            return TestMove(move, endpos);
        }
    } else {
        if (endpos) {
            *endpos = trace.endpos;
        }

        return qtrue;
    }
}

qboolean Player::TestMove(Vector& move, Vector *endpos)
{
    trace_t trace;
    Vector  pos(origin + move);

    trace = G_Trace(origin, mins, maxs, pos, this, MASK_PLAYERSOLID, true, "TestMove");
    if (trace.allsolid) {
        // player is completely trapped in another solid
        if (endpos) {
            *endpos = origin;
        }
        return qfalse;
    }

    if (trace.fraction < 1.0f) {
        Vector up(origin);
        up.z += STEPSIZE;

        trace = G_Trace(origin, mins, maxs, up, this, MASK_PLAYERSOLID, true, "TestMove");
        if (trace.fraction == 0.0f) {
            if (endpos) {
                *endpos = origin;
            }
            return qfalse;
        }

        Vector temp(trace.endpos);
        Vector end(temp + move);

        trace = G_Trace(temp, mins, maxs, end, this, MASK_PLAYERSOLID, true, "TestMove");
        if (trace.fraction == 0.0f) {
            if (endpos) {
                *endpos = origin;
            }
            return qfalse;
        }

        temp = trace.endpos;

        Vector down(trace.endpos);
        down.z = origin.z;

        trace = G_Trace(temp, mins, maxs, down, this, MASK_PLAYERSOLID, true, "TestMove");
    }

    if (endpos) {
        *endpos = trace.endpos;
    }

    return qtrue;
}

float Player::TestMoveDist(Vector& move)
{
    Vector endpos;

    TestMove(move, &endpos);
    endpos -= origin;

    return endpos.length();
}

static Vector vec_up = Vector(0, 0, 1);

void Player::CheckMoveFlags(void)
{
    trace_t trace;
    Vector  start;
    Vector  end;
    float   oldsp;
    Vector  olddir(oldvelocity.x, oldvelocity.y, 0);

    //
    // Check if moving forward will cause the player to fall
    //
    start = origin + yaw_forward * 52.0f;
    end   = start;
    end.z -= STEPSIZE * 2;

    trace   = G_Trace(start, mins, maxs, end, this, MASK_PLAYERSOLID, true, "CheckMoveFlags");
    canfall = (trace.fraction >= 1.0f);

    if (!groundentity && !(client->ps.walking)) {
        falling      = true;
        hardimpact   = false;
        mediumimpact = false;
    } else {
        falling      = false;
        mediumimpact = oldvelocity.z <= -180.0f;
        hardimpact   = oldvelocity.z < -400.0f;
    }

    // check for running into walls
    oldsp = VectorNormalize(olddir);
    if ((oldsp > 220.0f) && (velocity * olddir < 2.0f)) {
        moveresult = MOVERESULT_HITWALL;
    }

    move_forward_vel  = DotProduct(yaw_forward, velocity);
    move_backward_vel = -move_forward_vel;

    if (move_forward_vel < 0.0f) {
        move_forward_vel = 0.0f;
    }

    if (move_backward_vel < 0.0f) {
        move_backward_vel = 0.0f;
    }

    move_left_vel  = DotProduct(yaw_left, velocity);
    move_right_vel = -move_left_vel;

    if (move_left_vel < 0.0f) {
        move_left_vel = 0.0f;
    }

    if (move_right_vel < 0.0f) {
        move_right_vel = 0.0f;
    }

    move_up_vel   = DotProduct(vec_up, velocity);
    move_down_vel = -move_up_vel;

    if (move_up_vel < 0.0f) {
        move_up_vel = 0.0f;
    }

    if (move_down_vel < 0.0f) {
        move_down_vel = 0.0f;
    }
}

qboolean Player::CheckMove(Vector& move, Vector *endpos)
{
    return AnimMove(move, endpos);
}

float Player::CheckMoveDist(Vector& move)
{
    Vector endpos;

    CheckMove(move, &endpos);
    endpos -= origin;

    return endpos.length();
}

void Player::ClientMove(usercmd_t *ucmd)
{
    pmove_t pm;
    Vector  move;

#ifdef OPM_FEATURES
    int  touch[MAX_GENTITIES];
    int  num        = getUseableEntities(touch, MAX_GENTITIES, true);
    bool bHintShown = false;

    for (int i = 0; i < num; i++) {
        Entity *entity = g_entities[touch[i]].entity;
        if (entity && entity->m_HintString.length()) {
            entity->ProcessHint(edict, true);
            bHintShown     = true;
            m_bShowingHint = true;
            break;
        }
    }

    if (!bHintShown && m_bShowingHint) {
        m_bShowingHint = false;

        // FIXME: delete
        if (sv_specialgame->integer) {
            gi.MSG_SetClient(edict - g_entities);

            // Send the hint string once
            gi.MSG_StartCGM(CGM_HINTSTRING);
            gi.MSG_WriteString("");
            gi.MSG_EndCGM();
        }
    }
#endif

    oldorigin = origin;

    client->ps.pm_type = GetMovePlayerMoveType();
    // set move flags
    client->ps.pm_flags &=
        ~(PMF_FROZEN | PMF_NO_PREDICTION | PMF_NO_MOVE | PMF_DUCKED | PMF_TURRET | PMF_VIEW_PRONE | PMF_VIEW_DUCK_RUN
          | PMF_VIEW_JUMP_START);

    if (level.playerfrozen || m_bFrozen) {
        client->ps.pm_flags |= PMF_FROZEN;
    }

    if ((flags & FL_IMMOBILE) || (flags & FL_PARTIAL_IMMOBILE)) {
        client->ps.pm_flags |= PMF_NO_MOVE;
        client->ps.pm_flags |= PMF_NO_PREDICTION;
    }

    if (m_pGlueMaster) {
        //
        // Added in 2.0.
        // Disable movement prediction/movement if the player is glued to something
        //
        client->ps.pm_flags |= PMF_NO_PREDICTION;
        client->ps.pm_flags |= PMF_NO_MOVE;
    }

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        if (maxs.z == 54.0f || maxs.z == 60.0f) {
            client->ps.pm_flags |= PMF_DUCKED;
        } else if (viewheight == JUMP_START_VIEWHEIGHT) {
            client->ps.pm_flags |= PMF_VIEW_JUMP_START;
        }
    } else {
        if (maxs.z == 60.0f) {
            client->ps.pm_flags |= PMF_DUCKED;
        } else if (maxs.z == 54.0f) {
            client->ps.pm_flags |= PMF_DUCKED | PMF_VIEW_PRONE;
        } else if (maxs.z == 20.0f) {
            client->ps.pm_flags |= PMF_VIEW_PRONE;
        } else if (maxs.z == 53.0f) {
            client->ps.pm_flags |= PMF_VIEW_DUCK_RUN;
        } else if (viewheight == JUMP_START_VIEWHEIGHT) {
            client->ps.pm_flags |= PMF_VIEW_JUMP_START;
        }
    }

    switch (movecontrol) {
    case MOVECONTROL_USER:
    case MOVECONTROL_LEGS:
    case MOVECONTROL_USER_MOVEANIM:
        break;

    case MOVECONTROL_CROUCH:
        client->ps.pm_flags |= PMF_NO_PREDICTION | PMF_DUCKED | PMF_VIEW_PRONE;
        break;

    default:
        client->ps.pm_flags |= PMF_NO_PREDICTION;
    }

    if (movetype == MOVETYPE_NOCLIP) {
        if (!(last_ucmd.buttons & BUTTON_RUN)) {
            client->ps.speed = sv_runspeed->value * sv_walkspeedmult->value;
        } else {
            client->ps.speed = sv_runspeed->value;
        }
    } else if (!groundentity) {
        client->ps.speed = airspeed;
    } else {
        Weapon *pWeap;

        if (last_ucmd.buttons & BUTTON_RUN) {
            client->ps.speed = GetRunSpeed();
        } else {
            client->ps.speed = sv_runspeed->value * sv_walkspeedmult->value;
        }

        if (m_iMovePosFlags & MPF_POSITION_CROUCHING) {
            client->ps.speed = (float)client->ps.speed * sv_crouchspeedmult->value;
        }

        pWeap = GetActiveWeapon(WEAPON_MAIN);
        if (pWeap) {
            //
            // Also use the weapon movement speed
            //
            if (!IsZoomed()) {
                client->ps.speed = (float)client->ps.speed * pWeap->GetMovementSpeed();
            } else {
                client->ps.speed = (float)client->ps.speed * pWeap->GetZoomMovement();
            }
        }
    }

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        client->ps.speed = (int)((float)client->ps.speed * sv_dmspeedmult->value);
    }

    //====
    // Added in OPM
    for (int i = 0; i < MAX_SPEED_MULTIPLIERS; i++) {
        client->ps.speed = (int)((float)client->ps.speed * speed_multiplier[i]);
    }
    //====

    client->ps.gravity = sv_gravity->value * gravity;

    if ((movecontrol != MOVECONTROL_ABSOLUTE) && (movecontrol != MOVECONTROL_PUSH)
        && (movecontrol != MOVECONTROL_CLIMBWALL)) {
        Vector oldpos(origin);

        SetMoveInfo(&pm, ucmd);
        Pmove(&pm);
        GetMoveInfo(&pm);

        if (g_gametype->integer != GT_SINGLE_PLAYER && groundentity && groundentity->entity
            && groundentity->entity->IsSubclassOfSentient()) {
            //
            // Added in 2.0
            // If the player is on another sentient, try to make it fall off
            //
            velocity -= Vector(orientation[0]) * (random() * 20.f);
            velocity -= Vector(orientation[1]) * (random() * 10.f);
        }

        ProcessPmoveEvents(pm.pmoveEvent);

        // if we're not moving, set the blocked flag in case the user is trying to move
        if ((ucmd->forwardmove || ucmd->rightmove) && ((oldpos - origin).length() < 0.005f)) {
            moveresult = MOVERESULT_BLOCKED;
        }
        if (client->ps.walking && moveresult >= MOVERESULT_BLOCKED) {
            setOrigin(oldpos);
            VectorCopy(origin, client->ps.origin);
        }
    } else {
        if (movecontrol == MOVECONTROL_CLIMBWALL) {
            PM_UpdateViewAngles(&client->ps, ucmd);
            v_angle = client->ps.viewangles;
        } else if (!deadflag) {
            v_angle = client->ps.viewangles;
        }

        // should collect objects to touch against
        memset(&pm, 0, sizeof(pmove_t));

        // keep the command time up to date or else the next PMove we run will try to catch up
        client->ps.commandTime = ucmd->serverTime;

        velocity = vec_zero;
    }

    if ((getMoveType() != MOVETYPE_NOCLIP) && (client->ps.pm_flags & PMF_NO_PREDICTION)) {
        if ((movecontrol == MOVECONTROL_ABSOLUTE) || (movecontrol == MOVECONTROL_CLIMBWALL)) {
            velocity = vec_zero;
        }

        if ((movecontrol == MOVECONTROL_ANIM) || (movecontrol == MOVECONTROL_CLIMBWALL)
            || (movecontrol == MOVECONTROL_USEANIM) || (movecontrol == MOVECONTROL_LOOPUSEANIM)
            || (movecontrol == MOVECONTROL_USER_MOVEANIM)) {
            Vector delta = vec_zero;
            PlayerAnimDelta(delta);

            // using PM_NOCLIP for a smooth move
            //client->ps.pm_type = PM_NOCLIP;

            if (delta != vec_zero) {
                float mat[3][3];
                AngleVectors(angles, mat[0], mat[1], mat[2]);
                MatrixTransformVector(delta, mat, move);
                AnimMove(move, &origin);
                setOrigin(origin);
                CheckGround();
            }
        }
    }

    m_fLastDeltaTime = level.time;

    TouchStuff(&pm);
}

void Player::VehicleMove(usercmd_t *ucmd)
{
    if (!m_pVehicle) {
        return;
    }

    oldorigin = origin;

    client->ps.pm_type = GetMovePlayerMoveType();

    // set move flags
    client->ps.pm_flags &=
        ~(PMF_FROZEN | PMF_NO_PREDICTION | PMF_NO_MOVE | PMF_DUCKED | PMF_TURRET | PMF_VIEW_PRONE | PMF_VIEW_DUCK_RUN
          | PMF_VIEW_JUMP_START);

    // disable prediction
    client->ps.pm_flags |= PMF_TURRET | PMF_NO_PREDICTION;

    if (level.playerfrozen || m_bFrozen) {
        client->ps.pm_flags |= PMF_FROZEN;
    }

    client->ps.gravity = gravity * sv_gravity->value;

    if (m_pVehicle->Drive(current_ucmd)) {
        client->ps.commandTime = ucmd->serverTime;
        // Added in OPM
        //  The player can't walk while attached to a vehicle
        client->ps.groundEntityNum = ENTITYNUM_NONE;
        client->ps.walking         = false;
    } else {
        ClientMove(ucmd);
    }
}

void Player::TurretMove(usercmd_t *ucmd)
{
    if (!m_pTurret) {
        return;
    }

    oldorigin = origin;

    client->ps.pm_type = GetMovePlayerMoveType();

    // set move flags
    client->ps.pm_flags &=
        ~(PMF_FROZEN | PMF_NO_PREDICTION | PMF_NO_MOVE | PMF_DUCKED | PMF_TURRET | PMF_VIEW_PRONE | PMF_VIEW_DUCK_RUN
          | PMF_VIEW_JUMP_START);

    // disable prediction
    client->ps.pm_flags |= PMF_TURRET | PMF_NO_PREDICTION;
    if (getMoveType() == MOVETYPE_PORTABLE_TURRET) {
        client->ps.pm_flags |= PMF_TURRET;
    }

    if (level.playerfrozen || m_bFrozen) {
        client->ps.pm_flags |= PMF_FROZEN;
    }

    client->ps.gravity = gravity * sv_gravity->value;

    if (m_pVehicle) {
        // Added in 2.30
        m_pVehicle->PathDrive(current_ucmd);
    }

    if (m_pTurret->IsSubclassOfTurretGun() && m_pTurret->UserAim(current_ucmd)) {
        client->ps.commandTime = ucmd->serverTime;
        // Added in OPM
        //  The player can't walk while attached to a turret
        client->ps.groundEntityNum = ENTITYNUM_NONE;
        client->ps.walking         = false;
    } else {
        ClientMove(ucmd);
    }
}

void Player::ClientInactivityTimer(void)
{
    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }

    if (g_inactivekick->integer && g_inactivekick->integer < 60) {
        gi.cvar_set("g_inactiveKick", "60");
    }

    if (g_inactivespectate->integer && g_inactivespectate->integer < 20) {
        gi.cvar_set("g_inactiveSpectate", "20");
    }

    if (num_team_kills >= g_teamkillkick->integer) {
        const str message = gi.LV_ConvertString("was removed from the server for killing too many teammates.");

        //
        // The player reached maximum team kills
        //
        G_PrintToAllClients(va("%s %s\n", client->pers.netname, message.c_str()), 2);

        if (Q_stricmp(Info_ValueForKey(client->pers.userinfo, "ip"), "localhost")) {
            //
            // Make sure to not kick the local host
            //
            gi.DropClient(client->ps.clientNum, message.c_str());
        } else if (!m_bSpectator) {
            // if it's the host, put it back in spectator mode
            num_team_kills      = 0;
            m_iLastNumTeamKills = 0;

            PostEvent(EV_Player_Spectator, 0);
        }

        return;
    }

    if (num_team_kills >= g_teamkillwarn->integer && num_team_kills > m_iLastNumTeamKills) {
        const str sWarning   = gi.LV_ConvertString("Warning:");
        const str sTeamKills = gi.LV_ConvertString("more team kill(s) and you will be removed from the server.");

        m_iLastNumTeamKills = num_team_kills;

        gi.centerprintf(
            edict, "%s %i %s", sWarning.c_str(), g_teamkillkick->integer - num_team_kills, sTeamKills.c_str()
        );
    }

    if (current_ucmd->buttons & BUTTON_ANY || (!g_inactivespectate->integer && !g_inactivekick->integer)
        || current_ucmd->forwardmove || current_ucmd->rightmove || current_ucmd->upmove
        || (m_bTempSpectator && client->lastActiveTime >= level.inttime - 5000)) {
        client->lastActiveTime = level.inttime;
        client->activeWarning  = 0;
        return;
    }

    if (g_inactivekick->integer && client->lastActiveTime < level.inttime - 1000 * g_inactivekick->integer) {
        const char *s = Info_ValueForKey(client->pers.userinfo, "ip");

        if (Q_stricmp(s, "localhost")) {
            gi.DropClient(client->ps.clientNum, "was dropped for inactivity");
            return;
        }

        if (m_bSpectator) {
            return;
        }

        PostEvent(EV_Player_Spectator, 0);
        return;
    }

    if (g_inactivespectate->integer && client->lastActiveTime < level.inttime - g_inactivespectate->integer * 1000
        && !m_bSpectator) {
        PostEvent(EV_Player_Spectator, 0);
        return;
    }

    if (g_inactivekick->integer) {
        static struct {
            int iLevel;
            int iTime;
        } warnkick[7] = {
            {1,  30},
            {8,  15},
            {9,  5 },
            {10, 4 },
            {11, 3 },
            {12, 2 },
            {13, 1 }
        };

        int iKickWait = g_inactivekick->integer - (level.inttime - client->lastActiveTime) / 1000 - 1;

        for (int i = 0; i < 7; i++) {
            if (client->activeWarning < warnkick[i].iLevel && iKickWait < warnkick[i].iTime) {
                const str sActionIn = gi.LV_ConvertString("You will be kicked for inactivity in");
                const str sSeconds  = gi.LV_ConvertString("seconds");

                client->activeWarning = warnkick[i].iLevel;

                gi.centerprintf(edict, "%s %i %s", sActionIn.c_str(), warnkick[i].iTime, sSeconds.c_str());

                return;
            }
        }
    }

    if (g_inactivespectate->integer && dm_team != TEAM_SPECTATOR) {
        static struct {
            int iLevel;
            int iTime;
        } warnspectate[6] = {2, 15, 3, 5, 4, 4, 5, 3, 6, 2, 7, 1};

        int iSpectateWait = g_inactivespectate->integer - (level.inttime - client->lastActiveTime) / 1000 - 1;

        for (int i = 0; i < 6; i++) {
            if (client->activeWarning < warnspectate[i].iLevel && iSpectateWait < warnspectate[i].iTime) {
                const str sActionIn = gi.LV_ConvertString("You will be moved to spectator for inactivity in");
                const str sSeconds  = gi.LV_ConvertString("seconds");

                client->activeWarning = warnspectate[i].iLevel;

                gi.centerprintf(edict, "%s %i %s", sActionIn.c_str(), warnspectate[i].iTime, sSeconds.c_str());

                return;
            }
        }
    }
}

void Player::UpdateEnemies(void)
{
    float  fFov;
    float  fMaxDist;
    float  fMaxCosSquared;
    Vector vLookDir;

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        return;
    }

    if (m_pNextSquadMate == this) {
        return;
    }

    fFov           = fov * 0.9f;
    fMaxDist       = world->farplane_distance * 0.7867f;
    fMaxCosSquared = 0.0f;

    AngleVectors(m_vViewAng, vLookDir, NULL, NULL);

    if (m_Enemy) {
        m_Enemy->m_iAttackerCount -= 3;
        m_Enemy = NULL;
    }

    for (Sentient *obj = level.m_HeadSentient[0]; obj != NULL; obj = obj->m_NextSentient) {
        Vector vDelta;
        float  fDot;
        float  fDotSquared;

        if (CanSee(obj, fFov, fMaxDist, false)) {
            obj->m_fPlayerSightLevel += level.frametime;

            vDelta      = obj->origin - origin;
            fDot        = DotProduct(vDelta, vLookDir);
            fDotSquared = fDot * fDot;

            if (fDotSquared > fMaxCosSquared * vDelta.lengthSquared()) {
                fMaxCosSquared = fDotSquared / vDelta.lengthSquared();
                m_Enemy        = obj;
            }
        } else {
            obj->m_fPlayerSightLevel = 0.0f;
        }
    }

    if (m_Enemy) {
        m_Enemy->m_iAttackerCount += 3;
    }
}

/*
==============
ClientThink

This will be called once for each client frame, which will
usually be a couple times for each server frame.
==============
*/
void Player::ClientThink(void)
{
    // sanity check the command time to prevent speedup cheating
    if (current_ucmd->serverTime > level.svsTime) {
        //
        // we don't want any future commands, these could be from the previous game
        //
        return;
    }

    if (current_ucmd->serverTime < level.svsTime - 1000) {
        current_ucmd->serverTime = level.svsTime - 1000;
    }

    if ((current_ucmd->serverTime - client->ps.commandTime) < 1) {
        return;
    }

    TickSprint();

    if (g_gametype->integer != GT_SINGLE_PLAYER && dm_team == TEAM_SPECTATOR && !IsSpectator()) {
        Spectator();
    }

    last_ucmd = *current_ucmd;
    server_new_buttons |= current_ucmd->buttons & ~buttons;
    new_buttons = current_ucmd->buttons & ~buttons;
    buttons     = current_ucmd->buttons;

    if (camera) {
        m_vViewPos = camera->origin;
        m_vViewAng = camera->angles;
    } else {
        m_vViewPos[0] = (float)current_eyeinfo->ofs[0] + origin[0];
        m_vViewPos[1] = (float)current_eyeinfo->ofs[1] + origin[1];
        m_vViewPos[2] = (float)current_eyeinfo->ofs[2] + origin[2];

        m_vViewAng[0] = current_eyeinfo->angles[0];
        m_vViewAng[1] = current_eyeinfo->angles[1];
        m_vViewAng[2] = 0.0f;
    }

    VectorCopy(m_vViewPos, client->ps.vEyePos);

    if (!level.intermissiontime) {
        if (new_buttons & BUTTON_ATTACKRIGHT) {
            Weapon *weapon = GetActiveWeapon(WEAPON_MAIN);

            if (weapon && (weapon->GetZoom())) {
                ToggleZoom(weapon->GetZoom());
            }
        }

        if (new_buttons & BUTTON_USE) {
            DoUse(NULL);
        }

        moveresult = MOVERESULT_NONE;

        if (m_pTurret) {
            TurretMove(current_ucmd);
        } else if (m_pVehicle) {
            VehicleMove(current_ucmd);
        } else {
            ClientMove(current_ucmd);
        }

        // Save cmd angles so that we can get delta angle movements next frame
        client->cmd_angles[0] = SHORT2ANGLE(current_ucmd->angles[0]);
        client->cmd_angles[1] = SHORT2ANGLE(current_ucmd->angles[1]);
        client->cmd_angles[2] = SHORT2ANGLE(current_ucmd->angles[2]);

        if (g_gametype->integer != GT_SINGLE_PLAYER && g_smoothClients->integer) {
            VectorCopy(client->ps.velocity, edict->s.pos.trDelta);
            edict->s.pos.trTime = client->ps.commandTime;
        } else {
            VectorClear(edict->s.pos.trDelta);
            edict->s.pos.trTime = 0;
        }

        ClientInactivityTimer();
    } else {
        if (g_gametype->integer != GT_SINGLE_PLAYER) {
            client->ps.pm_flags |= PMF_FROZEN;
            client->ps.pm_flags |= PMF_INTERMISSION;
            VectorClear(client->ps.velocity);

            if (level.time - level.intermissiontime > 5.0f
                && (new_buttons & (BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT))) {
                level.exitintermission = true;
            }
        } else {
            if (level.intermissiontype == TRANS_MISSION_FAILED || IsDead()) {
                gi.cvar_set("g_success", "0");
                gi.cvar_set("g_failed", "1");
            } else {
                gi.cvar_set("g_success", "1");
                gi.cvar_set("g_failed", "0");
            }

            // prevent getting medals from cheats
            if (g_medal0->modificationCount > 1 || g_medal1->modificationCount > 1 || g_medal2->modificationCount > 1
                || g_medal3->modificationCount > 1 || g_medal4->modificationCount > 1 || g_medal5->modificationCount > 1
                || g_medalbt1->modificationCount > 1 || g_medalbt2->modificationCount > 1
                || g_medalbt3->modificationCount > 1 || g_medalbt4->modificationCount > 1
                || g_medalbt5->modificationCount > 1 || g_eogmedal0->modificationCount > 1
                || g_eogmedal1->modificationCount > 1 || g_eogmedal2->modificationCount > 1) {
                gi.cvar_set("g_gotmedal", "1");
            } else {
                gi.cvar_set("g_gotmedal", "0");
            }

            client->ps.pm_flags |= PMF_FROZEN;
            VectorClear(client->ps.velocity);

            if (level.time - level.intermissiontime > 4.0f) {
                if (level.intermissiontype) {
                    if (level.intermissiontype == TRANS_MISSION_FAILED) {
                        if ((new_buttons & BUTTON_ATTACKLEFT) || (new_buttons & BUTTON_ATTACKRIGHT)) {
                            G_MissionFailed();
                        }
                    } else if ((new_buttons & BUTTON_ATTACKLEFT) || (new_buttons & BUTTON_ATTACKRIGHT)) {
                        g_medal0->modificationCount    = 1;
                        g_medal1->modificationCount    = 1;
                        g_medal2->modificationCount    = 1;
                        g_medal3->modificationCount    = 1;
                        g_medal4->modificationCount    = 1;
                        g_medal5->modificationCount    = 1;
                        g_medalbt0->modificationCount  = 1;
                        g_medalbt1->modificationCount  = 1;
                        g_medalbt2->modificationCount  = 1;
                        g_medalbt3->modificationCount  = 1;
                        g_medalbt4->modificationCount  = 1;
                        g_medalbt5->modificationCount  = 1;
                        g_eogmedal0->modificationCount = 1;
                        g_eogmedal1->modificationCount = 1;
                        g_eogmedal2->modificationCount = 1;
                        g_medal0->modified             = false;
                        g_medal1->modified             = false;
                        g_medal2->modified             = false;
                        g_medal3->modified             = false;
                        g_medal4->modified             = false;
                        g_medal5->modified             = false;
                        g_eogmedal0->modified          = false;
                        g_eogmedal1->modified          = false;
                        g_eogmedal2->modified          = false;

                        level.exitintermission = true;
                    }
                } else {
                    level.exitintermission = true;
                }
            }
        }

        // Save cmd angles so that we can get delta angle movements next frame
        client->cmd_angles[0]  = SHORT2ANGLE(current_ucmd->angles[0]);
        client->cmd_angles[1]  = SHORT2ANGLE(current_ucmd->angles[1]);
        client->cmd_angles[2]  = SHORT2ANGLE(current_ucmd->angles[2]);
        client->ps.commandTime = current_ucmd->serverTime;
    }
}

void Player::Think(void)
{
    static cvar_t *g_aimLagTime = NULL;

    int     m_iClientWeaponCommand;
    Event  *m_pWeaponCommand = NULL;
    Weapon *pWeap;

    if (whereami->integer && origin != oldorigin) {
        gi.DPrintf("x %8.2f y %8.2f z %8.2f area %2d\n", origin[0], origin[1], origin[2], edict->r.areanum);
    }

    if (g_gametype->integer == GT_SINGLE_PLAYER && g_playermodel->modified) {
        setModel("models/player/" + str(g_playermodel->string) + ".tik");

        if (!edict->tiki) {
            setModel("models/player/american_army.tik");
        }

        g_playermodel->modified = qfalse;
    }

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        m_bIsDisguised = false;

        if (m_bHasDisguise && !level.m_bAlarm) {
            pWeap = GetActiveWeapon(WEAPON_MAIN);

            if (!pWeap || pWeap->IsSubclassOfInventoryItem()) {
                m_bIsDisguised = true;

                for (Sentient *pSent = level.m_HeadSentient[0]; pSent != NULL; pSent = pSent->m_NextSentient) {
                    Actor *act = (Actor *)pSent;

                    if (pSent->m_Enemy == this && act->IsAttacking()) {
                        m_bIsDisguised = false;
                        break;
                    }
                }
            }
        }

        PathSearch::PlayerCover(this);
        UpdateEnemies();
    }

    if (movetype == MOVETYPE_NOCLIP) {
        StopPartAnimating(torso);
        SetPartAnim("idle");

        client->ps.walking = qfalse;
        groundentity       = 0;
    } else {
        CheckMoveFlags();
        EvaluateState();
    }

    oldvelocity = velocity;
    old_v_angle = v_angle;

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        if ((server_new_buttons & BUTTON_ATTACKLEFT) && (!GetActiveWeapon(WEAPON_MAIN)) && !IsDead()
            && !IsNewActiveWeapon() && !LoadingSavegame) {
            Event *ev = new Event("useweaponclass");
            ev->AddString("item1");

            ProcessEvent(ev);
        }
    } else {
        // Added in 2.0: Invulnerability and team spawn
        TickInvulnerable();
        TickTeamSpawn();

        if (deadflag == DEAD_DEAD && level.time > respawn_time) {
            if (dmManager.AllowRespawn() && AllowTeamRespawn()) {
                if (((server_new_buttons & BUTTON_ATTACKLEFT) || (server_new_buttons & BUTTON_ATTACKRIGHT))
                    || (g_forcerespawn->integer > 0 && level.time > g_forcerespawn->integer + respawn_time)) {
                    // 2.0
                    //  Check for team respawn
                    //
                    if (!m_fSpawnTimeLeft) {
                        if (AllowTeamRespawn()) {
                            EndSpectator();
                            PostEvent(EV_Player_Respawn, 0);
                        }
                    } else {
                        m_bWaitingForRespawn = true;
                    }
                }
            } else if (!IsSpectator()) {
                BeginTempSpectator();
            }
        }

        if (IsSpectator()) {
            if (!m_bTempSpectator && level.time > respawn_time && (server_new_buttons & BUTTON_ATTACKLEFT)) {
                if (current_team && dm_team != TEAM_SPECTATOR) {
                    if (client->pers.dm_primary[0]) {
                        if ((g_gametype->integer == GT_FFA
                             || (g_gametype->integer >= GT_TEAM && dm_team > TEAM_FREEFORALL))
                            && deadflag != DEAD_DEAD) {
                            // 2.0
                            //  Check for team respawn
                            //
                            if (!m_fSpawnTimeLeft) {
                                if (AllowTeamRespawn()) {
                                    EndSpectator();
                                    PostEvent(EV_Player_Respawn, 0);
                                }
                            } else {
                                m_bWaitingForRespawn = true;
                            }
                        }
                    } else if (m_fWeapSelectTime < level.time) {
                        m_fWeapSelectTime = level.time + 1.0;
                        UserSelectWeapon(false);
                    }
                } else if (m_fWeapSelectTime < level.time) {
                    m_fWeapSelectTime = level.time + 1.0;
                    gi.SendServerCommand(edict - g_entities, "stufftext \"pushmenu_teamselect\"");
                }
            }
            // Removed in 2.0
            //else if (level.time > m_fWeapSelectTime + 10.0) {
            //    m_fWeapSelectTime = level.time;
            //    gi.centerprintf(edict, "\n\n\n%s", gi.LV_ConvertString("Press fire to join the battle!"));
            //}
        } else if (!client->pers.dm_primary[0]) {
            Spectator();
            if (m_fWeapSelectTime < level.time) {
                m_fWeapSelectTime = level.time + 1.0;
                UserSelectWeapon(false);
            }
        }

        if (IsSpectator()) {
            if (g_protocol >= PROTOCOL_MOHTA_MIN) {
                if (m_iPlayerSpectating) {
                    if (last_ucmd.upmove) {
                        if (!m_bSpectatorSwitching) {
                            m_bSpectatorSwitching = true;

                            if (last_ucmd.upmove > 0) {
                                SetPlayerSpectate(true);
                            } else {
                                SetPlayerSpectate(false);
                            }
                        }
                    } else {
                        m_bSpectatorSwitching = false;
                    }
                } else if ((server_new_buttons & BUTTON_USE)) {
                    SetPlayerSpectateRandom();
                    server_new_buttons &= ~BUTTON_USE;
                }
            } else {
                if ((server_new_buttons & BUTTON_USE)) {
                    SetPlayerSpectate(true);
                }
            }

            if (g_gametype->integer >= GT_TEAM && g_forceteamspectate->integer && GetTeam() > TEAM_FREEFORALL) {
                if (!m_iPlayerSpectating) {
                    SetPlayerSpectateRandom();
                } else {
                    gentity_t *ent = g_entities + m_iPlayerSpectating - 1;

                    if (!ent->inuse || !ent->entity) {
                        // Invalid spectate entity
                        SetPlayerSpectateRandom();
                    } else if (ent->entity->deadflag >= DEAD_DEAD || static_cast<Player *>(ent->entity)->IsSpectator()
                               || !IsValidSpectatePlayer(static_cast<Player *>(ent->entity))) {
                        SetPlayerSpectateRandom();
                    }
                }
            } else {
                if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
                    // Since 2.0, use = clear spectator
                    if (m_iPlayerSpectating && (server_new_buttons & BUTTON_USE)) {
                        m_iPlayerSpectating = 0;
                    }
                } else {
                    // On 1.11 and below, up = clear spectator
                    if (last_ucmd.upmove) {
                        m_iPlayerSpectating = 0;
                    }
                }

                if (m_iPlayerSpectating) {
                    gentity_t *ent = g_entities + m_iPlayerSpectating - 1;

                    if (!ent->inuse || !ent->entity) {
                        // Invalid spectate entity
                        SetPlayerSpectateRandom();
                    } else if (ent->entity->deadflag >= DEAD_DEAD || static_cast<Player *>(ent->entity)->IsSpectator()
                               || !IsValidSpectatePlayer(static_cast<Player *>(ent->entity))) {
                        SetPlayerSpectateRandom();
                    } else if (g_gametype->integer >= GT_TEAM && GetTeam() > TEAM_FREEFORALL
                               && static_cast<Player *>(ent->entity)->GetTeam() != GetTeam()) {
                        SetPlayerSpectateRandom();
                    }
                }
            }
        } else {
            m_iPlayerSpectating = 0;
        }
    }

    if (g_logstats->integer) {
        if (!logfile_started) {
            ProcessEvent(EV_Player_LogStats);
            logfile_started = qtrue;
        }
    }

    if (!IsDead()) {
        m_iClientWeaponCommand = G_GetWeaponCommand(server_new_buttons);

        switch (m_iClientWeaponCommand) {
        case 0:
            // No command
            break;
        case WEAPON_COMMAND_USE_PISTOL:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("pistol");
            break;
        case WEAPON_COMMAND_USE_RIFLE:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("rifle");
            break;
        case WEAPON_COMMAND_USE_SMG:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("smg");
            break;
        case WEAPON_COMMAND_USE_MG:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("mg");
            break;
        case WEAPON_COMMAND_USE_GRENADE:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("grenade");
            break;
        case WEAPON_COMMAND_USE_HEAVY:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("heavy");
            break;
        case WEAPON_COMMAND_USE_ITEM1:
            m_pWeaponCommand = new Event(EV_Sentient_ToggleItemUse);
            break;
        case WEAPON_COMMAND_USE_ITEM2:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("item2");
            break;
        case WEAPON_COMMAND_USE_ITEM3:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("item3");
            break;
        case WEAPON_COMMAND_USE_ITEM4:
            m_pWeaponCommand = new Event(EV_Sentient_UseWeaponClass);
            m_pWeaponCommand->AddString("item4");
            break;
        case WEAPON_COMMAND_USE_PREV_WEAPON:
            m_pWeaponCommand = new Event(EV_Player_PrevWeapon);
            break;
        case WEAPON_COMMAND_USE_NEXT_WEAPON:
            m_pWeaponCommand = new Event(EV_Player_NextWeapon);
            break;
        case WEAPON_COMMAND_USE_LAST_WEAPON:
            m_pWeaponCommand = new Event(EV_Sentient_UseLastWeapon);
            break;
        case WEAPON_COMMAND_HOLSTER:
            m_pWeaponCommand = new Event(EV_Player_Holster);
            break;
        case WEAPON_COMMAND_DROP:
            m_pWeaponCommand = new Event(EV_Player_DropWeapon);
            break;
        default:
            gi.DPrintf("Unrecognized weapon command %d\n", m_iClientWeaponCommand);
        }

        if (m_pWeaponCommand) {
            PostEvent(m_pWeaponCommand, 0);
        }
    }

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        if (!g_aimLagTime) {
            g_aimLagTime = gi.Cvar_Get("g_aimLagTime", "250", 0);
        }

        if (mLastTrailTime + g_aimLagTime->integer < level.inttime) {
            mLastTrailTime = level.inttime;

            mvTrail[0]        = centroid;
            mvTrailEyes[0]    = centroid;
            mvTrailEyes[0][0] = EyePosition()[0];
        }
    }
    UpdateFootsteps();

    //
    // Added in 2.0
    // Heal rate
    //
    if (m_fHealRate && !IsDead()) {
        float newrate;

        if (g_healrate->value && g_gametype->integer != GT_SINGLE_PLAYER) {
            newrate = max_health * (g_healrate->value / 100.f) * level.frametime;
            if (newrate >= m_fHealRate) {
                newrate     = m_fHealRate;
                m_fHealRate = 0;
            } else {
                m_fHealRate -= newrate;
            }
        } else {
            newrate     = m_fHealRate;
            m_fHealRate = 0;
        }

        // heal
        health += newrate;
        if (health > max_health) {
            // make sure it doesn't go above the maximum player health
            health = max_health;
        }
    }

    //
    // Added in 2.0: talk icon
    //
    if (buttons & BUTTON_TALK) {
        edict->s.eFlags |= EF_PLAYER_IN_MENU;
    } else {
        edict->s.eFlags &= ~EF_PLAYER_IN_MENU;
    }

    if (m_fTalkTime > level.time) {
        edict->s.eFlags |= EF_PLAYER_TALKING;
    } else {
        edict->s.eFlags &= ~EF_PLAYER_TALKING;
    }

    //
    // Added in OPM
    //
    if (!animDoneVM) {
        int    index;
        float  anim_time;
        vma_t *vma = vmalist.find(m_sVMcurrent);

        index = m_fpsTiki == NULL ? -1 : gi.Anim_NumForName(m_fpsTiki, m_sVMAcurrent);

        if (index >= 0) {
            anim_time = gi.Anim_Time(m_fpsTiki, index);

            if (m_fVMAtime < anim_time) {
                if (vma) {
                    m_fVMAtime += level.frametime * vma->speed;
                } else {
                    m_fVMAtime += level.frametime;
                }
            } else {
                animDoneVM = true;
                m_fVMAtime = 0;

                Notify("viewmodelanim_done");
            }
        } else {
            animDoneVM = true;
            m_fVMAtime = 0;

            Notify("viewmodelanim_done");
        }
    }

    server_new_buttons = 0;

    edict->r.svFlags &= ~(SVF_SINGLECLIENT | SVF_NOTSINGLECLIENT);
}

void Player::InitLegsStateTable(void)
{
    animdone_Legs     = false;
    currentState_Legs = statemap_Legs->FindState("STAND");

    str legsAnim(currentState_Legs->getLegAnim(*this, &legs_conditionals));
    if (legsAnim == "") {
        StopPartAnimating(legs);
    } else if (legsAnim != "none") {
        SetPartAnim(legsAnim.c_str(), legs);
    }
}

void Player::InitTorsoStateTable(void)
{
    animdone_Torso = false;

    currentState_Torso = statemap_Torso->FindState("STAND");

    str torsoAnim(currentState_Torso->getActionAnim(*this, &torso_conditionals));
    if (torsoAnim == "") {
        StopPartAnimating(torso);
    } else if (torsoAnim != "none") {
        SetPartAnim(torsoAnim.c_str(), torso);
    }
}

void Player::LoadStateTable(void)
{
    int          i;
    Conditional *cond;

    statemap_Legs  = NULL;
    statemap_Torso = NULL;

    //
    // Free existing conditionals
    //
    for (i = legs_conditionals.NumObjects(); i > 0; i--) {
        cond = legs_conditionals.ObjectAt(i);
        delete cond;
    }
    legs_conditionals.FreeObjectList();

    for (i = torso_conditionals.NumObjects(); i > 0; i--) {
        cond = torso_conditionals.ObjectAt(i);
        delete cond;
    }
    torso_conditionals.FreeObjectList();

    statemap_Legs =
        GetStatemap(str(g_statefile->string) + "_Legs.st", (Condition<Class> *)m_conditions, &legs_conditionals, false);
    statemap_Torso = GetStatemap(
        str(g_statefile->string) + "_Torso.st", (Condition<Class> *)m_conditions, &torso_conditionals, false
    );

    movecontrol = MOVECONTROL_LEGS;

    InitLegsStateTable();
    InitTorsoStateTable();

    movecontrol = currentState_Legs->getMoveType();
    if (!movecontrol) {
        movecontrol = MOVECONTROL_LEGS;
    }

    for (int i = 1; i <= legs_conditionals.NumObjects(); i++) {
        Conditional *c = legs_conditionals.ObjectAt(i);

        if (Q_stricmp(c->getName(), "PAIN") && !c->parmList.NumObjects()) {
            m_pLegsPainCond = c;
            break;
        }
    }

    for (int i = 1; i <= torso_conditionals.NumObjects(); i++) {
        Conditional *c = torso_conditionals.ObjectAt(i);

        if (Q_stricmp(c->getName(), "PAIN") && !c->parmList.NumObjects()) {
            m_pTorsoPainCond = c;
            break;
        }
    }

    if ((movecontrol < (sizeof(MoveStartFuncs) / sizeof(MoveStartFuncs[0]))) && (MoveStartFuncs[movecontrol])) {
        (this->*MoveStartFuncs[movecontrol])();
    }

    SetViewAngles(v_angle);
}

void Player::ResetState(Event *ev)
{
    movecontrol = MOVECONTROL_LEGS;
    LoadStateTable();
}

void Player::StartPush(void)
{
    trace_t trace;
    Vector  end(origin + yaw_forward * 64.0f);

    trace = G_Trace(origin, mins, maxs, end, this, MASK_SOLID, true, "StartPush");
    if (trace.fraction == 1.0f) {
        return;
    }
    v_angle.y = vectoyaw(trace.plane.normal) - 180;
    SetViewAngles(v_angle);

    setOrigin(trace.endpos - yaw_forward * 0.4f);
}

void Player::StartClimbLadder(void)
{
    trace_t trace;
    Vector  end(origin + yaw_forward * 20.0f);

    trace = G_Trace(origin, mins, maxs, end, this, MASK_SOLID, true, "StartClimbLadder");
    if ((trace.fraction == 1.0f) || !(trace.surfaceFlags & SURF_LADDER)) {
        return;
    }

    v_angle.y = vectoyaw(trace.plane.normal) - 180;
    SetViewAngles(v_angle);

    setOrigin(trace.endpos - yaw_forward * 0.4f);
}

void Player::StartUseAnim(void)
{
    UseAnim *ua;
    Vector   neworg;
    Vector   newangles;
    str      newanim;
    str      state;
    str      camera;
    trace_t  trace;

    if (toucheduseanim) {
        ua = (UseAnim *)(Entity *)toucheduseanim;
    } else if (atobject) {
        ua = (UseAnim *)(Entity *)atobject;
    } else {
        return;
    }

    useitem_in_use = ua;
    toucheduseanim = NULL;
    atobject       = NULL;

    if (ua->GetInformation(this, &neworg, &newangles, &newanim, &useanim_numloops, &state, &camera)) {
        trace = G_Trace(origin, mins, maxs, neworg, this, MASK_PLAYERSOLID, true, "StartUseAnim");
        if (trace.startsolid || (trace.fraction < 1.0f)) {
            gi.DPrintf("Move to UseAnim was blocked.\n");
        }

        if (!trace.startsolid) {
            setOrigin(trace.endpos);
        }

        setAngles(newangles);
        v_angle.y = newangles.y;
        SetViewAngles(v_angle);

        movecontrol = MOVECONTROL_ABSOLUTE;

        if (state.length()) {
            State *newState;

            newState = statemap_Torso->FindState(state);
            if (newState) {
                EvaluateState(newState);
            } else {
                gi.DPrintf("Could not find state %s on UseAnim\n", state.c_str());
            }
        } else {
            if (currentState_Torso) {
                if (camera.length()) {
                    currentState_Torso->setCameraType(camera);
                } else {
                    currentState_Torso->setCameraType("behind");
                }
            }
            SetPartAnim(newanim, legs);
        }
    }
}

void Player::StartLoopUseAnim(void)
{
    useanim_numloops--;
}

void Player::FinishUseAnim(Event *ev)
{
    UseAnim *ua;

    if (!useitem_in_use) {
        return;
    }

    ua = (UseAnim *)(Entity *)useitem_in_use;
    ua->TriggerTargets(this);
    useitem_in_use = NULL;
}

void Player::SetupUseObject(void)
{
    UseObject *uo;
    Vector     neworg;
    Vector     newangles;
    str        state;
    trace_t    trace;

    if (atobject) {
        uo = (UseObject *)(Entity *)atobject;
    } else {
        return;
    }

    useitem_in_use = uo;

    uo->Setup(this, &neworg, &newangles, &state);
    {
        trace = G_Trace(neworg, mins, maxs, neworg, this, MASK_PLAYERSOLID, true, "SetupUseObject - 1");
        if (trace.startsolid || trace.allsolid) {
            trace = G_Trace(origin, mins, maxs, neworg, this, MASK_PLAYERSOLID, true, "SetupUseObject - 2");
            if (trace.startsolid || (trace.fraction < 1.0f)) {
                gi.DPrintf("Move to UseObject was blocked.\n");
            }
        }

        if (!trace.startsolid) {
            setOrigin(trace.endpos);
        }

        setAngles(newangles);
        v_angle.y = newangles.y;
        SetViewAngles(v_angle);

        movecontrol = MOVECONTROL_ABSOLUTE;

        if (state.length()) {
            State *newState;

            newState = statemap_Torso->FindState(state);
            if (newState) {
                EvaluateState(newState);
            } else {
                gi.DPrintf("Could not find state %s on UseObject\n", state.c_str());
            }
        }
    }
}

void Player::StartUseObject(Event *ev)
{
    UseObject *uo;

    if (!useitem_in_use) {
        return;
    }

    uo = (UseObject *)(Entity *)useitem_in_use;
    uo->Start();
}

void Player::FinishUseObject(Event *ev)
{
    UseObject *uo;

    if (!useitem_in_use) {
        return;
    }

    uo = (UseObject *)(Entity *)useitem_in_use;
    uo->Stop(this);
    useitem_in_use = NULL;
}

void Player::Turn(Event *ev)
{
    float  yaw;
    Vector oldang(v_angle);

    yaw = ev->GetFloat(1);

    v_angle[YAW] = (int)(anglemod(v_angle[YAW]) / 22.5f) * 22.5f;
    SetViewAngles(v_angle);

    if (!CheckMove(vec_zero)) {
        SetViewAngles(oldang);
        return;
    }

    CancelEventsOfType(EV_Player_TurnUpdate);

    ev = new Event(EV_Player_TurnUpdate);
    ev->AddFloat(yaw / 5.0f);
    ev->AddFloat(0.5f);
    ProcessEvent(ev);
}

void Player::TurnUpdate(Event *ev)
{
    float  yaw;
    float  timeleft;
    Vector oldang(v_angle);

    yaw      = ev->GetFloat(1);
    timeleft = ev->GetFloat(2);
    timeleft -= 0.1f;

    if (timeleft > 0) {
        ev = new Event(EV_Player_TurnUpdate);
        ev->AddFloat(yaw);
        ev->AddFloat(timeleft);
        PostEvent(ev, 0.1f);

        v_angle[YAW] += yaw;
        SetViewAngles(v_angle);
    } else {
        v_angle[YAW] = (int)(anglemod(v_angle[YAW]) / 22.5f) * 22.5f;
        SetViewAngles(v_angle);
    }

    if (!CheckMove(vec_zero)) {
        SetViewAngles(oldang);
    }
}

void Player::TurnLegs(Event *ev)
{
    float yaw;

    yaw = ev->GetFloat(1);

    angles[YAW] += yaw;
    setAngles(angles);
}

void Player::EvaluateState(State *forceTorso, State *forceLegs)
{
    int           count;
    State        *laststate_Legs;
    State        *laststate_Torso;
    State        *startstate_Legs;
    State        *startstate_Torso;
    movecontrol_t move;

    if (getMoveType() == MOVETYPE_NOCLIP) {
        return;
    }

    if (flags & FL_IMMOBILE) {
        // Don't evaluate state when immobile
        return;
    }

    if (getMoveType() == MOVETYPE_PORTABLE_TURRET) {
        // Added in 2.0
        //  Animations are handled hardcodedly
        currentState_Torso = NULL;
        currentState_Legs  = NULL;
        return;
    }

    // Evaluate the current state.
    // When the state changes, we reevaluate the state so that if the
    // conditions aren't met in the new state, we don't play one frame of
    // the animation for that state before going to the next state.
    startstate_Torso = laststate_Torso = currentState_Torso;
    count                              = 0;
    do {
        // since we could get into an infinite loop here, do a check
        // to make sure we don't.
        count++;
        if (count > 10) {
            gi.DPrintf("Possible infinite loop in state '%s'\n", currentState_Torso->getName());
            if (count > 20) {
                assert(0);
                gi.Error(ERR_DROP, "Stopping due to possible infinite state loop\n");
                break;
            }
        }

        laststate_Torso = currentState_Torso;

        if (currentState_Torso) {
            laststate_Torso = currentState_Torso;

            if (forceTorso) {
                currentState_Torso = forceTorso;
            } else {
                currentState_Torso = currentState_Torso->Evaluate(*this, &torso_conditionals);
            }
        } else {
            // Added in 2.0
            //  Switch to the default torso state if it's NULL
            if (forceTorso) {
                currentState_Torso = forceTorso;
            } else {
                currentState_Torso = statemap_Torso->FindState("STAND");
            }

            laststate_Torso = NULL;
        }

        if (currentState_Torso) {
            if (laststate_Torso) {
                // Process exit commands of the last state
                laststate_Torso->ProcessExitCommands(this);
            }

            // Process entry commands of the new state
            currentState_Torso->ProcessEntryCommands(this);

            if (waitForState.length() && (!waitForState.icmpn(currentState_Torso->getName(), waitForState.length()))) {
                waitForState = "";
            }

            move = currentState_Torso->getMoveType();

            // use the current movecontrol
            if (move == MOVECONTROL_NONE) {
                move = movecontrol;
            }

            str legsAnim;
            str torsoAnim(currentState_Torso->getActionAnim(*this, &torso_conditionals));

            if (move == MOVECONTROL_LEGS) {
                if (!currentState_Legs) {
                    animdone_Legs     = false;
                    currentState_Legs = statemap_Legs->FindState("STAND");
                    legsAnim          = currentState_Legs->getLegAnim(*this, &legs_conditionals);

                    if (legsAnim == "") {
                        StopPartAnimating(legs);
                    } else if (legsAnim != "none") {
                        SetPartAnim(legsAnim.c_str(), legs);
                    }
                }

                if (torsoAnim == "none") {
                    StopPartAnimating(torso);
                    animdone_Torso = true;
                } else if (torsoAnim != "") {
                    SetPartAnim(torsoAnim.c_str(), torso);
                }
            } else {
                if (torsoAnim == "none") {
                    StopPartAnimating(torso);
                    animdone_Torso = true;
                } else if (torsoAnim != "") {
                    SetPartAnim(torsoAnim.c_str(), torso);
                }

                legsAnim = currentState_Torso->getLegAnim(*this, &torso_conditionals);

                if (legsAnim == "none" || legsAnim == "") {
                    StopPartAnimating(legs);
                } else {
                    SetPartAnim(legsAnim.c_str(), legs);
                }

                // Fixed in OPM
                //  Clear the legs state, so the torso state can reset it to STAND
                //  in subsequent iterations.
                //  As the legs animation is stopped, there would be no anim to wait on.
                //
                //  This prevents the current legs state to be stuck
                //  when the move control is set to non-legs and then to legs
                //  in the same iteration before the legs state is being processed.
                currentState_Legs = NULL;
            }

            if (movecontrol != move) {
                movecontrol = move;
                if ((move < (sizeof(MoveStartFuncs) / sizeof(MoveStartFuncs[0]))) && (MoveStartFuncs[move])) {
                    (this->*MoveStartFuncs[move])();
                }

                if (movecontrol == MOVECONTROL_CLIMBWALL) {
                    edict->s.eFlags |= EF_CLIMBWALL;
                } else {
                    edict->s.eFlags &= ~EF_CLIMBWALL;
                }
            }

            SetViewAngles(v_angle);
        } else {
            currentState_Torso = laststate_Torso;
        }
    } while (laststate_Torso != currentState_Torso);

    // Evaluate the current state.
    // When the state changes, we reevaluate the state so that if the
    // conditions aren't met in the new state, we don't play one frame of
    // the animation for that state before going to the next state.
    startstate_Legs = laststate_Legs = currentState_Legs;
    if (movecontrol == MOVECONTROL_LEGS) {
        count = 0;
        do {
            // since we could get into an infinite loop here, do a check
            // to make sure we don't.
            count++;
            if (count > 10) {
                gi.DPrintf("Possible infinite loop in state '%s'\n", currentState_Legs->getName());
                if (count > 20) {
                    assert(0);
                    gi.Error(ERR_DROP, "Stopping due to possible infinite state loop\n");
                    break;
                }
            }

            if (currentState_Legs) {
                laststate_Legs = currentState_Legs;

                if (forceLegs) {
                    currentState_Legs = forceLegs;
                } else {
                    currentState_Legs = currentState_Legs->Evaluate(*this, &legs_conditionals);
                }
            } else {
                // Added in 2.0
                //  Switch to the default legs state if it's NULL
                if (forceLegs) {
                    currentState_Legs = forceLegs;
                } else if ((m_iMovePosFlags & MPF_POSITION_CROUCHING)) {
                    currentState_Legs = statemap_Legs->FindState("CROUCH_IDLE");
                } else {
                    currentState_Legs = statemap_Legs->FindState("STAND");
                }

                laststate_Legs = NULL;
            }

            animdone_Legs = false;
            if (currentState_Legs) {
                if (laststate_Legs) {
                    // Process exit commands of the last state
                    laststate_Legs->ProcessExitCommands(this);
                }

                // Process entry commands of the new state
                currentState_Legs->ProcessEntryCommands(this);

                if (waitForState.length()
                    && (!waitForState.icmpn(currentState_Legs->getName(), waitForState.length()))) {
                    waitForState = "";
                }

                str legsAnim(currentState_Legs->getLegAnim(*this, &legs_conditionals));

                if (legsAnim == "none") {
                    StopPartAnimating(legs);
                    animdone_Legs = true;
                } else if (legsAnim != "") {
                    SetPartAnim(legsAnim, legs);
                }
            } else {
                currentState_Legs = laststate_Legs;
            }
        } while (laststate_Legs != currentState_Legs);
    } else {
        currentState_Legs = NULL;
    }

    if (g_showplayeranim->integer) {
        if (last_leg_anim_name != AnimName(legs)) {
            gi.DPrintf("Legs change from %s to %s\n", last_leg_anim_name.c_str(), AnimName(legs));
            last_leg_anim_name = AnimName(legs);
        }

        if (last_torso_anim_name != AnimName(torso)) {
            gi.DPrintf("Torso change from %s to %s\n", last_torso_anim_name.c_str(), AnimName(torso));
            last_torso_anim_name = AnimName(torso);
        }
    }

    if (g_showplayerstate->integer) {
        if (startstate_Legs != currentState_Legs) {
            gi.DPrintf(
                "Legs change from %s to %s\n",
                startstate_Legs ? startstate_Legs->getName() : "NULL",
                currentState_Legs ? currentState_Legs->getName() : "NULL"
            );
        }

        if (startstate_Torso != currentState_Torso) {
            gi.DPrintf(
                "Torso change from %s to %s\n",
                startstate_Torso ? startstate_Torso->getName() : "NULL",
                currentState_Torso ? currentState_Torso->getName() : "NULL"
            );
        }
    }

    // This is so we don't remember pain when we change to a state that has a PAIN condition
    pain = 0;
}

void Player::SelectPreviousItem(Event *ev)
{
    if (deadflag) {
        return;
    }

    Item *item = GetActiveWeapon(WEAPON_MAIN);

    item = PrevItem(item);

    if (item) {
        useWeapon((Weapon *)item, WEAPON_MAIN);
    }
}

void Player::SelectNextItem(Event *ev)
{
    if (deadflag) {
        return;
    }

    Item *item = GetActiveWeapon(WEAPON_MAIN);

    item = NextItem(item);

    if (item) {
        useWeapon((Weapon *)item, WEAPON_MAIN);
    }
}

void Player::SelectPreviousWeapon(Event *ev)
{
    Weapon *weapon;
    Weapon *initialWeapon;
    Weapon *activeWeapon;

    if (deadflag) {
        return;
    }

    activeWeapon = GetActiveWeapon(WEAPON_MAIN);
    if (activeWeapon && activeWeapon->IsSubclassOfInventoryItem()) {
        activeWeapon = NULL;
    }

    if (!activeWeapon) {
        activeWeapon = newActiveWeapon.weapon;
        if (activeWeapon && activeWeapon->IsSubclassOfInventoryItem()) {
            activeWeapon = NULL;
        }
    }

    if (activeWeapon) {
        // Fixed in OPM
        //  Fixes the bug that cause infinite loop when the last weapon has no ammo
        //  and the only weapon is an inventory item
        for (weapon = initialWeapon = PreviousWeapon(activeWeapon); weapon && weapon != activeWeapon; ) {
            if (g_gametype->integer == GT_SINGLE_PLAYER || !weapon->IsSubclassOfInventoryItem()) {
                break;
            }

            weapon = PreviousWeapon(weapon);
            if (weapon == initialWeapon) {
                break;
            }
        }
    } else {
        weapon = BestWeapon();
    }

    if (weapon && weapon != activeWeapon) {
        useWeapon(weapon);
    }

    if (deadflag) {
        return;
    }
}

void Player::SelectNextWeapon(Event *ev)
{
    Weapon *weapon;
    Weapon *initialWeapon;
    Weapon *activeWeapon;

    if (deadflag) {
        return;
    }

    activeWeapon = GetActiveWeapon(WEAPON_MAIN);
    if (activeWeapon && activeWeapon->IsSubclassOfInventoryItem()) {
        activeWeapon = NULL;
    }

    if (!activeWeapon) {
        activeWeapon = newActiveWeapon.weapon;
        if (activeWeapon && activeWeapon->IsSubclassOfInventoryItem()) {
            activeWeapon = NULL;
        }
    }

    if (activeWeapon) {
        // Fixed in OPM
        //  Fixes the bug that cause infinite loop when the last weapon has no ammo
        //  and the only weapon is an inventory item
        for (weapon = initialWeapon = NextWeapon(activeWeapon); weapon && weapon != activeWeapon; ) {
            if (g_gametype->integer == GT_SINGLE_PLAYER || !weapon->IsSubclassOfInventoryItem()) {
                break;
            }

            weapon = NextWeapon(weapon);
            if (weapon == initialWeapon) {
                break;
            }
        }
    } else {
        weapon = WorstWeapon();
    }

    if (weapon && weapon != activeWeapon) {
        useWeapon(weapon);
    }
}

void Player::DropCurrentWeapon(Event *ev)
{
    Weapon *weapon;
    Vector  forward;

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }

    weapon = GetActiveWeapon(WEAPON_MAIN);

    if (!weapon) {
        return;
    }

    // Don't drop the weapon if we're charging
    if (charge_start_time) {
        return;
    }

    if ((weapon->GetWeaponClass() & WEAPON_CLASS_ITEM)) {
        SelectNextWeapon(NULL);
        takeItem(weapon->model);
    } else {
        if (weapon->GetCurrentAttachToTag() != "tag_weapon_right") {
            EventCorrectWeaponAttachments(NULL);
        }

        // This check isn't in MOHAA
        if (!weapon->IsDroppable()) {
            return;
        }

        weapon->Drop();

        AngleVectors(m_vViewAng, forward, NULL, NULL);

        // make the weapon looks like it's thrown
        weapon->velocity = forward * 200.0f;

        edict->s.eFlags |= EF_UNARMED;

        SelectNextWeapon(NULL);

        if (holsteredWeapon == weapon) {
            holsteredWeapon = NULL;
        }
        if (lastActiveWeapon.weapon == weapon) {
            lastActiveWeapon.weapon = NULL;
        }
    }
}

void Player::GiveWeaponCheat(Event *ev)
{
    giveItem(ev->GetString(1));
}

void Player::GiveCheat(Event *ev)
{
    str name;

    if (deadflag) {
        return;
    }

    name = ev->GetString(1);

    if (!name.icmp("all")) {
        GiveAllCheat(ev);
        return;
    }
    EventGiveItem(ev);
}

void Player::GiveAllCheat(Event *ev)
{
    char *buffer;
    char *buf;
    char  com_token[MAX_STRING_CHARS];

    if (deadflag) {
        return;
    }

    if (gi.FS_ReadFile("global/giveall.scr", (void **)&buf, true) != -1) {
        buffer = buf;
        while (1) {
            Q_strncpyz(com_token, COM_ParseExt(&buffer, qtrue), sizeof(com_token));

            if (!com_token[0]) {
                break;
            }

            // Create the event
            ev = new Event(com_token);

            // get the rest of the line
            while (1) {
                Q_strncpyz(com_token, COM_ParseExt(&buffer, qfalse), sizeof(com_token));
                if (!com_token[0]) {
                    break;
                }

                ev->AddToken(com_token);
            }

            this->ProcessEvent(ev);
        }
        gi.FS_FreeFile(buf);
    }
}

void Player::GiveNewWeaponsCheat(Event *ev)
{
    char       *buffer;
    char       *current;
    const char *token;

    if (deadflag != DEAD_NO) {
        return;
    }

    if (gi.FS_ReadFile("global/givenewweapons.scr", (void **)&buffer, qtrue) != -1) {
        return;
    }

    current = buffer;
    for (;;) {
        Event *event;

        token = COM_ParseExt(&current, qtrue);
        if (!token[0]) {
            break;
        }

        event = new Event(token);

        for (;;) {
            token = COM_ParseExt(&current, qfalse);
            if (!token[0]) {
                break;
            }

            event->AddToken(token);
        }

        ProcessEvent(event);
    }

    gi.FS_FreeFile(buffer);
}

void Player::GodCheat(Event *ev)
{
    const char *msg;

    if (ev->NumArgs() > 0) {
        if (ev->GetInteger(1)) {
            flags |= FL_GODMODE;
            // Also enable the god mode for the vehicle
            if (m_pVehicle) {
                m_pVehicle->flags |= FL_GODMODE;
            }
        } else {
            flags &= ~FL_GODMODE;
            // Also disable the god mode for the vehicle
            if (m_pVehicle) {
                m_pVehicle->flags &= ~FL_GODMODE;
            }
        }
    } else {
        if (flags & FL_GODMODE) {
            flags &= ~FL_GODMODE;
            if (m_pVehicle) {
                m_pVehicle->flags &= ~FL_GODMODE;
            }
        } else {
            flags |= FL_GODMODE;
            if (m_pVehicle) {
                m_pVehicle->flags |= FL_GODMODE;
            }
        }
    }

    if (ev->isSubclassOf(ConsoleEvent)) {
        if (!(flags & FL_GODMODE)) {
            msg = "CHEAT: godmode OFF\n";
        } else {
            msg = "CHEAT: godmode ON\n";
        }

        gi.SendServerCommand(edict - g_entities, "print \"%s\"", msg);
    }
}

void Player::Kill(Event *ev)
{
    if ((level.time - respawn_time) < 5) {
        return;
    }

    flags &= ~FL_GODMODE;
    health = 1;
    Damage(this, this, 10, origin, vec_zero, vec_zero, 0, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
}

void Player::NoTargetCheat(Event *ev)
{
    const char *msg;

    flags ^= FL_NOTARGET;
    if (!(flags & FL_NOTARGET)) {
        msg = "notarget OFF\n";
    } else {
        msg = "notarget ON\n";
    }

    gi.SendServerCommand(edict - g_entities, "print \"%s\"", msg);
}

void Player::NoclipCheat(Event *ev)
{
    const char *msg;

    if (m_pVehicle) {
        msg = "Must exit vehicle first\n";
    } else if (m_pTurret) {
        msg = "Must exit turret first\n";
    } else if (getMoveType() == MOVETYPE_NOCLIP) {
        setMoveType(MOVETYPE_WALK);
        msg = "noclip OFF\n";

        // reset the state machine so that his animations are correct
        ResetState(NULL);
        charge_start_time = 0;
    } else {
        client->ps.feetfalling = false;
        movecontrol            = MOVECONTROL_LEGS;

        setMoveType(MOVETYPE_NOCLIP);
        msg = "noclip ON\n";
    }

    gi.SendServerCommand(edict - g_entities, "print \"%s\"", msg);
}

void Player::GameVersion(Event *ev)
{
    gi.SendServerCommand(edict - g_entities, "print \"%s : %s\n\"", GAMEVERSION, __DATE__);
}

void Player::SetFov(float newFov)
{
    fov = newFov;

    if (fov < 1) {
        fov = 80;
    } else if (fov > 160) {
        fov = 160;
    }
}

void Player::EventSetSelectedFov(Event *ev)
{
    float fOldSelectedFov;

    if (ev->NumArgs() < 1) {
        gi.SendServerCommand(edict - g_entities, "print \"Fov = %d\n\"", (unsigned int)fov);
        return;
    }

    fOldSelectedFov = selectedfov;
    SetSelectedFov(ev->GetFloat(1));
    if (fov == fOldSelectedFov) {
        SetFov(selectedfov);
    }
}

void Player::SetSelectedFov(float newFov)
{
    selectedfov = newFov;

    if (selectedfov < 1) {
        selectedfov = 80;
    } else if (selectedfov > 160) {
        selectedfov = 160;
    }

    /*
    if( g_gametype->integer != GT_SINGLE_PLAYER && !developer->integer )
    {
        if( selectedfov < 80 )
        {
            selectedfov = 80;
        }
        else if( selectedfov > 80 )
        {
            selectedfov = 80;
        }
    }
    */
}

/*
===============
CalcRoll

===============
*/
float Player::CalcRoll(void)
{
    float  sign;
    float  side;
    float  value;
    Vector l;

    angles.AngleVectors(NULL, &l, NULL);
    side = velocity * l;
    sign = side < 0 ? 4 : -4;
    side = fabs(side);

    value = sv_rollangle->value;

    if (side < sv_rollspeed->value) {
        side = side * value / sv_rollspeed->value;
    } else {
        side = value;
    }

    return side * sign;
}

//
// PMove Events
//
void Player::ProcessPmoveEvents(int event)
{
    float damage;

    switch (event) {
    case EV_NONE:
        break;
    case EV_FALL_SHORT:
    case EV_FALL_MEDIUM:
    case EV_FALL_FAR:
    case EV_FALL_FATAL:
        if (event == EV_FALL_FATAL) {
            if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
                damage = 101;
            } else {
                damage = max_health + 1.0f;
            }
        } else if (event == EV_FALL_FAR) {
            if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
                damage = 25;
            } else {
                damage = 20;
            }
        } else if (event == EV_FALL_MEDIUM) {
            if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
                damage = 15;
            } else {
                damage = 10;
            }
        } else {
            damage = 5;
        }

        if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
            // since 2.0, remove a percentage of the health
            damage = damage * (max_health / 100.0);
        }
        if (g_gametype->integer == GT_SINGLE_PLAYER || !DM_FLAG(DF_NO_FALLING)) {
            Damage(this, this, (int)damage, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_FALLING);
        }
        break;
    case EV_TERMINAL_VELOCITY:
        Sound("snd_fall", CHAN_VOICE);
        break;
    case EV_WATER_LEAVE: // foot leaves
        Sound("impact_playerleavewater", CHAN_AUTO);
        break;
    case EV_WATER_UNDER: // head touches
        Sound("impact_playersubmerge", CHAN_AUTO);
        break;
    case EV_WATER_CLEAR: // head leaves
        Sound("snd_gasp", CHAN_LOCAL);
        break;
    }
}

/*
=============
WorldEffects
=============
*/
void Player::WorldEffects(void)
{
    if (deadflag == DEAD_DEAD || getMoveType() == MOVETYPE_NOCLIP) {
        // if we are dead or no-cliping, no world effects
        return;
    }

    //
    // check for on fire
    //
    if (on_fire) {
        if (next_painsound_time < level.time) {
            next_painsound_time = level.time + 4;
            Sound("snd_onfire", CHAN_LOCAL);
        }
    }
}

/*
=============
AddBlend
=============
*/
void Player::AddBlend(float r, float g, float b, float a)
{
    float a2;
    float a3;

    if (a <= 0) {
        return;
    }

    // new total alpha
    a2 = blend[3] + (1 - blend[3]) * a;

    // fraction of color from old
    a3 = blend[3] / a2;

    blend[0] = blend[0] * a3 + r * (1 - a3);
    blend[1] = blend[1] * a3 + g * (1 - a3);
    blend[2] = blend[2] * a3 + b * (1 - a3);
    blend[3] = a2;
}

/*
=============
CalcBlend
=============
*/
void Player::CalcBlend(void)
{
    int    contents;
    Vector vieworg;

    client->ps.stats[STAT_ADDFADE] = 0;
    blend[0] = blend[1] = blend[2] = blend[3] = 0;

    // add for contents
    vieworg = m_vViewPos;

    contents = gi.pointcontents(vieworg, 0);

    if (contents & CONTENTS_SOLID) {
        // Outside of world
        //AddBlend( 0.8, 0.5, 0.0, 0.2 );
    } else if (contents & CONTENTS_LAVA) {
        AddBlend(level.lava_color[0], level.lava_color[1], level.lava_color[2], level.lava_alpha);
    } else if (contents & CONTENTS_WATER) {
        AddBlend(level.water_color[0], level.water_color[1], level.water_color[2], level.water_alpha);
    }

    // add for damage
    if (damage_alpha > 0) {
        AddBlend(damage_blend[0], damage_blend[1], damage_blend[2], damage_alpha);

        // drop the damage value
        damage_alpha -= 0.06f;
        if (damage_alpha < 0) {
            damage_alpha = 0;
        }
        client->ps.blend[0] = blend[0];
        client->ps.blend[1] = blend[1];
        client->ps.blend[2] = blend[2];
        client->ps.blend[3] = blend[3];
    }

    // Do the cinematic fading
    float alpha = 1;

    level.m_fade_time -= level.frametime;

    // Return if we are completely faded in
    if ((level.m_fade_time <= 0) && (level.m_fade_type == fadein)) {
        client->ps.blend[3] = 0 + damage_alpha;
        return;
    }

    // If we are faded out, and another fade out is coming in, then don't bother
    if ((level.m_fade_time_start > 0) && (level.m_fade_type == fadeout)) {
        if (client->ps.blend[3] >= 1) {
            return;
        }
    }

    if (level.m_fade_time_start > 0) {
        alpha = level.m_fade_time / level.m_fade_time_start;
    }

    if (level.m_fade_type == fadeout) {
        alpha = 1.0f - alpha;
    }

    if (alpha < 0) {
        alpha = 0;
    }

    if (alpha > 1) {
        alpha = 1;
    }

    if (level.m_fade_style == additive) {
        client->ps.blend[0]            = level.m_fade_color[0] * level.m_fade_alpha * alpha;
        client->ps.blend[1]            = level.m_fade_color[1] * level.m_fade_alpha * alpha;
        client->ps.blend[2]            = level.m_fade_color[2] * level.m_fade_alpha * alpha;
        client->ps.blend[3]            = level.m_fade_alpha * alpha;
        client->ps.stats[STAT_ADDFADE] = 1;
    } else {
        client->ps.blend[0]            = level.m_fade_color[0];
        client->ps.blend[1]            = level.m_fade_color[1];
        client->ps.blend[2]            = level.m_fade_color[2];
        client->ps.blend[3]            = level.m_fade_alpha * alpha;
        client->ps.stats[STAT_ADDFADE] = 0;
    }
}

/*
===============
P_DamageFeedback

Handles color blends and view kicks
===============
*/

void Player::DamageFeedback(void)
{
    float  realcount;
    float  count;
    vec3_t vDir;
    str    painAnim;
    int    animnum;

    // if we are dead, don't setup any feedback
    if (IsDead()) {
        damage_count = 0;
        damage_blood = 0;
        damage_alpha = 0;
        VectorClear(damage_angles);
        return;
    }

    if (damage_count) {
        // decay damage_count over time
        damage_count *= 0.8f;
        damage_from *= 0.8f;
        damage_angles *= 0.8f;
        if (damage_count < 0.1f) {
            damage_count = 0;
            damage_from  = Vector(0, 0, 0);
        }
    }

    // total points of damage shot at the player this frame
    if (!damage_blood) {
        // didn't take any damage
        return;
    }

    VectorNormalize2(damage_from, vDir);

    damage_angles.x -=
        DotProduct(vDir, orientation[0]) * damage_blood * g_viewkick_pitch->value * g_viewkick_dmmult->value;
    damage_angles.x = Q_clamp_float(damage_angles.x, -30, 30);

    damage_angles.y -=
        DotProduct(vDir, orientation[1]) * damage_blood * g_viewkick_yaw->value * g_viewkick_dmmult->value;
    damage_angles.y = Q_clamp_float(damage_angles.y, -30, 30);

    damage_angles.z +=
        DotProduct(vDir, orientation[2]) * damage_blood * g_viewkick_roll->value * g_viewkick_dmmult->value;
    damage_angles.z = Q_clamp_float(damage_angles.y, -25, 25);

    damage_count += damage_blood;
    count     = damage_blood;
    realcount = count;
    if (count < 10) {
        // always make a visible effect
        count = 10;
    }

    // the total alpha of the blend is always proportional to count
    if (damage_alpha < 0) {
        damage_alpha = 0;
    }

    damage_alpha += count * 0.001;
    if (damage_alpha < 0.2f) {
        damage_alpha = 0.2f;
    }
    if (damage_alpha > 0.6f) {
        // don't go too saturated
        damage_alpha = 0.6f;
    }

    // the color of the blend will vary based on how much was absorbed
    // by different armors
    damage_blend = vec_zero;
    if (damage_blood) {
        damage_blend += (damage_blood / realcount) * bcolor;
    }

    if (g_target_game >= target_game_e::TG_MOHTA) {
        //
        // Since 2.0: Try to find and play pain animation
        //
        if (getMoveType() == MOVETYPE_PORTABLE_TURRET) {
            // use mg42 pain animation
            painAnim = "mg42_tripod_";
        } else {
            Weapon     *pWeap;
            const char *itemName;
            // try to find an animation

            pWeap = GetActiveWeapon(WEAPON_MAIN);
            if (pWeap) {
                int weapon_class;

                weapon_class = pWeap->GetWeaponClass();
                if (weapon_class & WEAPON_CLASS_PISTOL) {
                    painAnim = "pistol_";
                } else if (weapon_class & WEAPON_CLASS_RIFLE) {
                    painAnim = "rifle_";
                } else if (weapon_class & WEAPON_CLASS_SMG) {
                    // get the animation name from the item name
                    itemName = pWeap->GetItemName();

                    if (!Q_stricmp(itemName, "MP40")) {
                        painAnim = "mp40_";
                    } else if (!Q_stricmp(itemName, "Sten Mark II")) {
                        painAnim = "sten_";
                    } else {
                        painAnim = "smg_";
                    }
                } else if (weapon_class & WEAPON_CLASS_MG) {
                    itemName = pWeap->GetItemName();

                    if (!Q_stricmp(itemName, "StG 44")) {
                        painAnim = "mp44_";
                    } else {
                        painAnim = "mg_";
                    }
                } else if (weapon_class & WEAPON_CLASS_GRENADE) {
                    itemName = pWeap->GetItemName();

                    // 2.30: use landmine animations
                    if (!Q_stricmp(itemName, "Minedetector")) {
                        painAnim = "minedetector_";
                    } else if (!Q_stricmp(itemName, "Minensuchgerat")) {
                        painAnim = "minedetectoraxis_";
                    } else if (!Q_stricmp(itemName, "LandmineAllies")) {
                        painAnim = "mine_";
                    } else if (!Q_stricmp(itemName, "LandmineAxis")) {
                        painAnim = "mine_";
                    } else if (!Q_stricmp(itemName, "LandmineAxis")) {
                        painAnim = "grenade_";
                    }
                } else if (weapon_class & WEAPON_CLASS_HEAVY) {
                    itemName = pWeap->GetItemName();

                    if (!Q_stricmp(itemName, "Shotgun")) {
                        painAnim = "shotgun_";
                    } else {
                        // Defaults to bazooka
                        painAnim = "bazooka_";
                    }
                } else {
                    itemName = pWeap->GetItemName();

                    if (!Q_stricmp(itemName, "Packed MG42 Turret")) {
                        painAnim = "mg42_";
                    } else {
                        // Default animation if not found
                        painAnim = "unarmed_";
                    }
                }
            } else {
                painAnim = "unarmed_";
            }

            // use the animation based on the movement
            if (m_iMovePosFlags & MPF_POSITION_CROUCHING) {
                painAnim += "crouch_";
            } else {
                painAnim += "stand_";
            }
        }

        painAnim += "hit_";

        if (pain_dir == PAIN_REAR || pain_location == HITLOC_TORSO_MID || HITLOC_TORSO_LOWER) {
            painAnim += "back";
        } else {
            switch (pain_location) {
            case HITLOC_HEAD:
            case HITLOC_HELMET:
            case HITLOC_NECK:
                painAnim += "head";
                break;
            case HITLOC_TORSO_UPPER:
            case HITLOC_TORSO_MID:
                painAnim += "uppertorso";
                break;
            case HITLOC_TORSO_LOWER:
            case HITLOC_PELVIS:
                painAnim += "lowertorso";
                break;
            case HITLOC_R_ARM_UPPER:
            case HITLOC_R_ARM_LOWER:
            case HITLOC_R_HAND:
                painAnim += "rarm";
                break;
            case HITLOC_L_ARM_UPPER:
            case HITLOC_L_ARM_LOWER:
            case HITLOC_L_HAND:
                painAnim += "larm";
                break;
            case HITLOC_R_LEG_UPPER:
            case HITLOC_L_LEG_UPPER:
            case HITLOC_R_LEG_LOWER:
            case HITLOC_L_LEG_LOWER:
            case HITLOC_R_FOOT:
            case HITLOC_L_FOOT:
                painAnim += "leg";
                break;
            default:
                painAnim += "uppertorso";
                break;
            }
        }

        animnum = gi.Anim_NumForName(edict->tiki, painAnim.c_str());
        if (animnum == -1) {
            gi.DPrintf("WARNING: Could not find player pain animation '%s'\n", painAnim.c_str());
        } else {
            NewAnim(animnum, EV_Player_AnimLoop_Pain, ANIMSLOT_PAIN);
            RestartAnimSlot(ANIMSLOT_PAIN);
            m_sPainAnim   = painAnim;
            m_fPainBlend  = 1.f;
            animdone_Pain = false;
        }
    }

    //
    // clear totals
    //
    damage_blood = 0;

    if (g_target_game >= target_game_e::TG_MOHTA) {
        //
        // Added in 2.0
        //  No more damage angles since MOHTA
        //

        if (IsSubclassOfPlayer()) {
            damage_count  = 0;
            damage_blood  = 0;
            damage_alpha  = 0;
            damage_angles = vec_zero;
        }
    }
}

void Player::GetPlayerView(Vector *pos, Vector *angle)
{
    if (pos) {
        *pos = origin;
        pos->z += viewheight;
    }

    if (angle) {
        *angle = Vector(client->ps.viewangles);
    }
}

void Player::SetPlayerView(
    Camera *camera, Vector position, float cameraoffset, Vector ang, Vector vel, float camerablend[4], float camerafov
)
{
    VectorCopy(ang, client->ps.viewangles);
    client->ps.viewheight = cameraoffset;

    VectorCopy(position, client->ps.origin);
    VectorCopy(vel, client->ps.velocity);

    /*
    client->ps.blend[ 0 ] = camerablend[ 0 ];
    client->ps.blend[ 1 ] = camerablend[ 1 ];
    client->ps.blend[ 2 ] = camerablend[ 2 ];
    client->ps.blend[ 3 ] = camerablend[ 3 ];
    */

    client->ps.fov = camerafov;

    if (camera) {
        if (camera->IsSubclassOfCamera()) {
            VectorCopy(camera->angles, client->ps.camera_angles);
            VectorCopy(camera->origin, client->ps.camera_origin);

            Vector vOfs = camera->GetPositionOffset();
            VectorCopy(vOfs, client->ps.camera_posofs);

            client->ps.pm_flags |= PMF_CAMERA_VIEW;

            if (camera->ShowQuakes()) {
                client->ps.pm_flags |= PMF_DAMAGE_ANGLES;
            } else {
                client->ps.pm_flags &= ~PMF_DAMAGE_ANGLES;
            }

            //
            // clear out the flags, but preserve the CF_CAMERA_CUT_BIT
            //
            client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT;
        } else {
            Vector vVec;

            if (camera->IsSubclassOfPlayer()) {
                Vector  vPos;
                Player *pPlayer = (Player *)camera;

                GetSpectateFollowOrientation(pPlayer, vPos, vVec);

                VectorCopy(vVec, client->ps.camera_angles);
                VectorCopy(vPos, client->ps.camera_origin);

                SetViewAngles(vVec);

                vPos[2] -= viewheight;
                setOrigin(vPos);

                vVec.setXYZ(0, 0, 0);
            } else {
                VectorCopy(ang, client->ps.camera_angles);
                VectorCopy(position, client->ps.camera_angles);

                vVec.setXYZ(0, 0, 0);
            }

            VectorCopy(vVec, client->ps.camera_posofs);
            client->ps.pm_flags |= PMF_CAMERA_VIEW;
            client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT;
        }
    } else {
        client->ps.pm_flags &= ~PMF_CAMERA_VIEW;
        //
        // make sure the third person camera is setup correctly.
        //

        if (getMoveType() != MOVETYPE_NOCLIP) {
            qboolean do_cut;
            int      camera_type;

            if (currentState_Torso) {
                camera_type = currentState_Torso->getCameraType();
            } else {
                camera_type = CAMERA_BEHIND;
            }
            if (last_camera_type != camera_type) {
                //
                // clear out the flags, but preserve the CF_CAMERA_CUT_BIT
                //
                client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT;
                do_cut                  = qtrue;
                switch (camera_type) {
                case CAMERA_TOPDOWN:
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH;
                    client->ps.camera_offset[PITCH] = -75;
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET;
                    do_cut = qfalse;
                    break;
                case CAMERA_FRONT:
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH;
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET;
                    client->ps.camera_offset[YAW]   = 180;
                    client->ps.camera_offset[PITCH] = 0;
                    break;
                case CAMERA_SIDE:
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH;
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET;
                    // randomly invert the YAW
                    if (G_Random(1) > 0.5f) {
                        client->ps.camera_offset[YAW] = -90;
                    } else {
                        client->ps.camera_offset[YAW] = 90;
                    }
                    client->ps.camera_offset[PITCH] = 0;
                    break;
                case CAMERA_SIDE_LEFT:
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH;
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET;
                    client->ps.camera_offset[YAW]   = 90;
                    client->ps.camera_offset[PITCH] = 0;
                    break;
                case CAMERA_SIDE_RIGHT:
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH;
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET;
                    client->ps.camera_offset[YAW]   = -90;
                    client->ps.camera_offset[PITCH] = 0;
                    break;
                case CAMERA_BEHIND_FIXED:
                    do_cut                          = qfalse;
                    client->ps.camera_offset[YAW]   = 0;
                    client->ps.camera_offset[PITCH] = 0;
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET;
                    break;
                case CAMERA_BEHIND_NOPITCH:
                    do_cut = qfalse;
                    client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH;
                    client->ps.camera_offset[YAW]   = 0;
                    client->ps.camera_offset[PITCH] = 0;
                    break;
                case CAMERA_BEHIND:
                    do_cut                          = qfalse;
                    client->ps.camera_offset[YAW]   = 0;
                    client->ps.camera_offset[PITCH] = 0;
                    break;
                default:
                    do_cut                          = qfalse;
                    client->ps.camera_offset[YAW]   = 0;
                    client->ps.camera_offset[PITCH] = 0;
                    break;
                }
                last_camera_type = camera_type;
                if (do_cut) {
                    CameraCut();
                }
            }
        } else {
            client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT;
        }

        //
        // these are explicitly not cleared so that when the client lerps it still has the last
        // camera position for reference. Additionally this causes no extra hits to the network
        // traffic.
        //
        //VectorClear( client->ps.camera_angles );
        //VectorClear( client->ps.camera_origin );
    }

#define EARTHQUAKE_SCREENSHAKE_PITCH 2
#define EARTHQUAKE_SCREENSHAKE_YAW   2
#define EARTHQUAKE_SCREENSHAKE_ROLL  3

    if (level.earthquake_magnitude != 0.0f) {
        client->ps.damage_angles[PITCH] = G_CRandom() * level.earthquake_magnitude * EARTHQUAKE_SCREENSHAKE_PITCH;
        client->ps.damage_angles[YAW]   = G_CRandom() * level.earthquake_magnitude * EARTHQUAKE_SCREENSHAKE_YAW;
        client->ps.damage_angles[ROLL]  = G_CRandom() * level.earthquake_magnitude * EARTHQUAKE_SCREENSHAKE_ROLL;
    } else if (damage_count) {
        client->ps.damage_angles[PITCH] = damage_angles[PITCH];
        client->ps.damage_angles[YAW]   = damage_angles[YAW];
        client->ps.damage_angles[ROLL]  = damage_angles[ROLL];
    } else {
        VectorClear(client->ps.damage_angles);
    }

    if (m_vViewVariation != vec_zero) {
        for (int i = 0; i < 3; i++) {
            if (m_vViewVariation[i] == 0.0f) {
                continue;
            }

            client->ps.damage_angles[i] += G_CRandom() * m_vViewVariation[i];

            m_vViewVariation[i] = m_vViewVariation[i] - m_vViewVariation[i] * level.frametime * 8.0f;

            if (m_vViewVariation[i] < 0.01f) {
                m_vViewVariation[i] = 0.0f;
            }
        }
    }
}

void Player::SetupView(void)
{
    // if we currently are not in a camera or the camera we are looking through is automatic, evaluate our camera choices

    if (actor_to_watch || actor_camera) {
        Vector   dir;
        Vector   watch_angles;
        float    dist = 0;
        Vector   focal_point;
        Vector   left;
        trace_t  trace;
        qboolean delete_actor_camera = false;
        Vector   camera_mins;
        Vector   camera_maxs;

        if (actor_to_watch) {
            dir  = actor_to_watch->origin - origin;
            dist = dir.length();
        }

        // See if we still want to watch this actor

        if (!actor_to_watch || dist > 150 || actor_to_watch->deadflag) {
            delete_actor_camera = true;
        } else {
            // Create the camera if we don't have one yet

            if (!actor_camera) {
                actor_camera = new Camera();

                if (G_Random() < .5) {
                    actor_camera_right          = true;
                    starting_actor_camera_right = true;
                } else {
                    actor_camera_right          = false;
                    starting_actor_camera_right = false;
                }
            }

            // Setup the new position of the actor camera

            // Go a little above the view height

            actor_camera->origin = origin;
            actor_camera->origin[2] += DEFAULT_VIEWHEIGHT + 10;

            // Find the focal point ( either the actor's watch offset or top of the bounding box)

            if (actor_to_watch->watch_offset != vec_zero) {
                MatrixTransformVector(actor_to_watch->watch_offset, actor_to_watch->orientation, focal_point);
                focal_point += actor_to_watch->origin;
            } else {
                focal_point    = actor_to_watch->origin;
                focal_point[2] = actor_to_watch->maxs[2];
            }

            // Shift the camera back just a little

            dir = focal_point - actor_camera->origin;
            dir.normalize();
            actor_camera->origin -= dir * 15;

            // Shift the camera a little to the left or right

            watch_angles = dir.toAngles();
            watch_angles.AngleVectors(NULL, &left);

            if (actor_camera_right) {
                actor_camera->origin -= left * 15;
            } else {
                actor_camera->origin += left * 15;
            }

            // Make sure this camera position is ok

            camera_mins = "-5 -5 -5";
            camera_maxs = "5 5 5";

            trace = G_Trace(
                actor_camera->origin,
                camera_mins,
                camera_maxs,
                actor_camera->origin,
                actor_camera,
                MASK_DEADSOLID,
                false,
                "SetupView"
            );

            if (trace.startsolid) {
                // Try other side

                if (actor_camera_right == starting_actor_camera_right) {
                    if (actor_camera_right) {
                        actor_camera->origin += left * 30;
                    } else {
                        actor_camera->origin -= left * 30;
                    }

                    actor_camera_right = !actor_camera_right;

                    trace = G_Trace(
                        actor_camera->origin,
                        camera_mins,
                        camera_maxs,
                        actor_camera->origin,
                        actor_camera,
                        MASK_DEADSOLID,
                        false,
                        "SetupView2"
                    );

                    if (trace.startsolid) {
                        // Both spots have failed stop doing actor camera
                        delete_actor_camera = true;
                    }
                } else {
                    // Both spots have failed stop doing actor camera
                    delete_actor_camera = true;
                }
            }

            if (!delete_actor_camera) {
                // Set the camera's position

                actor_camera->setOrigin(actor_camera->origin);

                // Set the camera's angles

                dir          = focal_point - actor_camera->origin;
                watch_angles = dir.toAngles();
                actor_camera->setAngles(watch_angles);

                // Set this as our camera

                SetCamera(actor_camera, .5);
            }
        }

        if (delete_actor_camera) {
            // Get rid of this camera

            actor_to_watch = NULL;

            if (actor_camera) {
                delete actor_camera;
                actor_camera = NULL;
                SetCamera(NULL, .5);
            }
        }
    } else if ((level.automatic_cameras.NumObjects() > 0) && (!camera || camera->IsAutomatic())) {
        int     i;
        float   score, bestScore;
        Camera *cam, *bestCamera;

        bestScore  = 999;
        bestCamera = NULL;
        for (i = 1; i <= level.automatic_cameras.NumObjects(); i++) {
            cam   = level.automatic_cameras.ObjectAt(i);
            score = cam->CalculateScore(this, currentState_Torso->getName());
            // if this is our current camera, scale down the score a bit to favor it.
            if (cam == camera) {
                score *= 0.9f;
            }

            if (score < bestScore) {
                bestScore  = score;
                bestCamera = cam;
            }
        }
        if (bestScore <= 1.0f) {
            // we have a camera to switch to
            if (bestCamera != camera) {
                float time;

                if (camera) {
                    camera->AutomaticStop(this);
                }
                time = bestCamera->AutomaticStart(this);
                SetCamera(bestCamera, time);
            }
        } else {
            // we don't have a camera to switch to
            if (camera) {
                float time;

                time = camera->AutomaticStop(this);
                SetCamera(NULL, time);
            }
        }
    }

    // If there is no camera, use the player's view
    if (!camera) {
        if (g_gametype->integer != GT_SINGLE_PLAYER && IsSpectator() && m_iPlayerSpectating != 0) {
            gentity_t *ent = g_entities + m_iPlayerSpectating - 1;

            if (ent->inuse && ent->entity && ent->entity->deadflag <= DEAD_DYING) {
                Player *m_player = (Player *)ent->entity;
                Vector  vAngles;

                m_player->GetPlayerView(NULL, &vAngles);

                SetPlayerView(
                    (Camera *)m_player,
                    m_player->origin,
                    m_player->viewheight,
                    vAngles,
                    m_player->velocity,
                    blend,
                    m_player->fov
                );
            } else {
                SetPlayerView(NULL, origin, viewheight, v_angle, velocity, blend, fov);
            }
        } else {
            SetPlayerView(NULL, origin, viewheight, v_angle, velocity, blend, fov);
        }
    } else {
        SetPlayerView(camera, origin, viewheight, v_angle, velocity, blend, camera->Fov());
    }
}

Vector Player::GetAngleToTarget(Entity *ent, str tag, float yawclamp, float pitchclamp, Vector baseangles)
{
    assert(ent);

    if (ent) {
        Vector        delta, angs;
        orientation_t tag_or;

        int tagnum = gi.Tag_NumForName(edict->tiki, tag.c_str());

        if (tagnum < 0) {
            return Vector(0, 0, 0);
        }

        GetTagPositionAndOrientation(tagnum, &tag_or);

        delta = ent->centroid - tag_or.origin;
        delta.normalize();

        angs = delta.toAngles();

        AnglesSubtract(angs, baseangles, angs);

        angs[PITCH] = AngleNormalize180(angs[PITCH]);
        angs[YAW]   = AngleNormalize180(angs[YAW]);

        if (angs[PITCH] > pitchclamp) {
            angs[PITCH] = pitchclamp;
        } else if (angs[PITCH] < -pitchclamp) {
            angs[PITCH] = -pitchclamp;
        }

        if (angs[YAW] > yawclamp) {
            angs[YAW] = yawclamp;
        } else if (angs[YAW] < -yawclamp) {
            angs[YAW] = -yawclamp;
        }

        return angs;
    } else {
        return Vector(0, 0, 0);
    }
}

void Player::DebugWeaponTags(int controller_tag, Weapon *weapon, str weapon_tagname)
{
    int           i;
    orientation_t bone_or, tag_weapon_or, barrel_or, final_barrel_or;

    GetTagPositionAndOrientation(edict->s.bone_tag[controller_tag], &bone_or);
    //G_DrawCoordSystem( Vector( bone_or.origin ), Vector( bone_or.axis[0] ), Vector( bone_or.axis[1] ), Vector( bone_or.axis[2] ), 20 );

    GetTagPositionAndOrientation(gi.Tag_NumForName(edict->tiki, weapon_tagname), &tag_weapon_or);
    //G_DrawCoordSystem( Vector( tag_weapon_or.origin ), Vector( tag_weapon_or.axis[0] ), Vector( tag_weapon_or.axis[1] ), Vector( tag_weapon_or.axis[2] ), 40 );

    weapon->GetRawTag("tag_barrel", &barrel_or);
    VectorCopy(tag_weapon_or.origin, final_barrel_or.origin);

    for (i = 0; i < 3; i++) {
        VectorMA(final_barrel_or.origin, barrel_or.origin[i], tag_weapon_or.axis[i], final_barrel_or.origin);
    }

    MatrixMultiply(barrel_or.axis, tag_weapon_or.axis, final_barrel_or.axis);
    //G_DrawCoordSystem( Vector( final_barrel_or.origin ), Vector( final_barrel_or.axis[0] ), Vector( final_barrel_or.axis[1] ), Vector( final_barrel_or.axis[2] ), 80 );

#if 0
   if ( g_crosshair->integer )
      {
      trace_t trace;
      Vector  start,end,ang,dir,delta;
      vec3_t  mat[3];

      AnglesToAxis( v_angle, mat );

      dir   = mat[0];
      start = final_barrel_or.origin;
      end   = start + ( dir *  MAX_MAP_BOUNDS ); 

      G_DrawCoordSystem( start, Vector( mat[0] ), Vector( mat[1] ), Vector( mat[2] ), 80 );
      
      trace = G_Trace( start, vec_zero, vec_zero, end, this, MASK_PROJECTILE|MASK_WATER, qfalse, "Crosshair" );
      crosshair->setOrigin( trace.endpos );

      delta = trace.endpos - start;
      float length = delta.length();
      float scale  = g_crosshair_maxscale->value * length / MAX_MAP_BOUNDS;
      
      if ( scale < 1 )
         scale = 1;

      crosshair->setScale( scale );

      if ( trace.ent )
         {
         vectoangles( trace.plane.normal, ang );
         }
      else
         {
         vectoangles( dir, ang );
         }

      crosshair->setAngles( ang );
      }
#endif
}

void Player::AcquireTarget(void) {}

void Player::RemoveTarget(Entity *ent_to_remove) {}

void Player::AutoAim(void) {}

/*
===============
PlayerAngles
===============
*/
void Player::PlayerAngles(void)
{
    if (getMoveType() == MOVETYPE_PORTABLE_TURRET) {
        PortableTurret *portableTurret = static_cast<PortableTurret *>(m_pTurret.Pointer());
        angles[0]                      = portableTurret->GetGroundPitch();
        angles[1]                      = portableTurret->GetStartYaw();
    }

    PmoveAdjustAngleSettings(v_angle, angles, &client->ps, &edict->s);

    SetViewAngles(v_angle);
    setAngles(angles);
}

void Player::FinishMove(void)
{
    //
    // If the origin or velocity have changed since ClientThink(),
    // update the pmove values.  This will happen when the client
    // is pushed by a bmodel or kicked by an explosion.
    //
    // If it wasn't updated here, the view position would lag a frame
    // behind the body position when pushed -- "sinking into plats",
    //
    if (!(client->ps.pm_flags & PMF_FROZEN) && !(client->ps.pm_flags & PMF_NO_MOVE)) {
        origin.copyTo(client->ps.origin);
        velocity.copyTo(client->ps.velocity);
    }

    // This check is in mohaa but the animation will look bad
    if (!(client->ps.pm_flags & PMF_FROZEN)) {
        PlayerAngles();
        AdjustAnimBlends();
    }

    // burn from lava, etc
    WorldEffects();

    // determine the view offsets
    DamageFeedback();
    CalcBlend();

    if (g_gametype->integer != GT_SINGLE_PLAYER && g_smoothClients->integer) {
        VectorCopy(client->ps.velocity, edict->s.pos.trDelta);
        edict->s.pos.trTime = client->ps.commandTime;
    } else {
        VectorClear(edict->s.pos.trDelta);
        edict->s.pos.trTime = 0;
    }
}

void Player::CopyStats(Player *player)
{
    gentity_t *ent;
    int        i;

    origin = player->origin;
    SetViewAngles(player->GetViewAngles());

    client->ps.bobCycle = player->client->ps.bobCycle;

    client->ps.pm_flags |=
        player->client->ps.pm_flags & (PMF_DUCKED | PMF_VIEW_DUCK_RUN | PMF_VIEW_JUMP_START | PMF_VIEW_PRONE);

    memcpy(&client->ps.stats, &player->client->ps.stats, sizeof(client->ps.stats));
    memcpy(&client->ps.activeItems, &player->client->ps.activeItems, sizeof(client->ps.activeItems));
    memcpy(&client->ps.ammo_name_index, &player->client->ps.ammo_name_index, sizeof(client->ps.ammo_name_index));
    memcpy(&client->ps.ammo_amount, &player->client->ps.ammo_amount, sizeof(client->ps.ammo_amount));
    memcpy(&client->ps.max_ammo_amount, &player->client->ps.max_ammo_amount, sizeof(client->ps.max_ammo_amount));

    VectorCopy(player->client->ps.origin, client->ps.origin);
    VectorCopy(player->client->ps.velocity, client->ps.velocity);

    client->ps.iViewModelAnim        = player->client->ps.iViewModelAnim;
    client->ps.iViewModelAnimChanged = player->client->ps.iViewModelAnimChanged;

    client->ps.gravity = player->client->ps.gravity;
    client->ps.speed   = player->client->ps.speed;

    // copy angles
    memcpy(&client->ps.delta_angles, &player->client->ps.delta_angles, sizeof(client->ps.delta_angles));

    memcpy(&client->ps.blend, &player->client->ps.blend, sizeof(client->ps.blend));
    memcpy(&client->ps.damage_angles, &player->client->ps.damage_angles, sizeof(client->ps.damage_angles));
    memcpy(&client->ps.viewangles, &player->client->ps.viewangles, sizeof(client->ps.delta_angles));

    // copy camera stuff
    //memcpy( &client->ps.camera_origin, &player->client->ps.camera_origin, sizeof( client->ps.camera_origin ) );
    //memcpy( &client->ps.camera_angles, &player->client->ps.camera_angles, sizeof( client->ps.camera_angles ) );
    //memcpy( &client->ps.camera_offset, &player->client->ps.camera_offset, sizeof( client->ps.camera_offset ) );
    //memcpy( &client->ps.camera_posofs, &player->client->ps.camera_posofs, sizeof( client->ps.camera_posofs ) );
    //client->ps.camera_time = player->client->ps.camera_time;
    //client->ps.camera_flags = player->client->ps.camera_flags;

    client->ps.fLeanAngle = player->client->ps.fLeanAngle;
    client->ps.fov        = player->client->ps.fov;

    client->ps.viewheight      = player->client->ps.viewheight;
    client->ps.walking         = player->client->ps.walking;
    client->ps.groundPlane     = player->client->ps.groundPlane;
    client->ps.groundEntityNum = player->client->ps.groundEntityNum;
    memcpy(&client->ps.groundTrace, &player->client->ps.groundTrace, sizeof(trace_t));

    edict->s.eFlags &= ~EF_UNARMED;
    edict->r.svFlags &= ~SVF_NOCLIENT;
    edict->s.renderfx &= ~RF_DONTDRAW;

    player->edict->r.svFlags |= SVF_NOTSINGLECLIENT;
    player->edict->r.singleClient = client->ps.clientNum;

    edict->r.svFlags |= SVF_SINGLECLIENT;
    edict->r.singleClient = client->ps.clientNum;

    client->ps.pm_flags |= PMF_FROZEN | PMF_NO_MOVE | PMF_NO_PREDICTION;

    memcpy(&edict->s.frameInfo, &player->edict->s.frameInfo, sizeof(edict->s.frameInfo));

    DetachAllChildren(NULL);

    for (i = 0; i < MAX_MODEL_CHILDREN; i++) {
        Entity *dest;

        if (player->children[i] == ENTITYNUM_NONE) {
            continue;
        }

        ent = g_entities + player->children[i];

        if (!ent->inuse || !ent->entity) {
            continue;
        }

        dest = new Entity;

        CloneEntity(dest, ent->entity);

        dest->edict->s.modelindex   = ent->entity->edict->s.modelindex;
        dest->edict->tiki           = ent->entity->edict->tiki;
        dest->edict->s.actionWeight = ent->entity->edict->s.actionWeight;
        memcpy(&dest->edict->s.frameInfo, &ent->entity->edict->s.frameInfo, sizeof(dest->edict->s.frameInfo));
        dest->CancelPendingEvents();
        dest->attach(entnum, ent->entity->edict->s.tag_num);

        dest->PostEvent(EV_DetachAllChildren, level.frametime);
    }
}

void Player::UpdateStats(void)
{
    int    i, count;
    Vector vObjectiveLocation;
    float  healthfrac;
    float  healfrac;

    //
    // Health
    //

    if (g_spectatefollow_firstperson->integer && IsSpectator() && m_iPlayerSpectating != 0) {
        //
        // Added in OPM
        //  First-person spectate
        //
        gentity_t *ent = g_entities + (m_iPlayerSpectating - 1);

        if (ent->inuse && ent->entity && ent->entity->deadflag <= DEAD_DYING) {
            CopyStats((Player *)ent->entity);
            return;
        }
    }

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        client->ps.stats[STAT_TEAM]              = TEAM_ALLIES;
        client->ps.stats[STAT_KILLS]             = 0;
        client->ps.stats[STAT_DEATHS]            = 0;
        client->ps.stats[STAT_HIGHEST_SCORE]     = 0;
        client->ps.stats[STAT_ATTACKERCLIENT]    = -1;
        client->ps.stats[STAT_INFOCLIENT]        = -1;
        client->ps.stats[STAT_INFOCLIENT_HEALTH] = 0;

        vObjectiveLocation = level.m_vObjectiveLocation;
    } else {
        client->ps.stats[STAT_TEAM] = dm_team;

        if (g_gametype->integer >= GT_TEAM && current_team != NULL) {
            client->ps.stats[STAT_KILLS]  = current_team->m_teamwins;
            client->ps.stats[STAT_DEATHS] = current_team->m_iDeaths;
        } else {
            client->ps.stats[STAT_KILLS]  = num_kills;
            client->ps.stats[STAT_DEATHS] = num_deaths;
        }

        if (g_gametype->integer < GT_TEAM) {
            gentity_t *ent;
            int        i;
            int        bestKills = -9999;

            // Get the best player
            for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
                if (!ent->inuse || !ent->client || !ent->entity) {
                    continue;
                }

                Player *p = (Player *)ent->entity;
                if (p->GetNumKills() > bestKills) {
                    bestKills = p->GetNumKills();
                }
            }

            client->ps.stats[STAT_HIGHEST_SCORE] = bestKills;
        } else {
            if (dmManager.GetTeamAxis()->m_teamwins > dmManager.GetTeamAllies()->m_teamwins) {
                client->ps.stats[STAT_HIGHEST_SCORE] = dmManager.GetTeamAxis()->m_teamwins;
            } else {
                client->ps.stats[STAT_HIGHEST_SCORE] = dmManager.GetTeamAllies()->m_teamwins;
            }
        }

        if (!pAttackerDistPointer) {
            client->ps.stats[STAT_ATTACKERCLIENT] = -1;
        } else if (fAttackerDispTime <= level.time && deadflag == DEAD_NO) {
            pAttackerDistPointer                  = NULL;
            client->ps.stats[STAT_ATTACKERCLIENT] = -1;
        } else {
            client->ps.stats[STAT_ATTACKERCLIENT] = pAttackerDistPointer->edict - g_entities;
        }

        client->ps.stats[STAT_INFOCLIENT]        = -1;
        client->ps.stats[STAT_INFOCLIENT_HEALTH] = 0;

        if (IsSpectator() || g_gametype->integer >= GT_TEAM) {
            if (m_iPlayerSpectating && IsSpectator()) {
                gentity_t *ent = g_entities + (m_iPlayerSpectating - 1);

                if (ent->inuse && ent->entity && deadflag < DEAD_DEAD) {
                    m_iInfoClient       = ent - g_entities;
                    m_iInfoClientHealth = ent->entity->health;
                    m_fInfoClientTime   = level.time;

                    float percent = ent->entity->health / ent->entity->max_health * 100.0f;

                    if (percent > 0.0f && percent < 1.0f) {
                        percent = 1.0f;
                    }

                    client->ps.stats[STAT_INFOCLIENT_HEALTH] = percent;
                }
            } else {
                Vector  vForward;
                trace_t trace;

                AngleVectors(m_vViewAng, vForward, NULL, NULL);

                Vector vEnd = m_vViewPos + vForward * 2048.0f;

                trace = G_Trace(m_vViewPos, vec_zero, vec_zero, vEnd, this, MASK_BEAM, qfalse, "infoclientcheck");

                if (trace.ent && trace.ent->entity->IsSubclassOfPlayer() && !(trace.ent->r.svFlags & SVF_NOCLIENT)) {
                    Player *p = static_cast<Player *>(trace.ent->entity);

                    if (IsSpectator() || p->GetTeam() == GetTeam()) {
                        m_iInfoClient       = trace.ent - g_entities;
                        m_iInfoClientHealth = p->health;
                        m_fInfoClientTime   = level.time;

                        float percent = trace.ent->entity->health / trace.ent->entity->max_health * 100.0f;

                        if (percent > 0.0f && percent < 1.0f) {
                            percent = 1.0f;
                        }

                        client->ps.stats[STAT_INFOCLIENT_HEALTH] = percent;
                    }
                }
            }

            if (m_iInfoClient != -1) {
                if (level.time <= m_fInfoClientTime + 1.5f) {
                    client->ps.stats[STAT_INFOCLIENT]        = m_iInfoClient;
                    client->ps.stats[STAT_INFOCLIENT_HEALTH] = m_iInfoClientHealth;
                } else {
                    m_iInfoClient = -1;
                }
            }
        }

        if (g_gametype->integer >= GT_TOW || level.m_bForceTeamObjectiveLocation) {
            if (GetTeam() == TEAM_AXIS) {
                vObjectiveLocation = level.m_vAxisObjectiveLocation;
            } else if (GetTeam() == TEAM_ALLIES) {
                vObjectiveLocation = level.m_vAlliedObjectiveLocation;
            }
        } else {
            vObjectiveLocation = level.m_vObjectiveLocation;
        }

        if (g_protocol < protocol_e::PROTOCOL_MOHTA_MIN && vObjectiveLocation == vec_zero) {
            //
            // try to use the nearest teammate instead.
            // the reason is that mohaa 1.11 and below doesn't have a radar
            // for teammates
            //
            if (g_gametype->integer > GT_FFA && !IsDead() && !IsSpectator()) {
                gentity_t *ent;
                int        i;
                Player    *p;
                float      fNearest = 9999.0f;
                float      fLength;

                // match the compass direction to the nearest player
                for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
                    if (!ent->inuse || !ent->client || !ent->entity || ent->entity == this) {
                        continue;
                    }

                    p = (Player *)ent->entity;
                    if (p->IsDead() || p->IsSpectator() || p->dm_team != dm_team) {
                        continue;
                    }

                    fLength = (p->centroid - centroid).length();

                    if (fLength < fNearest) {
                        fNearest           = fLength;
                        vObjectiveLocation = p->centroid;
                    }
                }
            }
        }
    }

    if (m_pVehicle && !m_pTurret) {
        client->ps.stats[STAT_VEHICLE_HEALTH]     = m_pVehicle->health;
        client->ps.stats[STAT_VEHICLE_MAX_HEALTH] = m_pVehicle->max_health;
    }

    //
    // Health fraction
    //
    healthfrac = (health / max_health * 100.0f);

    if (m_pVehicle && !m_pTurret) {
        if (!m_pVehicle->isSubclassOf(FixedTurret)) {
            healthfrac = (m_pVehicle->health / m_pVehicle->max_health * 100.f);
        }
    }

    if (healthfrac < 1 && healthfrac > 0) {
        healthfrac = 1;
    }
    if (healthfrac < 0) {
        healthfrac = 0;
    }

    client->ps.stats[STAT_HEALTH] = healthfrac;

    //
    // Healing
    //
    if (m_fHealRate && (!m_pVehicle || m_pTurret || m_pVehicle->isSubclassOf(FixedTurret))) {
        healfrac = (health + m_fHealRate) / max_health * 100.f;
    } else {
        healfrac = 0;
    }
    if (healfrac < 1 && healfrac > 0) {
        healfrac = 1;
    }
    if (healfrac < 0) {
        healfrac = 0;
    }

    client->ps.stats[STAT_NEXTHEALTH] = healfrac;
    client->ps.stats[STAT_MAXHEALTH]  = 100;

    Weapon *activeweap = GetActiveWeapon(WEAPON_MAIN);

    client->ps.stats[STAT_WEAPONS]         = 0;
    client->ps.stats[STAT_EQUIPPED_WEAPON] = 0;
    client->ps.stats[STAT_AMMO]            = 0;
    client->ps.stats[STAT_MAXAMMO]         = 0;
    client->ps.stats[STAT_CLIPAMMO]        = 0;
    client->ps.stats[STAT_MAXCLIPAMMO]     = 0;

    client->ps.activeItems[ITEM_AMMO]   = -1;
    client->ps.activeItems[ITEM_WEAPON] = -1;
    client->ps.activeItems[2]           = -1;
    client->ps.activeItems[3]           = -1;
    client->ps.activeItems[4]           = -1;
    client->ps.activeItems[5]           = -1;

    if (m_pTurret) {
        client->ps.activeItems[ITEM_WEAPON] = m_pTurret->getIndex();
        if (getMoveType() == MOVETYPE_PORTABLE_TURRET || getMoveType() == MOVETYPE_TURRET) {
            // Use the turret's ammo
            client->ps.stats[STAT_CLIPAMMO]    = m_pTurret->ammo_in_clip[FIRE_PRIMARY];
            client->ps.stats[STAT_MAXCLIPAMMO] = m_pTurret->ammo_clip_size[FIRE_PRIMARY];
        }
    } else if (activeweap) {
        if (activeweap->m_bSecondaryAmmoInHud) {
            client->ps.stats[STAT_AMMO]           = AmmoCount(activeweap->GetAmmoType(FIRE_SECONDARY));
            client->ps.stats[STAT_MAXAMMO]        = MaxAmmoCount(activeweap->GetAmmoType(FIRE_SECONDARY));
            client->ps.stats[STAT_SECONDARY_AMMO] = AmmoCount(activeweap->GetAmmoType(FIRE_PRIMARY));
        } else {
            client->ps.stats[STAT_AMMO]    = AmmoCount(activeweap->GetAmmoType(FIRE_PRIMARY));
            client->ps.stats[STAT_MAXAMMO] = MaxAmmoCount(activeweap->GetAmmoType(FIRE_PRIMARY));
        }

        client->ps.stats[STAT_CLIPAMMO]    = activeweap->ClipAmmo(FIRE_PRIMARY);
        client->ps.stats[STAT_MAXCLIPAMMO] = activeweap->GetClipSize(FIRE_PRIMARY);

        client->ps.activeItems[ITEM_AMMO] = AmmoIndex(activeweap->GetAmmoType(FIRE_PRIMARY));

        // grenade and rockets must match the number of ammo
        if (client->ps.stats[STAT_MAXCLIPAMMO] == 1) {
            client->ps.stats[STAT_MAXAMMO]++;
            client->ps.stats[STAT_AMMO] += client->ps.stats[STAT_CLIPAMMO];
        }

        if (!activeweap->IsSubclassOfInventoryItem()) {
            client->ps.stats[STAT_EQUIPPED_WEAPON] = activeweap->GetWeaponClass();
        }

        client->ps.activeItems[ITEM_WEAPON] = activeweap->getIndex();
    } else if (m_pVehicle) {
        Entity *pEnt = m_pVehicle->QueryTurretSlotEntity(0);
        if (pEnt && pEnt->IsSubclassOfVehicleTurretGun()) {
            VehicleTurretGun *vt = static_cast<VehicleTurretGun *>(pEnt);

            client->ps.activeItems[ITEM_WEAPON]   = vt->getIndex();
            client->ps.stats[STAT_CLIPAMMO]       = vt->ammo_in_clip[FIRE_PRIMARY];
            client->ps.stats[STAT_MAXCLIPAMMO]    = vt->ammo_clip_size[FIRE_PRIMARY];
            client->ps.stats[STAT_SECONDARY_AMMO] = vt->GetWarmupFraction() * 100.f;
        }
    }

    //
    // set boss health
    //
    client->ps.stats[STAT_BOSSHEALTH] = bosshealth->value * 100.0f;

    if (bosshealth->value * 100.0f > 0 && client->ps.stats[STAT_BOSSHEALTH] == 0) {
        client->ps.stats[STAT_BOSSHEALTH] = 1;
    }

    // Set cinematic stuff

    client->ps.stats[STAT_CINEMATIC] = 0;

    if (level.cinematic) {
        client->ps.stats[STAT_CINEMATIC] = (1 << 0);
    }

    if (actor_camera) {
        client->ps.stats[STAT_CINEMATIC] += (1 << 1);
    }

    count = inventory.NumObjects();

    int iItem = 0;

    for (i = 1; i <= count; i++) {
        int     entnum = inventory.ObjectAt(i);
        Weapon *weapon = (Weapon *)G_GetEntity(entnum);
        int     weapon_class;

        if (weapon->IsSubclassOfWeapon()) {
            if (weapon->IsSubclassOfInventoryItem()) {
                if (iItem > 3) {
                    weapon->SetItemSlot(0);
                } else {
                    client->ps.activeItems[iItem + 2] = weapon->getIndex();
                    weapon->SetItemSlot(WEAPON_CLASS_ITEM1 << iItem);

                    if (activeweap && weapon == activeweap) {
                        client->ps.stats[STAT_EQUIPPED_WEAPON] = WEAPON_CLASS_ITEM1 << iItem;
                    }

                    iItem++;
                }
            } else {
                weapon_class = weapon->GetWeaponClass();

                if (weapon_class & WEAPON_CLASS_GRENADE) {
                    if (weapon->HasAmmo(FIRE_PRIMARY)) {
                        client->ps.stats[STAT_WEAPONS] |= weapon_class;
                    }
                } else {
                    client->ps.stats[STAT_WEAPONS] |= weapon_class;
                }
            }
        }
    }

    // Go through all the player's ammo and send over the names/amounts
    memset(client->ps.ammo_amount, 0, sizeof(client->ps.ammo_amount));
    memset(client->ps.ammo_name_index, 0, sizeof(client->ps.ammo_name_index));
    memset(client->ps.max_ammo_amount, 0, sizeof(client->ps.max_ammo_amount));

    count = ammo_inventory.NumObjects();

    for (i = 1; i <= count; i++) {
        Ammo *ammo = ammo_inventory.ObjectAt(i);

        if (ammo) {
            client->ps.ammo_amount[i - 1]     = ammo->getAmount();
            client->ps.max_ammo_amount[i - 1] = ammo->getMaxAmount();
            client->ps.ammo_name_index[i - 1] = ammo->getIndex();
        }
    }

    if (m_iInZoomMode == -1) {
        client->ps.stats[STAT_INZOOM] = fov;
    } else {
        client->ps.stats[STAT_INZOOM] = 0;
    }

    client->ps.stats[STAT_CROSSHAIR] =
        ((!client->ps.stats[STAT_INZOOM] || client->ps.stats[STAT_INZOOM] > 30)
         && (activeweap && !activeweap->IsSubclassOfInventoryItem() && activeweap->GetUseCrosshair()))
        || m_pTurret || (m_pVehicle && m_pVehicle->IsSubclassOfVehicleTank());

    client->ps.stats[STAT_COMPASSNORTH] = ANGLE2SHORT(world->m_fNorth);

    if (VectorCompare(vObjectiveLocation, vec_zero)) {
        client->ps.stats[STAT_OBJECTIVELEFT]   = 1730;
        client->ps.stats[STAT_OBJECTIVERIGHT]  = 1870;
        client->ps.stats[STAT_OBJECTIVECENTER] = 1800;
    } else {
        Vector vDelta;
        float  yaw;
        float  fOffset;

        vDelta = vObjectiveLocation - centroid;
        yaw    = AngleSubtract(v_angle[1], vDelta.toYaw()) + 180.0f;

        vDelta  = yaw_left * 300.0f + yaw_forward * vDelta.length() + centroid - centroid;
        fOffset = AngleSubtract(vDelta.toYaw(), v_angle[1]);
        if (fOffset < 0.0f) {
            fOffset = -fOffset;
        }

        fOffset = 53.0f - fOffset + 7.0f;
        if (fOffset < 7.0f) {
            fOffset = 7.0f;
        }

        client->ps.stats[STAT_OBJECTIVELEFT] = anglemod(yaw - fOffset) * 10.0f;
        if (client->ps.stats[STAT_OBJECTIVELEFT] <= 0) {
            client->ps.stats[STAT_OBJECTIVELEFT] = 1;
        } else if (client->ps.stats[STAT_OBJECTIVELEFT] > 3599) {
            client->ps.stats[STAT_OBJECTIVELEFT] = 3599;
        }

        client->ps.stats[STAT_OBJECTIVERIGHT] = anglemod(yaw + fOffset) * 10.0f;
        if (client->ps.stats[STAT_OBJECTIVERIGHT] <= 0) {
            client->ps.stats[STAT_OBJECTIVERIGHT] = 1;
        } else if (client->ps.stats[STAT_OBJECTIVERIGHT] > 3599) {
            client->ps.stats[STAT_OBJECTIVERIGHT] = 3599;
        }

        client->ps.stats[STAT_OBJECTIVECENTER] = anglemod(yaw) * 10.0f;
        if (client->ps.stats[STAT_OBJECTIVECENTER] <= 0) {
            client->ps.stats[STAT_OBJECTIVECENTER] = 1;
        } else if (client->ps.stats[STAT_OBJECTIVECENTER] > 3599) {
            client->ps.stats[STAT_OBJECTIVECENTER] = 3599;
        }
    }

    client->ps.stats[STAT_DAMAGEDIR] = damage_yaw;
    if (client->ps.stats[STAT_DAMAGEDIR] < 0) {
        client->ps.stats[STAT_DAMAGEDIR] = 0;
    } else if (client->ps.stats[STAT_DAMAGEDIR] > 3600) {
        client->ps.stats[STAT_DAMAGEDIR] = 3600;
    }

    // Do letterbox

    // Check for letterbox fully out
    if ((level.m_letterbox_time <= 0) && (level.m_letterbox_dir == letterbox_in)) {
        client->ps.stats[STAT_LETTERBOX] = level.m_letterbox_fraction * MAX_LETTERBOX_SIZE;
        return;
    } else if ((level.m_letterbox_time <= 0) && (level.m_letterbox_dir == letterbox_out)) {
        client->ps.stats[STAT_LETTERBOX] = 0;
        return;
    }

    float frac;

    level.m_letterbox_time -= level.frametime;

    frac = level.m_letterbox_time / level.m_letterbox_time_start;

    if (frac > 1) {
        frac = 1;
    }
    if (frac < 0) {
        frac = 0;
    }

    if (level.m_letterbox_dir == letterbox_in) {
        frac = 1.0f - frac;
    }

    client->ps.stats[STAT_LETTERBOX] = (frac * level.m_letterbox_fraction) * MAX_LETTERBOX_SIZE;
}

void Player::UpdateMusic(void)
{
    if (music_forced) {
        client->ps.current_music_mood  = music_current_mood;
        client->ps.fallback_music_mood = music_fallback_mood;
    }

    // Copy music volume and fade time to player state
    client->ps.music_volume           = music_current_volume;
    client->ps.music_volume_fade_time = music_volume_fade_time;
}

void Player::SetReverb(int type, float level)
{
    reverb_type  = type;
    reverb_level = level;
}

void Player::SetReverb(str type, float level)
{
    reverb_type  = EAXMode_NameToNum(type);
    reverb_level = level;
}

void Player::SetReverb(Event *ev)
{
    if (ev->NumArgs() < 2) {
        return;
    }

    SetReverb(ev->GetInteger(1), ev->GetFloat(2));
}

void Player::UpdateReverb(void)
{
    client->ps.reverb_type  = reverb_type;
    client->ps.reverb_level = reverb_level;
}

void Player::UpdateMisc(void)
{
    //
    // clear out the level exit flag
    //
    client->ps.pm_flags &= ~PMF_LEVELEXIT;

    //
    // see if our camera is the level exit camera
    //
    if (camera && camera->IsLevelExit()) {
        client->ps.pm_flags |= PMF_LEVELEXIT;
    } else if (level.near_exit) {
        client->ps.pm_flags |= PMF_LEVELEXIT;
    }

    //
    // do anything special for respawns
    //
    if (client->ps.pm_flags & PMF_RESPAWNED) {
        //
        // change music
        //
        if (music_current_mood != mood_success) {
            ChangeMusic("success", "normal", false);
        }
    }
}

/*
=================
EndFrame

Called for each player at the end of the server frame
and right after spawning
=================
*/
void Player::EndFrame(void)
{
    FinishMove();
    UpdateStats();
    UpdateMusic();
    UpdateReverb();
    UpdateMisc();

    if (!g_spectatefollow_firstperson->integer || !IsSpectator() || !m_iPlayerSpectating) {
        SetupView();
    } else {
        gentity_t *ent = g_entities + m_iPlayerSpectating - 1;

        if (!ent->inuse || !ent->entity || ent->entity->deadflag >= DEAD_DEAD) {
            SetupView();
        }
    }
}

void Player::GotKill(Event *ev)
{
    /*
    Entity *victim;
   Entity *inflictor;
   float   damage;
   int     meansofdeath;
   qboolean gibbed;

   if ( deathmatch->integer )
        {
      return;
        }

    victim = ev->GetEntity( 1 );
    damage = ev->GetInteger( 2 );
    inflictor = ev->GetEntity( 3 );
    meansofdeath = ev->GetInteger( 4 );
    gibbed = ev->GetInteger( 5 );
*/
}

void Player::SetPowerupTimer(Event *ev)
{
    Event *event;

    poweruptimer = ev->GetInteger(1);
    poweruptype  = ev->GetInteger(2);
    event        = new Event(EV_Player_UpdatePowerupTimer);
    PostEvent(event, 1);
}

void Player::UpdatePowerupTimer(Event *ev)
{
    poweruptimer -= 1;
    if (poweruptimer > 0) {
        PostEvent(ev, 1);
    } else {
        poweruptype = 0;
    }
}

void Player::ChangeMusic(const char *current, const char *fallback, qboolean force)
{
    int current_mood_num;
    int fallback_mood_num;

    music_forced = force;

    if (current) {
        current_mood_num = MusicMood_NameToNum(current);
        if (current_mood_num < 0) {
            gi.DPrintf("current music mood %s not found", current);
        } else {
            music_current_mood = current_mood_num;
        }
    }

    if (fallback) {
        fallback_mood_num = MusicMood_NameToNum(fallback);
        if (fallback_mood_num < 0) {
            gi.DPrintf("fallback music mood %s not found", fallback);
            fallback = NULL;
        } else {
            music_fallback_mood = fallback_mood_num;
        }
    }
}

void Player::ChangeMusicVolume(float volume, float fade_time)
{
    music_volume_fade_time = fade_time;
    music_saved_volume     = music_current_volume;
    music_current_volume   = volume;
}

void Player::RestoreMusicVolume(float fade_time)
{
    music_volume_fade_time = fade_time;
    music_current_volume   = music_saved_volume;
    music_saved_volume     = -1.0;
}

void Player::addOrigin(Vector org)
{
    setLocalOrigin(localorigin + org);

    animspeed         = org.x * (1.f / level.frametime);
    airspeed          = org.y * (1.f / level.frametime);
    m_vPushVelocity.x = org.z * (1.f / level.frametime);
}

void Player::Jump(Event *ev)
{
    float maxheight;

    if (m_pTurret || m_pVehicle) {
        // Don't jump when inside a vehicle or turret
        return;
    }

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        // Added in 2.0
        //  Don't jump when on top of another sentient
        if (groundentity && groundentity->entity && groundentity->entity->IsSubclassOfSentient()) {
            return;
        }
    }

    maxheight = ev->GetFloat(1);

    if (maxheight > 16) {
        // v^2 = 2ad
        velocity[2] += sqrt(2 * sv_gravity->integer * maxheight);

        if (client->ps.groundEntityNum != ENTITYNUM_NONE) {
            velocity += m_vPushVelocity;
        }

        // make sure the player leaves the ground
        client->ps.walking = qfalse;

        // Added in 2.0
        m_bHasJumped = true;
    }
}

void Player::JumpXY(Event *ev)
{
    float forwardmove;
    float sidemove;
    float distance;
    float time;
    float speed;

    if (m_pTurret || m_pVehicle) {
        // Don't jump when inside a vehicle or turret
        return;
    }

    forwardmove = ev->GetFloat(1);
    sidemove    = ev->GetFloat(2);
    speed       = ev->GetFloat(3);

    velocity = yaw_forward * forwardmove - yaw_left * sidemove;
    distance = velocity.length();
    velocity *= speed / distance;
    time        = distance / speed;
    velocity[2] = sv_gravity->integer * time * 0.5f;

    if (client->ps.groundEntityNum != ENTITYNUM_NONE) {
        velocity += G_GetEntity(client->ps.groundEntityNum)->velocity;
    }

    airspeed = distance;

    // make sure the player leaves the ground
    client->ps.walking = qfalse;
}

void Player::SetViewAngles(Vector newViewangles)
{
    // set the delta angle
    client->ps.delta_angles[0] = ANGLE2SHORT(newViewangles.x - client->cmd_angles[0]);
    client->ps.delta_angles[1] = ANGLE2SHORT(newViewangles.y - client->cmd_angles[1]);
    client->ps.delta_angles[2] = ANGLE2SHORT(newViewangles.z - client->cmd_angles[2]);

    v_angle = newViewangles;
    // Fixed in OPM
    //  Normalize angles to the range (-180, +180)
    //  so interpolation is done properly client-side
    v_angle.EulerNormalize();

    // get the pitch and roll from our leg angles
    newViewangles.x = angles.x;
    newViewangles.z = angles.z;
    AnglesToMat(newViewangles, orientation);
    yaw_forward = orientation[0];
    yaw_left    = orientation[1];
}

void Player::SetTargetViewAngles(Vector angles)
{
    v_angle = angles;
}

void Player::DumpState(Event *ev)
{
    gi.DPrintf(
        "Legs: %s Torso: %s\n", currentState_Legs ? currentState_Legs->getName() : "NULL", currentState_Torso->getName()
    );
}

void Player::ForceTorsoState(Event *ev)
{
    State *ts = statemap_Torso->FindState(ev->GetString(1));
    EvaluateState(ts);
}

void Player::ForceLegsState(Event *ev)
{
    State *ls = statemap_Legs->FindState(ev->GetString(1));
    EvaluateState(NULL, ls);
}

void Player::TouchedUseAnim(Entity *ent)
{
    toucheduseanim = ent;
}

void Player::NextPainTime(Event *ev)
{
    float time = ev->GetFloat(1);

    nextpaintime = level.time + time;

    if (time >= 0.0f) {
        pain          = 0.0f;
        pain_type     = MOD_NONE;
        pain_location = HITLOC_MISS;

        m_pLegsPainCond->clearCheck();
        m_pTorsoPainCond->clearCheck();
    }
}

void Player::EnterVehicle(Event *ev)
{
    Entity *ent;

    ent = ev->GetEntity(1);
    if (ent && ent->IsSubclassOfVehicle()) {
        flags |= FL_PARTIAL_IMMOBILE;
        viewheight = STAND_EYE_HEIGHT;
        velocity   = vec_zero;
        m_pVehicle = (Vehicle *)ent;
        if (m_pVehicle->IsDrivable()) {
            setMoveType(MOVETYPE_VEHICLE);
        } else {
            setMoveType(MOVETYPE_NOCLIP);
        }

        SafeHolster(true);
    }
}

void Player::ExitVehicle(Event *ev)
{
    flags &= ~FL_PARTIAL_IMMOBILE;
    setMoveType(MOVETYPE_WALK);
    m_pVehicle = NULL;

    if (camera) {
        SetCamera(NULL, 0.5f);
        ZoomOff();
    }

    SafeHolster(false);
    takedamage = DAMAGE_YES;
    setSolidType(SOLID_BBOX);
}

void Player::EnterTurret(TurretGun *ent)
{
    flags |= FL_PARTIAL_IMMOBILE;
    viewheight = DEFAULT_VIEWHEIGHT;
    velocity   = vec_zero;
    m_pTurret  = ent;

    if (ent->inheritsFrom(PortableTurret::classinfostatic())) {
        // carryable turret
        setMoveType(MOVETYPE_PORTABLE_TURRET);
        StopPartAnimating(torso);
        SetPartAnim("mg42tripod_aim_straight_straight");
    } else {
        // standard turret
        setMoveType(MOVETYPE_TURRET);
    }

    SafeHolster(true);
}

void Player::EnterTurret(Event *ev)
{
    TurretGun *ent = (TurretGun *)ev->GetEntity(1);

    if (!ent) {
        return;
    }

    if (!ent->inheritsFrom(TurretGun::classinfostatic())) {
        return;
    }

    EnterTurret(ent);
}

void Player::ExitTurret(void)
{
    if (m_pTurret->inheritsFrom(PortableTurret::classinfostatic())) {
        StopPartAnimating(torso);
        SetPartAnim("mg42tripod_aim_straight_straight");
    }

    flags &= ~FL_PARTIAL_IMMOBILE;
    setMoveType(MOVETYPE_WALK);
    m_pTurret = NULL;

    SafeHolster(qfalse);

    new_buttons        = 0;
    server_new_buttons = 0;
}

void Player::ExitTurret(Event *ev)
{
    ExitTurret();
}

void Player::HolsterToggle(Event *ev)
{
    if (deadflag) {
        return;
    }

    if (WeaponsOut()) {
        // fucking compiler bug
        // it won't call the parent's override function
        ((Sentient *)this)->Holster(qtrue);
    } else {
        ((Sentient *)this)->Holster(qfalse);
    }
}

void Player::Holster(Event *ev)
{
    SafeHolster(ev->GetBoolean(1));
}

void Player::WatchActor(Event *ev)
{
    if (camera || currentState_Torso->getCameraType() != CAMERA_BEHIND) {
        return;
    }

    actor_to_watch = (Actor *)ev->GetEntity(1);
}

void Player::StopWatchingActor(Event *ev)
{
    Actor *old_actor;

    old_actor = (Actor *)ev->GetEntity(1);

    if (old_actor == actor_to_watch) {
        actor_to_watch = NULL;
    }
}

void Player::setAngles(Vector ang)
{
    // set the angles normally

    if (bindmaster) {
        ang -= bindmaster->angles;
    }

    Entity::setAngles(ang);
}

painDirection_t Player::Pain_string_to_int(str pain)
{
    if (!pain.icmp(pain, "Front")) {
        return PAIN_FRONT;
    } else if (!pain.icmp(pain, "Left")) {
        return PAIN_LEFT;
    } else if (!pain.icmp(pain, "Right")) {
        return PAIN_RIGHT;
    } else if (!pain.icmp(pain, "Rear")) {
        return PAIN_REAR;
    } else {
        return PAIN_NONE;
    }
}

void Player::ArchivePersistantData(Archiver& arc)
{
    str model_name;
    str name;

    Sentient::ArchivePersistantData(arc);

    model_name = g_playermodel->string;

    arc.ArchiveString(&model_name);

    if (arc.Loading()) {
        // set the cvar
        gi.cvar_set("g_playermodel", model_name.c_str());

        setModel("models/player/" + model_name + ".tik");
    }

    if (arc.Saving()) {
        if (holsteredWeapon) {
            name = holsteredWeapon->getName();
        } else {
            name = "none";
        }
    }

    arc.ArchiveString(&name);
    if (arc.Loading() && name != "none") {
        holsteredWeapon = (Weapon *)FindItem(name);
    }

    UpdateWeapons();

    // Force a re-evaluation of the player's state
    LoadStateTable();
}

void Player::VelocityModified(void) {}

int Player::GetKnockback(int original_knockback, qboolean blocked)
{
    int new_knockback;

    new_knockback = original_knockback - 50;

    // See if we still have enough knockback to knock the player down
    if (new_knockback >= 200 && take_pain) {
        knockdown = true;

        if (blocked) {
            float damage;

            damage = new_knockback / 50;

            if (damage > 10) {
                damage = 10;
            }

            Damage(world, world, damage, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_CRUSH);
        }
    }

    // Make sure knockback is still at least 0

    if (new_knockback < 0) {
        new_knockback = 0;
    }

    return new_knockback;
}

void Player::ResetHaveItem(Event *ev)
{
    str             fullname;
    ScriptVariable *var;

    fullname = str("playeritem_") + ev->GetString(1);

    var = game.vars->GetVariable(fullname.c_str());

    if (var) {
        var->setIntValue(0);
    }
}

void Player::ReceivedItem(Item *item) {}

void Player::RemovedItem(Item *item) {}

void Player::AmmoAmountChanged(Ammo *ammo, int ammo_in_clip)
{
    str             fullname;
    ScriptVariable *var;

    //
    // set our level variables
    //
    fullname = str("playerammo_") + ammo->getName();

    var = level.vars->GetVariable(fullname.c_str());
    if (!var) {
        level.vars->SetVariable(fullname.c_str(), ammo->getAmount() + ammo_in_clip);
    } else {
        var->setIntValue(ammo->getAmount() + ammo_in_clip);
    }
}

void Player::WaitForState(Event *ev)
{
    waitForState = ev->GetString(1);
}

void Player::SetDamageMultiplier(Event *ev)
{
    damage_multiplier = ev->GetFloat(1);
}

void Player::SetTakePain(Event *ev)
{
    take_pain = ev->GetBoolean(1);
}

void Player::Loaded(void)
{
    UpdateWeapons();
}

void Player::PlayerShowModel(Event *ev)
{
    Entity::showModel();
    UpdateWeapons();
}

void Player::showModel(void)
{
    Entity::showModel();
    UpdateWeapons();
}

Vector Player::EyePosition(void)
{
    return m_vViewPos;
}

void Player::ModifyHeight(Event *ev)
{
    str height = ev->GetString(1);

    if (!height.icmp("stand")) {
        viewheight   = DEFAULT_VIEWHEIGHT;
        maxs.z       = 94.0f;
        m_bHasJumped = false;
    } else if (!height.icmp("jumpstart")) {
        if (g_protocol < protocol_e::PROTOCOL_MOHTA_MIN) {
            viewheight = JUMP_START_VIEWHEIGHT;
        }
        maxs.z = 94.0f;
    } else if (!height.icmp("duck")) {
        viewheight = CROUCH_VIEWHEIGHT;
        maxs.z     = 54.0f;
    } else if (!height.icmp("duckrun")) {
        viewheight = CROUCH_RUN_VIEWHEIGHT;
        maxs.z     = 60.0f;
    } else if (!height.icmp("prone")) {
        //
        // Added in OPM
        //  (prone)
        viewheight = PRONE_VIEWHEIGHT;
        maxs.z     = 20.0f;
    } else {
        gi.Printf("Unknown modheight '%s' defaulting to stand\n", height.c_str());
        viewheight = DEFAULT_VIEWHEIGHT;
        maxs.z     = 94.0f;
    }
}

// Specify the view height of the player and the height of his bounding box
void Player::ModifyHeightFloat(Event *ev)
{
    // params
    int   height;
    float max_z;

    height = ev->GetInteger(1);
    max_z  = ev->GetFloat(2);

    viewheight = height;

    if (max_z >= 94.0) {
        max_z = 94.0;
    } else if (max_z >= 74.0 && max_z < 94.0) {
        max_z = 54.0;
    } else if (max_z >= 30.0 && max_z < 54.0) {
        max_z = 20.0;
    } else if (max_z <= 20.0) {
        max_z = 20.0;
    }

    maxs.z = max_z;

    client->ps.pm_flags &= ~(PMF_DUCKED | PMF_VIEW_PRONE | PMF_VIEW_DUCK_RUN | PMF_VIEW_JUMP_START);

    // FIXME...
    /*
    gi.MSG_SetClient(edict - g_entities);

    gi.MSG_StartCGM(CGM_MODHEIGHTFLOAT);
    gi.MSG_WriteLong(height);
    gi.MSG_WriteFloat(max_z);
    gi.MSG_EndCGM();
    */
}

void Player::SetMovePosFlags(Event *ev)
{
    str sParm;

    if (ev->NumArgs() <= 0) {
        Com_Printf("moveposflags command without any parameters\n");
        return;
    }

    sParm = ev->GetString(1);

    if (!sParm.icmp("crouching")) {
        m_iMovePosFlags = MPF_POSITION_CROUCHING;
    } else if (!sParm.icmp("prone")) {
        m_iMovePosFlags = MPF_POSITION_PRONE;
    } else if (!sParm.icmp("offground")) {
        m_iMovePosFlags = MPF_POSITION_OFFGROUND;
    } else {
        m_iMovePosFlags = MPF_POSITION_STANDING;
    }

    if (ev->NumArgs() > 1) {
        sParm = ev->GetString(2);

        if (!sParm.icmp("walking") || !sParm.icmp("walking\"") // there is a mistake in WALK_FORWARD
        ) {
            m_iMovePosFlags |= MPF_MOVEMENT_WALKING;
        } else if (!sParm.icmp("running")) {
            m_iMovePosFlags |= MPF_MOVEMENT_RUNNING;
        } else if (!sParm.icmp("falling")) {
            m_iMovePosFlags |= MPF_MOVEMENT_FALLING;
        }
    }
}

void Player::GetPositionForScript(Event *ev)
{
    if (m_iMovePosFlags & MPF_POSITION_CROUCHING) {
        ev->AddConstString(STRING_CROUCHING);
    } else if (m_iMovePosFlags & MPF_POSITION_PRONE) {
        ev->AddConstString(STRING_PRONE);
    } else if (m_iMovePosFlags & MPF_POSITION_OFFGROUND) {
        ev->AddConstString(STRING_OFFGROUND);
    } else {
        ev->AddConstString(STRING_STANDING);
    }
}

void Player::GetMovementForScript(Event *ev)
{
    if (m_iMovePosFlags & MPF_MOVEMENT_WALKING) {
        ev->AddConstString(STRING_WALKING);
    } else if (m_iMovePosFlags & MPF_MOVEMENT_RUNNING) {
        ev->AddConstString(STRING_RUNNING);
    } else if (m_iMovePosFlags & MPF_MOVEMENT_FALLING) {
        ev->AddConstString(STRING_FALLING);
    } else {
        ev->AddConstString(STRING_STANDING);
    }
}

void Player::ToggleZoom(int iZoom)
{
    if (iZoom && m_iInZoomMode == -1) {
        SetFov(selectedfov);
        m_iInZoomMode = 0;
    } else {
        SetFov(iZoom);
        m_iInZoomMode = -1;
    }
}

void Player::ZoomOff(void)
{
    SetFov(selectedfov);
    m_iInZoomMode = 0;
}

void Player::ZoomOffEvent(Event *ev)
{
    ZoomOff();
}

qboolean Player::IsZoomed(void)
{
    return m_iInZoomMode == -1;
}

void Player::SafeZoomed(Event *ev)
{
    if (ev->GetInteger(1)) {
        if (m_iInZoomMode > 0) {
            SetFov(m_iInZoomMode);
            m_iInZoomMode = -1;
        }
    } else {
        if (m_iInZoomMode == -1) {
            m_iInZoomMode = fov;
            SetFov(selectedfov);
        }
    }
}

void Player::AttachToLadder(Event *ev)
{
    Vector      vStart, vEnd, vOffset;
    trace_t     trace;
    FuncLadder *pLadder;

    if (deadflag) {
        return;
    }

    AngleVectors(m_vViewAng, vOffset, NULL, NULL);

    vStart = m_vViewPos - vOffset * 12.0f;
    vEnd   = m_vViewPos + vOffset * 128.0f;

    trace = G_Trace(vStart, vec_zero, vec_zero, vEnd, this, MASK_LADDER, qfalse, "Player::AttachToLadder");

    if (trace.fraction == 1.0f || !trace.ent || !trace.ent->entity || !trace.ent->entity->isSubclassOf(FuncLadder)) {
        return;
    }

    pLadder   = (FuncLadder *)trace.ent->entity;
    m_pLadder = pLadder;

    pLadder->PositionOnLadder(this);

    SetViewAngles(Vector(v_angle[0], angles[1], v_angle[2]));
}

void Player::UnattachFromLadder(Event *ev)
{
    m_pLadder = NULL;
}

void Player::TweakLadderPos(Event *ev)
{
    FuncLadder *pLadder = (FuncLadder *)m_pLadder.Pointer();

    if (pLadder) {
        pLadder->AdjustPositionOnLadder(this);
    }
}

void Player::EnsureOverLadder(Event *ev)
{
    FuncLadder *pLadder = (FuncLadder *)m_pLadder.Pointer();

    if (pLadder) {
        pLadder->EnsureOverLadder(this);
    }
}

void Player::EnsureForwardOffLadder(Event *ev)
{
    FuncLadder *pLadder = (FuncLadder *)m_pLadder.Pointer();

    if (pLadder) {
        pLadder->EnsureForwardOffLadder(this);
    }
}

void Player::EventForceLandmineMeasure(Event *ev)
{
    MeasureLandmineDistances();
}

str Player::GetCurrentDMWeaponType() const
{
    return m_sDmPrimary;
}

void Player::Score(Event *ev)
{
    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        // Of course useless in single-player mode
        return;
    }

    dmManager.Score(this);
}

// Was between 2.0 and 2.15
/*
nationality_t GetAlliedType(const char* name)
{
    if (!Q_stricmpn(name, "american", 8)) {
        return NA_AMERICAN;
    } else if (!Q_stricmpn(name, "allied_russian", 14)) {
        return NA_RUSSIAN;
    } else if (!Q_stricmpn(name, "allied_british", 14)) {
        return NA_BRITISH;
    } else if (!Q_stricmpn(name, "allied", 6)) {
        return NA_AMERICAN;
    } else {
        return NA_NONE;
    }
}
*/

// Commented out in OPM. See the other comment below.
/*
nationality_t GetPlayerTeamType(const char *name)
{
    if (!Q_stricmpn(name, "american", 8)) {
        return NA_AMERICAN;
    } else if (!Q_stricmpn(name, "allied_russian", 14)) {
        return NA_RUSSIAN;
    } else if (!Q_stricmpn(name, "allied_british", 14)) {
        return NA_BRITISH;
    } else if (!Q_stricmpn(name, "allied_sas", 10)) {
        return NA_BRITISH;
    } else if (!Q_stricmpn(name, "allied", 6)) {
        return NA_AMERICAN;
    } else if (!Q_stricmpn(name, "german", 6)) {
        return NA_GERMAN;
    } else if (!Q_stricmpn(name, "it", 2)) {
        return NA_ITALIAN;
    } else if (!Q_stricmpn(name, "sc", 2)) {
        return NA_ITALIAN;
    } else {
        return NA_NONE;
    }
}
*/

// Fixed in OPM.
//  This fixes the issue where the player can equip weapons
//  from the other team
nationality_t GetPlayerAxisTeamType(const char *name)
{
    if (g_target_game < target_game_e::TG_MOHTA) {
        // Only american and german are supported on older versions of the game
        return NA_GERMAN;
    }

    if (!Q_stricmpn(name, "german", 6)) {
        return NA_GERMAN;
    }

    if (g_target_game < target_game_e::TG_MOHTT) {
        // Italian skins are supported only in mohaab
        return NA_GERMAN;
    }

    if (!Q_stricmpn(name, "it", 2)) {
        return NA_ITALIAN;
    } else if (!Q_stricmpn(name, "sc", 2)) {
        return NA_ITALIAN;
    }

    // fallback to german
    return NA_GERMAN;
}

nationality_t GetPlayerAlliedTeamType(const char *name)
{
    if (g_target_game < target_game_e::TG_MOHTA) {
        // Only american and german are supported on older versions of the game
        return NA_AMERICAN;
    }

    if (!Q_stricmpn(name, "american", 8)) {
        return NA_AMERICAN;
    } else if (!Q_stricmpn(name, "allied_russian", 14)) {
        return NA_RUSSIAN;
    } else if (!Q_stricmpn(name, "allied_british", 14)) {
        return NA_BRITISH;
    } else if (!Q_stricmpn(name, "allied_sas", 10)) {
        return NA_BRITISH;
    } else if (!Q_stricmpn(name, "allied", 6)) {
        return NA_AMERICAN;
    }

    // fallback to american
    return NA_AMERICAN;
}

void Player::InitDeathmatch(void)
{
    fAttackerDispTime    = 0.0f;
    pAttackerDistPointer = nullptr;
    m_iInfoClient        = -1;
    m_fWeapSelectTime    = level.time - 9.0f;

    if (!g_realismmode->integer) {
        m_fDamageMultipliers[HITLOC_HEAD]        = 2.0f;
        m_fDamageMultipliers[HITLOC_HELMET]      = 2.0f;
        m_fDamageMultipliers[HITLOC_NECK]        = 2.0f;
        m_fDamageMultipliers[HITLOC_TORSO_UPPER] = 1.0f;
        m_fDamageMultipliers[HITLOC_TORSO_MID]   = 0.95f;
        m_fDamageMultipliers[HITLOC_TORSO_LOWER] = 0.90f;
        m_fDamageMultipliers[HITLOC_PELVIS]      = 0.85f;
        m_fDamageMultipliers[HITLOC_R_ARM_UPPER] = 0.80f;
        m_fDamageMultipliers[HITLOC_L_ARM_UPPER] = 0.80f;
        m_fDamageMultipliers[HITLOC_R_LEG_UPPER] = 0.80f;
        m_fDamageMultipliers[HITLOC_L_LEG_UPPER] = 0.80f;
        m_fDamageMultipliers[HITLOC_R_ARM_LOWER] = 0.60f;
        m_fDamageMultipliers[HITLOC_L_ARM_LOWER] = 0.60f;
        m_fDamageMultipliers[HITLOC_R_LEG_LOWER] = 0.60f;
        m_fDamageMultipliers[HITLOC_L_LEG_LOWER] = 0.60f;
        m_fDamageMultipliers[HITLOC_R_HAND]      = 0.50f;
        m_fDamageMultipliers[HITLOC_L_HAND]      = 0.50f;
        m_fDamageMultipliers[HITLOC_R_FOOT]      = 0.50f;
        m_fDamageMultipliers[HITLOC_L_FOOT]      = 0.50f;
    }

    if (current_team) {
        if (AllowTeamRespawn()) {
            EndSpectator();

            if (dmManager.GetMatchStartTime() > 0.0f && !dmManager.AllowRespawn() && g_allowjointime->value > 0.0f
                && (level.time - dmManager.GetMatchStartTime()) > g_allowjointime->value) {
                m_bTempSpectator = true;
            }

            switch (g_gametype->integer) {
            case GT_TEAM_ROUNDS:
            case GT_OBJECTIVE:
            case GT_TOW:
            case GT_LIBERATION:
                if (!m_bTempSpectator) {
                    BeginFight();
                } else {
                    Spectator();
                }
                break;
            default:
                BeginFight();
                break;
            }
        }
    } else {
        if (client->pers.teamnum) {
            SetTeam(client->pers.teamnum);
        } else {
            SetTeam(TEAM_SPECTATOR);
        }
    }

    edict->s.eFlags &= ~(TEAM_ALLIES | TEAM_AXIS);

    if (GetTeam() == TEAM_ALLIES) {
        edict->s.eFlags |= TEAM_ALLIES;
    } else if (GetTeam() == TEAM_AXIS) {
        edict->s.eFlags |= TEAM_AXIS;
    }

    G_SetClientConfigString(edict);

    if (g_gametype->integer >= GT_TEAM_ROUNDS) {
        if (client->pers.round_kills) {
            num_deaths               = client->pers.round_kills;
            client->pers.round_kills = 0;
        }
    }

    ChooseSpawnPoint();
    EquipWeapons();

    if (current_team) {
        current_team->m_bHasSpawnedPlayers = qtrue;
    }
}

bool Player::QueryLandminesAllowed() const
{
    const char *mapname;

    if (g_target_game < target_game_e::TG_MOHTT) {
        return false;
    }

    if (dmflags->integer & DF_WEAPON_NO_LANDMINE) {
        return false;
    }

    if (dmflags->integer & DF_WEAPON_LANDMINE_ALWAYS) {
        return true;
    }

    mapname = level.mapname.c_str();

    if (!Q_stricmpn(mapname, "obj/obj_", 8u)) {
        return false;
    }
    if (!Q_stricmpn(mapname, "dm/mohdm", 8u)) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Bahnhof_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "obj/MP_Ardennes_TOW")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Bazaar_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "obj/MP_Berlin_TOW")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Brest_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "obj/MP_Druckkammern_TOW")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Gewitter_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "obj/MP_Flughafen_TOW")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Holland_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Malta_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Stadt_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Unterseite_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "DM/MP_Verschneit_DM")) {
        return false;
    }
    if (!Q_stricmp(mapname, "lib/mp_ship_lib")) {
        return false;
    }

    return true;
}

void Player::EnsurePlayerHasAllowedWeapons()
{
    int i;

    //if (client != (gclient_t*)-2190)
    if (!client) {
        return;
    }

    if (!client->pers.dm_primary[0]) {
        return;
    }

    for (i = 0; i < 7; i++) {
        if (!Q_stricmp(client->pers.dm_primary, "sniper")) {
            if (!(dmflags->integer & DF_WEAPON_NO_SNIPER)) {
                return;
            }

            Q_strncpyz(client->pers.dm_primary, "rifle", sizeof(client->pers.dm_primary));
        } else if (!Q_stricmp(client->pers.dm_primary, "rifle")) {
            if (!(dmflags->integer & DF_WEAPON_NO_RIFLE)) {
                return;
            }

            Q_strncpyz(client->pers.dm_primary, "smg", sizeof(client->pers.dm_primary));
        } else if (!Q_stricmp(client->pers.dm_primary, "smg")) {
            if (!(dmflags->integer & DF_WEAPON_NO_RIFLE)) {
                return;
            }

            Q_strncpyz(client->pers.dm_primary, "mg", sizeof(client->pers.dm_primary));
        } else if (!Q_stricmp(client->pers.dm_primary, "mg")) {
            if (!(dmflags->integer & DF_WEAPON_NO_RIFLE)) {
                return;
            }

            Q_strncpyz(client->pers.dm_primary, "shotgun", sizeof(client->pers.dm_primary));
        } else if (!Q_stricmp(client->pers.dm_primary, "shotgun")) {
            if (!(dmflags->integer & DF_WEAPON_NO_RIFLE)) {
                return;
            }

            Q_strncpyz(client->pers.dm_primary, "heavy", sizeof(client->pers.dm_primary));
        } else if (!Q_stricmp(client->pers.dm_primary, "heavy")) {
            if (!(dmflags->integer & DF_WEAPON_NO_RIFLE)) {
                return;
            }

            Q_strncpyz(client->pers.dm_primary, "landmine", sizeof(client->pers.dm_primary));
        } else if (!Q_stricmp(client->pers.dm_primary, "landmine")) {
            if (QueryLandminesAllowed()) {
                return;
            }

            Q_strncpyz(client->pers.dm_primary, "sniper", sizeof(client->pers.dm_primary));
        }
    }

    gi.cvar_set("dmflags", va("%i", dmflags->integer & ~DF_WEAPON_NO_RIFLE));
    Com_Printf("No valid weapons -- re-allowing the rifle\n");
    Q_strncpyz(client->pers.dm_primary, "rifle", sizeof(client->pers.dm_primary));
}

void Player::EquipWeapons()
{
    Event *event;

    nationality_t nationality;

    if (IsSpectator()) {
        FreeInventory();
        return;
    }

    // Fixed in OPM
    //  Old behavior was calling GetPlayerTeamType() regardless of the team
    if (GetTeam() == TEAM_AXIS) {
        nationality = GetPlayerAxisTeamType(client->pers.dm_playergermanmodel);
    } else {
        nationality = GetPlayerAlliedTeamType(client->pers.dm_playermodel);
    }

    event = new Event(EV_Sentient_UseItem);

    if (!m_sDmPrimary.length()) {
        // Set the primary weapon
        m_sDmPrimary = client->pers.dm_primary;
    }

    EnsurePlayerHasAllowedWeapons();

    if (!Q_stricmp(client->pers.dm_primary, "sniper") && !(dmflags->integer & DF_WEAPON_NO_SNIPER)) {
        switch (nationality) {
        case NA_BRITISH:
            if (g_target_game < target_game_e::TG_MOHTT) {
                giveItem("weapons/springfield.tik");
                event->AddString("Springfield '03 Sniper");
            } else {
                giveItem("weapons/Uk_W_L42A1.tik");
                event->AddString("Enfield L42A1");
            }
            break;
        case NA_RUSSIAN:
            if (g_target_game < target_game_e::TG_MOHTA || dmflags->integer & DF_OLD_SNIPER) {
                // Old snipers are forced older versions of the game
                giveItem("weapons/springfield.tik");
                event->AddString("Springfield '03 Sniper");
            } else {
                giveItem("weapons/svt_rifle.tik");
                event->AddString("SVT 40");
            }
            break;
        case NA_GERMAN:
            if (g_target_game < target_game_e::TG_MOHTA || dmflags->integer & DF_OLD_SNIPER) {
                // Old snipers are forced older versions of the game
                giveItem("weapons/kar98sniper.tik");
                event->AddString("KAR98 - Sniper");
            } else {
                giveItem("weapons/g43.tik");
                event->AddString("G 43");
            }
            break;
        case NA_ITALIAN:
            giveItem("weapons/kar98sniper.tik");
            event->AddString("KAR98 - Sniper");
            break;
        case NA_AMERICAN:
        default:
            giveItem("weapons/springfield.tik");
            event->AddString("Springfield '03 Sniper");
            break;
        }
    } else if (!Q_stricmp(client->pers.dm_primary, "smg") && !(dmflags->integer & DF_WEAPON_NO_SMG)) {
        switch (nationality) {
        case NA_BRITISH:
            giveItem("weapons/sten.tik");
            event->AddString("Sten Mark II");
            break;
        case NA_RUSSIAN:
            giveItem("weapons/ppsh_smg.tik");
            event->AddString("PPSH SMG");
            break;
        case NA_GERMAN:
            giveItem("weapons/mp40.tik");
            event->AddString("MP40");
            break;
        case NA_ITALIAN:
            giveItem("weapons/it_w_moschetto.tik");
            event->AddString("Moschetto");
            break;
        case NA_AMERICAN:
        default:
            giveItem("weapons/thompsonsmg.tik");
            event->AddString("Thompson");
            break;
        }
    } else if (!Q_stricmp(client->pers.dm_primary, "mg") && !(dmflags->integer & DF_WEAPON_NO_MG)) {
        switch (nationality) {
        case NA_BRITISH:
            if (g_target_game < target_game_e::TG_MOHTT) {
                giveItem("weapons/bar.tik");
                event->AddString("BAR");
                break;
            } else {
                giveItem("weapons/Uk_W_Vickers.tik");
                event->AddString("Vickers-Berthier");
            }
            break;
        case NA_GERMAN:
            giveItem("weapons/mp44.tik");
            event->AddString("StG 44");
            break;
        case NA_ITALIAN:
            giveItem("weapons/It_W_Breda.tik");
            event->AddString("Breda");
            break;
        case NA_AMERICAN:
        default:
            giveItem("weapons/bar.tik");
            event->AddString("BAR");
            break;
        }
    } else if (!Q_stricmp(client->pers.dm_primary, "heavy") && !(dmflags->integer & DF_WEAPON_NO_ROCKET)) {
        switch (nationality) {
        case NA_GERMAN:
        case NA_ITALIAN:
            giveItem("weapons/panzerschreck.tik");
            event->AddString("Panzerschreck");
            break;
        case NA_BRITISH:
            if (g_target_game < target_game_e::TG_MOHTT) {
                giveItem("weapons/bazooka.tik");
                event->AddString("Bazooka");
            } else {
                giveItem("weapons/Uk_W_Piat.tik");
                event->AddString("PIAT");
            }
            break;
        case NA_AMERICAN:
        default:
            giveItem("weapons/bazooka.tik");
            event->AddString("Bazooka");
            break;
        }
    } else if (!Q_stricmp(client->pers.dm_primary, "shotgun") && !(dmflags->integer & DF_WEAPON_NO_SHOTGUN)) {
        switch (nationality) {
        case NA_BRITISH:
            if (g_target_game < target_game_e::TG_MOHTT) {
                giveItem("weapons/shotgun.tik");
                event->AddString("Shotgun");
            } else {
                giveItem("weapons/DeLisle.tik");
                event->AddString("DeLisle");
            }
            break;
        case NA_GERMAN:
            if (g_target_game < target_game_e::TG_MOHTA || dmflags->integer & DF_DISALLOW_KAR98_MORTAR) {
                // Fallback to shotgun
                // The shotgun is forced on older versions of the game
                giveItem("weapons/shotgun.tik");
                event->AddString("Shotgun");
            } else {
                giveItem("weapons/kar98_mortar.tik");
                event->AddString("Gewehrgranate");
            }
            break;
        case NA_AMERICAN:
        default:
            giveItem("weapons/shotgun.tik");
            event->AddString("Shotgun");
            break;
        }
    } else if (!Q_stricmp(client->pers.dm_primary, "landmine") && QueryLandminesAllowed()) {
        //gi.Cvar_Get("g_rifles_for_sweepers", "0", 0);

        switch (nationality) {
        case NA_BRITISH:
            giveItem("weapons/US_W_Minedetector.tik");
            event->AddString("Minedetector");

            if (g_rifles_for_sweepers->integer) {
                // Give a lite version of the rifle
                giveItem("weapons/enfield_lite.tik");
            } else {
                // Just give some ammo for the pistol
                GiveAmmo("pistol", 12);
            }
            break;
        case NA_RUSSIAN:
            giveItem("weapons/US_W_Minedetector.tik");
            event->AddString("Minedetector");

            if (g_rifles_for_sweepers->integer) {
                // Give a lite version of the rifle
                giveItem("weapons/Mosin_Nagant_Rifle_lite.tik");
            } else {
                // Just give some ammo for the pistol
                GiveAmmo("pistol", 14);
            }
            break;
        case NA_GERMAN:
            giveItem("weapons/Gr_W_Minedetector.tik");
            event->AddString("Minensuchgerat");

            if (g_rifles_for_sweepers->integer) {
                // Give a lite version of the rifle
                giveItem("weapons/kar98_lite.tik");
            } else {
                // Just give some ammo for the pistol
                GiveAmmo("pistol", 16);
            }
            break;
        case NA_AMERICAN:
        default:
            giveItem("weapons/US_W_Minedetector.tik");
            event->AddString("Minedetector");

            if (g_rifles_for_sweepers->integer) {
                // Give a lite version of the rifle
                giveItem("weapons/m1_garand_lite.tik");
            } else {
                // Just give some ammo for the pistol
                GiveAmmo("pistol", 14);
            }
            break;
        }
    } else if (!(dmflags->integer & DF_WEAPON_NO_RIFLE)) {
        switch (nationality) {
        case NA_BRITISH:
            giveItem("weapons/enfield.tik");
            event->AddString("Lee-Enfield");
            break;
        case NA_RUSSIAN:
            giveItem("weapons/Mosin_Nagant_Rifle.tik");
            event->AddString("Mosin Nagant Rifle");
            break;
        case NA_GERMAN:
            giveItem("weapons/kar98.tik");
            event->AddString("Mauser KAR 98K");
            break;
        case NA_ITALIAN:
            giveItem("weapons/it_w_carcano.tik");
            event->AddString("Carcano");
            break;
        case NA_AMERICAN:
        default:
            giveItem("weapons/m1_garand.tik");
            event->AddString("M1 Garand");
            break;
        }
    }

    // Make the player switch to the weapon some time after spawning
    PostEvent(event, 0.3f);

    //
    // Pistols and grenades
    //
    switch (nationality) {
    case NA_BRITISH:
        giveItem("weapons/mills_grenade.tik");
        if (g_target_game >= target_game_e::TG_MOHTA) {
            giveItem("weapons/M18_smoke_grenade.tik");
        }
        giveItem("weapons/Webley_Revolver.tik");
        break;
    case NA_RUSSIAN:
        giveItem("weapons/Russian_F1_grenade.tik");
        if (g_target_game >= target_game_e::TG_MOHTA) {
            giveItem("weapons/RDG-1_Smoke_grenade.tik");
        }
        giveItem("weapons/Nagant_revolver.tik");
        break;
    case NA_GERMAN:
        giveItem("weapons/steilhandgranate.tik");
        if (g_target_game >= target_game_e::TG_MOHTA) {
            giveItem("weapons/nebelhandgranate.tik");
        }
        giveItem("weapons/p38.tik");
        break;
    case NA_ITALIAN:
        giveItem("weapons/it_w_bomba.tik");
        if (g_target_game >= target_game_e::TG_MOHTA) {
            giveItem("weapons/it_w_bombabreda.tik");
        }
        giveItem("weapons/it_w_beretta.tik");
        break;
    default:
        giveItem("weapons/m2frag_grenade.tik");
        if (g_target_game >= target_game_e::TG_MOHTA) {
            giveItem("weapons/M18_smoke_grenade.tik");
        }
        giveItem("weapons/colt45.tik");
        break;
    }
}

void Player::EquipWeapons_ver8()
{
    // spectators should not have weapons
    if (IsSpectator()) {
        FreeInventory();
    } else {
        Event *ev = new Event("use");

        if (!Q_stricmp(client->pers.dm_primary, "rifle")) {
            if (dm_team == TEAM_ALLIES) {
                giveItem("models/weapons/m1_garand.tik");
                ev->AddString("models/weapons/m1_garand.tik");
            } else {
                giveItem("models/weapons/kar98.tik");
                ev->AddString("models/weapons/kar98.tik");
            }

            GiveAmmo("rifle", 100);
        } else if (!Q_stricmp(client->pers.dm_primary, "sniper")) {
            if (dm_team == TEAM_ALLIES) {
                giveItem("models/weapons/springfield.tik");
                ev->AddString("models/weapons/springfield.tik");
            } else {
                giveItem("models/weapons/kar98sniper.tik");
                ev->AddString("models/weapons/kar98sniper.tik");
            }
        } else if (!Q_stricmp(client->pers.dm_primary, "smg")) {
            if (dm_team == TEAM_ALLIES) {
                giveItem("models/weapons/thompsonsmg.tik");
                ev->AddString("models/weapons/thompsonsmg.tik");
            } else {
                giveItem("models/weapons/mp40.tik");
                ev->AddString("models/weapons/mp40.tik");
            }
        } else if (!Q_stricmp(client->pers.dm_primary, "mg")) {
            if (dm_team == TEAM_ALLIES) {
                giveItem("models/weapons/bar.tik");
                ev->AddString("models/weapons/bar.tik");
            } else {
                giveItem("models/weapons/mp44.tik");
                ev->AddString("models/weapons/mp44.tik");
            }
        } else if (!Q_stricmp(client->pers.dm_primary, "heavy")) {
            if (dm_team == TEAM_ALLIES) {
                giveItem("models/weapons/bazooka.tik");
                ev->AddString("models/weapons/bazooka.tik");
            } else {
                giveItem("models/weapons/panzerschreck.tik");
                ev->AddString("models/weapons/panzerschreck.tik");
            }
        } else if (!Q_stricmp(client->pers.dm_primary, "shotgun")) {
            giveItem("models/weapons/shotgun.tik");
            ev->AddString("models/weapons/shotgun.tik");
        }

        PostEvent(ev, 0.3f);

        if (dm_team == TEAM_ALLIES) {
            giveItem("models/weapons/colt45.tik");
            giveItem("models/weapons/m2frag_grenade.tik");
        } else {
            giveItem("models/weapons/p38.tik");
            giveItem("models/weapons/steilhandgranate.tik");
        }

        giveItem("models/items/binoculars.tik");
    }
}

void Player::Spectator(void)
{
    if (!IsSpectator()) {
        respawn_time = level.time + 1.0f;
    }

    RemoveFromVehiclesAndTurrets();

    m_bSpectator        = !m_bTempSpectator;
    m_iPlayerSpectating = 0;
    takedamage          = DAMAGE_NO;
    deadflag            = DEAD_NO;
    health              = max_health;

    client->ps.feetfalling = 0;
    movecontrol            = MOVECONTROL_USER;
    client->ps.pm_flags |= PMF_SPECTATING;

    EvaluateState(statemap_Torso->FindState("STAND"), statemap_Legs->FindState("STAND"));

    setSolidType(SOLID_NOT);
    setMoveType(MOVETYPE_NOCLIP);

    FreeInventory();

    hideModel();

    SetPlayerSpectateRandom();
}

bool Player::IsValidSpectatePlayer(Player *pPlayer)
{
    if (g_gametype->integer <= GT_FFA) {
        return true;
    }

    if (GetTeam() <= TEAM_FREEFORALL) {
        return true;
    }

    if (g_forceteamspectate->integer) {
        if (!GetDM_Team()->NumLivePlayers()) {
            return true;
        }

        if (pPlayer->GetTeam() == GetTeam()) {
            return true;
        }

        return false;
    }

    return true;
}

void Player::SetPlayerSpectate(bool bNext)
{
    int        i;
    int        dir;
    int        num;
    gentity_t *ent;
    Player    *pPlayer;

    if (bNext) {
        dir = 1;
        num = m_iPlayerSpectating;
    } else {
        dir = -1;
        if (m_iPlayerSpectating) {
            num = m_iPlayerSpectating - 2;
        } else {
            num = game.maxclients - 1;
        }
    }

    for (i = num; i < game.maxclients && i >= 0; i += dir) {
        ent = &g_entities[i];
        if (!ent->inuse || !ent->entity) {
            continue;
        }

        pPlayer = (Player *)ent->entity;

        if (!pPlayer->IsDead() && !pPlayer->IsSpectator() && IsValidSpectatePlayer(pPlayer)) {
            m_iPlayerSpectating = i + 1;
            client->ps.camera_flags &= ~CF_CAMERA_CUT_BIT;
            client->ps.camera_flags |= (client->ps.camera_flags & CF_CAMERA_CUT_BIT) ^ CF_CAMERA_CUT_BIT;
            return;
        }
    }

    if (m_iPlayerSpectating) {
        m_iPlayerSpectating = 0;
        SetPlayerSpectate(bNext);
    }
}

void Player::SetPlayerSpectateRandom(void)
{
    Player *pPlayer;
    int     i;
    int     numvalid;
    int     iRandom;

    numvalid = 0;

    for (i = 0; i < game.maxclients; i++) {
        gentity_t *ent = &g_entities[i];
        if (!ent->inuse || !ent->entity) {
            continue;
        }

        pPlayer = static_cast<Player *>(ent->entity);
        if (!pPlayer->IsDead() && !pPlayer->IsSpectator() && IsValidSpectatePlayer(pPlayer)) {
            numvalid++;
        }
    }

    if (!numvalid) {
        // There is no valid player to spectate

        // Added in OPM.
        //  Clear the player spectating value
        m_iPlayerSpectating = 0;
        return;
    }

    iRandom = (int)(random() * numvalid);

    for (i = 0; i < game.maxclients; i++) {
        gentity_t *ent = &g_entities[i];
        if (!ent->inuse || !ent->entity) {
            continue;
        }

        pPlayer = static_cast<Player *>(ent->entity);
        if (!pPlayer->IsDead() && !pPlayer->IsSpectator() && IsValidSpectatePlayer(pPlayer)) {
            if (!iRandom) {
                m_iPlayerSpectating = i + 1;

                client->ps.camera_flags &= ~CF_CAMERA_CUT_BIT;
                client->ps.camera_flags |= (client->ps.camera_flags & CF_CAMERA_CUT_BIT) ^ CF_CAMERA_CUT_BIT;
                break;
            }

            iRandom--;
        }
    }
}

void Player::GetSpectateFollowOrientation(Player *pPlayer, Vector& vPos, Vector& vAng)
{
    Vector  forward, right, up;
    Vector  vCamOfs;
    Vector  start;
    trace_t trace;

    if (!g_spectatefollow_firstperson->integer) {
        // spectating a player
        vAng = pPlayer->GetVAngles();

        AngleVectors(vAng, forward, right, up);

        vCamOfs = pPlayer->origin;
        vCamOfs[2] += pPlayer->viewheight;

        vCamOfs += forward * g_spectatefollow_forward->value;
        vCamOfs += right * g_spectatefollow_right->value;
        vCamOfs += up * g_spectatefollow_up->value;

        if (pPlayer->client->ps.fLeanAngle != 0.0f) {
            vCamOfs += pPlayer->client->ps.fLeanAngle * 0.65f * right;
        }

        start = pPlayer->origin;
        start[2] += pPlayer->maxs[2] - 2.0;

        Vector vMins = Vector(-2, -2, 2);
        Vector vMaxs = Vector(2, 2, 2);

        trace =
            G_Trace(start, vMins, vMaxs, vCamOfs, pPlayer, MASK_SHOT, false, "Player::GetSpectateFollowOrientation");

        vAng[0] += g_spectatefollow_pitch->value * trace.fraction;
        vPos = trace.endpos;
    } else {
        vAng = pPlayer->angles;
        vPos = pPlayer->origin;
    }
}

void Player::Spectator(Event *ev)
{
    client->pers.dm_primary[0] = 0;
    SetTeam(TEAM_SPECTATOR);
}

void Player::Leave_DM_Team(Event *ev)
{
    // Fixed in OPM
    //  FIXME: should it be permanently disabled ?
#if 0
    if (current_team)
    {
        dmManager.LeaveTeam(this);
    }
    else
    {
        gi.centerprintf(edict, gi.LV_ConvertString("You are not on a team"));
    }
#endif
}

void Player::Join_DM_Team(Event *ev)
{
    teamtype_t  team;
    str         teamname;
    const char *join_message;
    Entity     *ent;

    if (ev->isSubclassOf(ConsoleEvent) && disable_team_change) {
        // Added in OPM
        return;
    }

    teamname = ev->GetString(1);

    if (!teamname.icmp("allies")) {
        team = TEAM_ALLIES;
    } else if (!teamname.icmp("axis") || !teamname.icmp("german") || !teamname.icmp("nazi")) {
        team = TEAM_AXIS;
    } else {
        team = TEAM_AXIS;
    }

    if (current_team && current_team->m_teamnumber == team) {
        //
        // don't switch if on same team
        //
        return;
    }

    if (deadflag && deadflag != DEAD_DEAD) {
        // ignore team switching if the player is dying and not dead
        return;
    }

    if (ev->isSubclassOf(ConsoleEvent) && !CheckCanSwitchTeam(team)) {
        return;
    }

    m_fTeamSelectTime = level.time;
    SetTeam(team);
    // Make sure to remove player from turret
    RemoveFromVehiclesAndTurrets();

    //
    // Since 2.0: Remove projectiles the player shot
    //
    for (ent = G_NextEntity(NULL); ent; ent = G_NextEntity(ent)) {
        if (ent->IsSubclassOfProjectile() && ent->edict->r.ownerNum == edict->r.ownerNum) {
            ent->PostEvent(EV_Remove, 0);
        }
    }

    if (client->pers.dm_primary[0]) {
        if (IsSpectator()) {
            if (m_fSpawnTimeLeft) {
                m_bWaitingForRespawn = true;
            } else if (AllowTeamRespawn()) {
                EndSpectator();

                if (deadflag) {
                    deadflag = DEAD_DEAD;
                }

                PostEvent(EV_Player_Respawn, 0);
                gi.centerprintf(edict, " ");
            }
        } else if (g_gametype->integer >= GT_TEAM) {
            client->pers.dm_primary[0] = 0;
            UserSelectWeapon(false);
            Spectator();
        } else {
            PostEvent(EV_Player_Respawn, 0);
        }
    } else if (IsSpectator()) {
        UserSelectWeapon(true);
    }

    if (g_gametype->integer >= GT_TEAM) {
        //
        // in team game modes, display a message to indicate
        // a player joined a team
        //
        if (GetTeam() == TEAM_ALLIES) {
            join_message = "has joined the Allies";
        } else if (GetTeam() == TEAM_AXIS) {
            join_message = "has joined the Axis";
        } else {
            return;
        }

        G_PrintToAllClients(va("%s %s\n", client->pers.netname, gi.LV_ConvertString(join_message)), 2);
    }
}

void Player::Auto_Join_DM_Team(Event *ev)
{
    Event *event = new Event(EV_Player_JoinDMTeam);

    if (dmManager.GetAutoJoinTeam() == TEAM_AXIS) {
        event->AddString("axis");
    } else {
        event->AddString("allies");
    }

    ProcessEvent(event);
}

teamtype_t Player::GetTeam() const
{
    return dm_team;
}

void Player::SetTeam(teamtype_t team)
{
    dmManager.JoinTeam(this, team);

    if (dm_team == TEAM_SPECTATOR) {
        Spectator();
    }
}

void Player::SetDM_Team(DM_Team *team)
{
    current_team = team;

    // clear the player's team
    edict->s.eFlags &= ~EF_ANY_TEAM;

    if (team) {
        dm_team = static_cast<teamtype_t>(team->getNumber());
        if (dm_team == TEAM_ALLIES) {
            edict->s.eFlags |= EF_ALLIES;
        } else if (dm_team == TEAM_AXIS) {
            edict->s.eFlags |= EF_AXIS;
        }
    } else {
        dm_team = TEAM_NONE;
    }

    client->pers.teamnum = dm_team;
    G_SetClientConfigString(edict);

    if (m_fTeamSelectTime != level.time && (edict->s.eFlags & (EF_ANY_TEAM))) {
        InitModel();
    }
}

DM_Team *Player::GetDM_Team()
{
    return current_team;
}

bool Player::IsSpectator(void)
{
    return (m_bSpectator || m_bTempSpectator);
}

void Player::BeginFight(void)
{
    m_bAllowFighting = true;
}

void Player::EndFight(void)
{
    m_bAllowFighting = false;
}

void Player::WarpToPoint(Entity *spawnpoint)
{
    if (spawnpoint) {
        setOrigin(spawnpoint->origin + Vector(0, 0, 1));
        setAngles(spawnpoint->angles);
        SetViewAngles(angles);
        client->ps.camera_flags &= ~CF_CAMERA_CUT_BIT;
        client->ps.camera_flags |= (client->ps.camera_flags & CF_CAMERA_CUT_BIT) ^ CF_CAMERA_CUT_BIT;
    }
}

void Player::UpdateStatus(const char *s)
{
    gi.SendServerCommand(edict - g_entities, "status \"%s\"", s);
}

void Player::HUDPrint(const char *s)
{
    gi.SendServerCommand(edict - g_entities, "hudprint \"%s\"\n", s);
}

void Player::GibEvent(Event *ev)
{
    qboolean hidemodel;

    hidemodel = !ev->GetInteger(1);

    if (com_blood->integer) {
        if (hidemodel) {
            gibbed     = true;
            takedamage = DAMAGE_NO;
            setSolidType(SOLID_NOT);
            hideModel();
        }

        CreateGibs(this, health, 0.75f, 3);
    }
}

void Player::ArmorDamage(Event *ev)
{
    int mod = ev->GetInteger(9);

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        // players that are not allowed fighting mustn't take damage
        if (!m_bAllowFighting && mod != MOD_TELEFRAG) {
            return;
        }

        Player *attacker = (Player *)ev->GetEntity(1);

        if (attacker && attacker->IsSubclassOfPlayer()) {
            if (attacker != this) {
                if (g_gametype->integer >= GT_TEAM && !g_teamdamage->integer) {
                    // check for team damage
                    if (attacker->GetDM_Team() == GetDM_Team() && mod != MOD_TELEFRAG) {
                        return;
                    }
                }

                pAttackerDistPointer = attacker;
                fAttackerDispTime    = g_drawattackertime->value + level.time;
            }
        }
    }

    m_iNumHitsTaken++;

    Sentient::ArmorDamage(ev);

    Event *event = new Event;

    event->AddEntity(ev->GetEntity(1));
    event->AddFloat(ev->GetFloat(2));
    event->AddEntity(ev->GetEntity(3));
    event->AddVector(ev->GetVector(4));
    event->AddVector(ev->GetVector(5));
    event->AddVector(ev->GetVector(6));
    event->AddInteger(ev->GetInteger(7));
    event->AddInteger(ev->GetInteger(8));
    event->AddInteger(ev->GetInteger(9));
    event->AddInteger(ev->GetInteger(10));
    event->AddEntity(this);

    scriptedEvents[SE_DAMAGE].Trigger(event);
}

void Player::Disconnect(void)
{
    Event *ev = new Event;

    ev->AddListener(this);
    scriptedEvents[SE_DISCONNECTED].Trigger(ev);

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        dmManager.RemovePlayer(this);
    }
}

void Player::CallVote(Event *ev)
{
    str arg1;
    str arg2;
    int numVoters;

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }

    if (!g_allowvote->integer) {
        HUDPrint(va("%s\n", gi.LV_ConvertString("Voting not allowed here.")));
        return;
    }

    if (level.m_voteTime != 0.0f) {
        HUDPrint(va("%s\n", gi.LV_ConvertString("A vote is already in progress.")));
        return;
    }

    if (votecount >= MAX_VOTE_COUNT) {
        if (m_fLastVoteTime) {
            while (m_fLastVoteTime < level.time && votecount > 0) {
                m_fLastVoteTime += 60;
                votecount--;
            }
        }

        if (votecount >= MAX_VOTE_COUNT) {
            HUDPrint(
                va("%s %d %s.\n",
                   gi.LV_ConvertString("You cannot call another vote for"),
                   (unsigned int)(m_fLastVoteTime - level.time + 1),
                   gi.LV_ConvertString("seconds"))
            );
            return;
        }
    }

    if (IsSpectator() || IsDead()) {
        HUDPrint(va("%s\n", gi.LV_ConvertString("You are not allowed to call a vote as a spectator.")));
        return;
    }

    arg1 = ev->GetString(1);
    if (ev->NumArgs() > 1) {
        arg2 = ev->GetString(2);
    }

    if (!atoi(arg1.c_str())) {
        if (strchr(arg1.c_str(), ';') || strchr(arg2.c_str(), ';')) {
            HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid vote string.")));
            return;
        }

        if (Q_stricmp(arg1.c_str(), "restart") && Q_stricmp(arg1.c_str(), "nextmap") && Q_stricmp(arg1.c_str(), "map")
            && Q_stricmp(arg1.c_str(), "g_gametype") && Q_stricmp(arg1.c_str(), "kick")
            && Q_stricmp(arg1.c_str(), "clientkick") && Q_stricmp(arg1.c_str(), "fraglimit")) {
            HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid vote string.")));
            HUDPrint(va(
                "%s restart, nextmap, map <mapname>, g_gametype <n>, fraglimit <n>, timelimit <n>, kick <player>, and "
                "clientkick <player #>.\n",
                gi.LV_ConvertString("Vote commands are:")
            ));

            return;
        }

        if (!Q_stricmp(arg1.c_str(), "kick")) {
            //
            // check for a valid player
            //
            gentity_t *ent;
            int        i;

            for (i = 0; i < game.maxclients; i++) {
                ent = &g_entities[i];

                if (!ent->inuse || !ent->client || !ent->entity) {
                    continue;
                }

                if (!Q_stricmp(ent->client->pers.netname, arg2.c_str())) {
                    // Prevent the player from kicking himself out
                    if (ent->entity == this) {
                        HUDPrint(va("%s\n", gi.LV_ConvertString("You are not allowed to kick yourself.")));
                        return;
                    }

                    break;
                }
            }

            if (i == game.maxclients) {
                HUDPrint(va("%s %s\n", arg2.c_str(), gi.LV_ConvertString("is not a valid player name to kick.")));
                return;
            }
        } else if (!Q_stricmp(arg1.c_str(), "map") && *sv_nextmap->string) {
            level.m_voteString = va("%s %s; set next map \"%s\"", arg1.c_str(), arg2.c_str(), arg2.c_str());
        } else {
            level.m_voteString = va("%s %s", arg1.c_str(), arg2.c_str());
        }

        if (level.m_nextVoteTime) {
            level.m_nextVoteTime = 0;
            gi.SendConsoleCommand(va("%s", level.m_voteString.c_str()));
        }

        if (!Q_stricmp(arg1.c_str(), "g_gametype")) {
            int gametypeNum;

            // get the gametype number
            gametypeNum = atoi(arg2.c_str());
            if (gametypeNum <= GT_SINGLE_PLAYER || gametypeNum >= GT_MAX_GAME_TYPE) {
                HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid gametype for a vote.")));
                return;
            }

            level.m_voteString = va("%s %i", arg1.c_str(), gametypeNum);

            switch (gametypeNum) {
            case GT_FFA:
                level.m_voteName = "Game Type Free-For-All";
                break;
            case GT_TEAM:
                level.m_voteName = "Game Type Match";
                break;
            case GT_TEAM_ROUNDS:
                level.m_voteName = "Game Type Round-Based-Match";
                break;
            case GT_OBJECTIVE:
                level.m_voteName = "Game Type Objective-Match";
                break;
            case GT_TOW:
                level.m_voteName = "Game Type Tug of War";
                break;
            case GT_LIBERATION:
                level.m_voteName = "Game Type Liberation";
                break;
            default:
                HUDPrint(va("%s %s %d\n", gi.LV_ConvertString("Game Type"), arg1.c_str(), gametypeNum));
                return;
            }
        } else if (!Q_stricmp(arg1.c_str(), "map")) {
            if (*sv_nextmap->string) {
                level.m_voteString = va("%s %s; set nextmap \"%s\"", arg1.c_str(), arg2.c_str(), sv_nextmap->string);
            } else {
                level.m_voteString = va("%s %s", arg1.c_str(), arg2.c_str());
            }

            level.m_voteName = va("Map %s", arg2.c_str());
        } else {
            level.m_voteString = va("%s %s", arg1.c_str(), arg2.c_str());
            level.m_voteName   = level.m_voteString;
        }
    } else {
        int              voteIndex;
        int              subListIndex;
        str              voteOptionCommand;
        str              voteOptionSubCommand;
        str              voteOptionName;
        str              voteOptionSubName;
        voteoptiontype_t optionType;

        union {
            int   optionInteger;
            float optionFloat;
            int   optionClientNum;
        };

        gentity_t *ent;

        char buffer[64];

        voteIndex = atoi(arg1.c_str());
        if (!level.GetVoteOptionMain(voteIndex, &voteOptionCommand, &optionType)) {
            HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid vote option.")));
            return;
        }

        level.GetVoteOptionMainName(voteIndex, &voteOptionName);

        switch (optionType) {
        case VOTE_NO_CHOICES:
            level.m_voteString = voteOptionCommand;
            level.m_voteName   = voteOptionName;
            break;
        case VOTE_OPTION_LIST:
            subListIndex = atoi(arg2.c_str());

            if (!level.GetVoteOptionSub(voteIndex, subListIndex, &voteOptionSubCommand)) {
                HUDPrint(
                    va("%s %i %s \"%s\".\n",
                       gi.LV_ConvertString("Invalid vote choice"),
                       subListIndex,
                       gi.LV_ConvertString("for vote option"),
                       voteOptionName.c_str())
                );
                return;
            }

            level.m_voteString = va("%s %s", voteOptionCommand.c_str(), voteOptionSubCommand.c_str());
            // get the sub-option name
            level.GetVoteOptionSubName(voteIndex, subListIndex, &voteOptionSubName);
            level.m_voteName =
                va("%s %s", gi.LV_ConvertString(voteOptionName.c_str()), gi.LV_ConvertString(voteOptionSubName.c_str())
                );
            break;
        case VOTE_OPTION_TEXT:
            if (strchr(arg2.c_str(), ';')) {
                HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid vote text entered.")));
                return;
            }

            level.m_voteString = va("%s %s", voteOptionCommand.c_str(), arg2.c_str());
            level.m_voteName   = va("%s %s", gi.LV_ConvertString(voteOptionName.c_str()), arg2.c_str());
            break;
        case VOTE_OPTION_INTEGER:
            optionInteger = atoi(arg2.c_str());
            Com_sprintf(buffer, sizeof(buffer), "%d", optionInteger);

            if (Q_stricmp(buffer, arg2.c_str())) {
                HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid vote integer entered.")));
                return;
            }

            level.m_voteString = va("%s %i", voteOptionCommand.c_str(), optionInteger);
            level.m_voteName   = va("%s %i", gi.LV_ConvertString(voteOptionName.c_str()), optionInteger);
            break;
        case VOTE_OPTION_FLOAT:
            optionFloat = atof(arg2.c_str());
            Com_sprintf(buffer, sizeof(buffer), "%f", optionFloat);

            if (Q_stricmp(buffer, arg2.c_str())) {
                HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid vote float entered.")));
                return;
            }

            level.m_voteString = va("%s %g", voteOptionCommand.c_str(), optionFloat);
            level.m_voteName   = va("%s %g", gi.LV_ConvertString(voteOptionName.c_str()), optionFloat);
            break;
        case VOTE_OPTION_CLIENT:
        case VOTE_OPTION_CLIENT_NOT_SELF:
            optionClientNum = atoi(arg2.c_str());
            if (optionClientNum < 0 || optionClientNum >= game.maxclients) {
                HUDPrint(va("%s\n", gi.LV_ConvertString("Invalid client number for a vote.")));
                return;
            }

            ent = &g_entities[optionClientNum];
            if (!ent->inuse || !ent->client || !ent->entity) {
                HUDPrint(va("%s\n", gi.LV_ConvertString("Client selected for the vote is not connected.")));
                return;
            }

            level.m_voteString = va("%s %i", voteOptionCommand.c_str(), optionClientNum);
            level.m_voteName =
                va("%s #%i: %s", gi.LV_ConvertString(voteOptionName.c_str()), optionClientNum, ent->client->pers.netname
                );
            break;
        default:
            level.GetVoteOptionMainName(voteIndex, &voteOptionName);
            gi.Printf(
                "ERROR: Vote option (\"%s\" \"%s\") with unknown vote option type\n",
                voteOptionName.c_str(),
                voteOptionCommand.c_str()
            );
            return;
        }
    }

    G_PrintToAllClients(va("%s %s.\n", client->pers.netname, gi.LV_ConvertString("called a vote")));

    level.m_voteTime = (level.svsFloatTime - level.svsStartFloatTime) * 1000;
    level.m_voteYes  = 1;
    level.m_voteNo   = 0;

    // Reset all player's vote
    numVoters = 0;

    for (int i = 0; i < game.maxclients; i++) {
        gentity_t *ent = &g_entities[i];

        if (!ent->client || !ent->inuse) {
            continue;
        }

        Player *p = (Player *)ent->entity;
        p->voted  = false;

        numVoters++;
    }

    level.m_numVoters = numVoters;
    client->ps.voted  = true;
    voted             = true;
    votecount++;

    m_fLastVoteTime = level.time + 60;

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        //
        // clients below version 2.0 don't support vote cs
        //
        gi.setConfigstring(CS_VOTE_TIME, va("%i", level.m_voteTime));
        gi.setConfigstring(CS_VOTE_STRING, level.m_voteName.c_str());
        gi.setConfigstring(CS_VOTE_YES, va("%i", level.m_voteYes));
        gi.setConfigstring(CS_VOTE_NO, va("%i", level.m_voteNo));
        gi.setConfigstring(CS_VOTE_UNDECIDED, va("%i", level.m_numVoters - (level.m_voteYes + level.m_voteNo)));
    }
}

void Player::Vote(Event *ev)
{
    str arg1;

    if (level.m_voteTime == 0.0f) {
        HUDPrint(gi.LV_ConvertString("No vote in progress."));
        return;
    }

    if (client->ps.voted) {
        HUDPrint(gi.LV_ConvertString("Vote already cast."));
        return;
    }

    if (ev->NumArgs() != 1) {
        HUDPrint(va("%s: vote <1|0|y|n>", gi.LV_ConvertString("Usage")));
        return;
    }

    HUDPrint(gi.LV_ConvertString("Vote cast."));
    client->ps.voted = true;

    arg1  = ev->GetString(1);
    voted = (arg1[0] == 'y') || (arg1[0] == 'Y') || (arg1[0] == '1');
}

void Player::RetrieveVoteOptions(Event *ev)
{
    if (m_fNextVoteOptionTime > level.time) {
        gi.SendServerCommand(edict - g_entities, "vo0 \"\"\n");
        gi.SendServerCommand(edict - g_entities, "vo2 \"\"\n");
    } else {
        m_fNextVoteOptionTime = level.time + 2.0;
        level.SendVoteOptionsFile(edict);
    }
}

void Player::EventPrimaryDMWeapon(Event *ev)
{
    str  dm_weapon = ev->GetString(1);
    bool bIsBanned = false;

    if (!dm_weapon.length()) {
        // Added in OPM.
        //  Prevent the player from cheating by going into spectator
        return;
    }

    if (!str::icmp(dm_weapon, "shotgun")) {
        bIsBanned = (dmflags->integer & DF_WEAPON_NO_SHOTGUN);
    } else if (!str::icmp(dm_weapon, "rifle")) {
        bIsBanned = (dmflags->integer & DF_WEAPON_NO_RIFLE);
    } else if (!str::icmp(dm_weapon, "sniper")) {
        bIsBanned = (dmflags->integer & DF_WEAPON_NO_SNIPER);
    } else if (!str::icmp(dm_weapon, "smg")) {
        bIsBanned = (dmflags->integer & DF_WEAPON_NO_SMG);
    } else if (!str::icmp(dm_weapon, "mg")) {
        bIsBanned = (dmflags->integer & DF_WEAPON_NO_MG);
    } else if (!str::icmp(dm_weapon, "heavy")) {
        bIsBanned = (dmflags->integer & DF_WEAPON_NO_ROCKET);
    } else if (!str::icmp(dm_weapon, "landmine")) {
        bIsBanned = (dmflags->integer & DF_WEAPON_NO_LANDMINE) || !QueryLandminesAllowed();
    } else if (!str::icmp(dm_weapon, "auto")) {
        const char *primaryList[7];
        size_t      numPrimaries = 0;

        //
        // Added in OPM
        //  Choose a random allowed weapon
        //
        if (!(dmflags->integer & DF_WEAPON_NO_SHOTGUN)) {
            primaryList[numPrimaries++] = "shotgun";
        }
        if (!(dmflags->integer & DF_WEAPON_NO_RIFLE)) {
            primaryList[numPrimaries++] = "rifle";
        }
        if (!(dmflags->integer & DF_WEAPON_NO_SNIPER)) {
            primaryList[numPrimaries++] = "sniper";
        }
        if (!(dmflags->integer & DF_WEAPON_NO_SMG)) {
            primaryList[numPrimaries++] = "smg";
        }
        if (!(dmflags->integer & DF_WEAPON_NO_MG)) {
            primaryList[numPrimaries++] = "mg";
        }
        if (!(dmflags->integer & DF_WEAPON_NO_ROCKET)) {
            primaryList[numPrimaries++] = "heavy";
        }
        if (!(dmflags->integer & DF_WEAPON_NO_LANDMINE) && QueryLandminesAllowed()) {
            primaryList[numPrimaries++] = "landmine";
        }

        if (numPrimaries) {
            dm_weapon = primaryList[rand() % numPrimaries];
        } else {
            bIsBanned = qtrue;
        }
    }

    if (bIsBanned) {
        gi.SendServerCommand(
            edict - g_entities, "print \"" HUD_MESSAGE_WHITE "%s\n\"", "That weapon is currently banned."
        );
        return;
    }

    Q_strncpyz(client->pers.dm_primary, dm_weapon.c_str(), sizeof(client->pers.dm_primary));

    if (m_bSpectator) {
        if (current_team && (current_team->m_teamnumber == TEAM_AXIS || current_team->m_teamnumber == TEAM_ALLIES)) {
            if (m_fSpawnTimeLeft) {
                m_bWaitingForRespawn = true;
            } else if (AllowTeamRespawn()) {
                EndSpectator();

                if (deadflag) {
                    deadflag = DEAD_DEAD;
                }

                PostEvent(EV_Player_Respawn, 0);

                gi.centerprintf(edict, "");
            }
        } else {
            gi.SendServerCommand(edict - g_entities, "stufftext \"wait 250;pushmenu_teamselect\"");
        }
    } else {
        gi.SendServerCommand(
            edict - g_entities, "print \"" HUD_MESSAGE_WHITE "%s\n\"", "Will switch to new weapon next time you respawn"
        );
    }
}

void Player::DeadBody(Event *ev)
{
    Body *body;

    if (knockdown) {
        return;
    }

    knockdown = true;

    body = new Body;
    body->setModel(model);

    for (int i = 0; i < MAX_FRAMEINFOS; i++) {
        body->edict->s.frameInfo[i] = edict->s.frameInfo[i];
    }

    body->edict->s.actionWeight = edict->s.actionWeight;
    body->edict->s.scale        = edict->s.scale;

    body->setOrigin(origin);
    body->setAngles(angles);

    body->edict->s.eFlags &= ~(EF_AXIS | EF_ALLIES);

    if (GetTeam() == TEAM_ALLIES) {
        edict->s.eFlags |= EF_ALLIES;
    } else if (GetTeam() == TEAM_AXIS) {
        edict->s.eFlags |= EF_AXIS;
    }
}

void Player::WonMatch(void)
{
    num_won_matches++;
}

void Player::LostMatch(void)
{
    num_lost_matches++;
}

void Player::ArmWithWeapons(Event *ev)
{
    EquipWeapons();
}

void Player::EventGetCurrentDMWeaponType(Event *ev)
{
    ev->AddString(GetCurrentDMWeaponType());
}

void Player::PhysicsOff(Event *ev)
{
    if (g_target_game > TG_MOH || g_gametype->integer != GT_SINGLE_PLAYER) {
        // Added in 2.0
        //  Reset the state to STAND before disabling physics
        EvaluateState(statemap_Torso->FindState("STAND"), statemap_Legs->FindState("STAND"));
    }

    flags |= FL_IMMOBILE;
}

void Player::PhysicsOn(Event *ev)
{
    flags &= ~FL_IMMOBILE;
}

void Player::GetIsSpectator(Event *ev)
{
    ev->AddInteger(IsSpectator());
}

void Player::EventSetInJail(Event *ev)
{
    m_bIsInJail = ev->GetBoolean(1);
}

bool Player::IsInJail() const
{
    return m_bIsInJail;
}

void Player::EventGetInJail(Event *ev)
{
    ev->AddInteger(m_bIsInJail);
}

void Player::GetNationalityPrefix(Event *ev)
{
    nationality_t nationality;

    if (GetTeam() == TEAM_AXIS) {
        nationality = GetPlayerAxisTeamType(client->pers.dm_playergermanmodel);
    } else {
        nationality = GetPlayerAlliedTeamType(client->pers.dm_playermodel);
    }

    switch (nationality) {
    case NA_RUSSIAN:
        ev->AddString("dfrru");
        break;
    case NA_ITALIAN:
        ev->AddString("denit");
        break;
    case NA_BRITISH:
        ev->AddString("dfruk");
        break;
    case NA_AMERICAN:
        ev->AddString("dfr");
        break;

    case NA_NONE:
    default:
        ev->AddString("dfr");
        break;
    }
}

void Player::GetIsDisguised(Event *ev)
{
    ev->AddInteger(m_bIsDisguised);
}

void Player::GetHasDisguise(Event *ev)
{
    ev->AddInteger(m_bHasDisguise);
}

void Player::SetHasDisguise(Event *ev)
{
    m_bHasDisguise = ev->GetBoolean(1);
}

void Player::SetObjectiveCount(Event *ev)
{
    m_iObjectivesCompleted = ev->GetInteger(1);
    m_iNumObjectives       = ev->GetInteger(2);
}

void Player::Stats(Event *ev)
{
    char entry[2048];
    int  i;
    str  szPreferredWeapon;
    str  szGunneryEvaluation;
    int  iNumHeadShots;
    int  iNumTorsoShots;
    int  iNumLeftLegShots;
    int  iNumRightLegShots;
    int  iNumGroinShots;
    int  iNumLeftArmShots;
    int  iNumRightArmShots;
    int  iNumShotsFired;
    int  iNumHits;
    int  iBestNumHits;

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        // Only works in singleplayer
        return;
    }

    szPreferredWeapon   = "none";
    szGunneryEvaluation = "none";
    iNumHeadShots       = m_iNumHeadShots;
    iNumTorsoShots      = m_iNumTorsoShots;
    iNumLeftLegShots    = m_iNumLeftLegShots;
    iNumRightLegShots   = m_iNumRightLegShots;
    iNumGroinShots      = m_iNumGroinShots;
    iNumLeftArmShots    = m_iNumLeftArmShots;
    iNumRightArmShots   = m_iNumRightArmShots;
    iNumShotsFired      = m_iNumShotsFired;
    iNumHits            = m_iNumHits;
    iBestNumHits        = 0;

    for (i = 1; i <= inventory.NumObjects(); i++) {
        Entity *pEnt = G_GetEntity(inventory.ObjectAt(i));
        if (pEnt->IsSubclassOfWeapon()) {
            Weapon *pWeap = static_cast<Weapon *>(pEnt);

            iNumHeadShots += pWeap->m_iNumHeadShots;
            iNumTorsoShots += pWeap->m_iNumTorsoShots;
            iNumLeftLegShots += pWeap->m_iNumLeftLegShots;
            iNumRightLegShots += pWeap->m_iNumRightLegShots;
            iNumGroinShots += pWeap->m_iNumGroinShots;
            iNumLeftArmShots += pWeap->m_iNumLeftArmShots;
            iNumRightArmShots += pWeap->m_iNumRightArmShots;
            iNumShotsFired += pWeap->m_iNumShotsFired;
            iNumHits += pWeap->m_iNumHits;

            if (pWeap->m_iNumHits > iBestNumHits) {
                szPreferredWeapon = pWeap->item_name;
                iBestNumHits      = pWeap->m_iNumHits;
            }
        }
    }

    if (m_sPerferredWeaponOverride.length()) {
        szPreferredWeapon = m_sPerferredWeaponOverride;
    }

    if (iNumHits) {
        Com_sprintf(
            entry,
            sizeof(entry),
            "%i %i %i %i %.1f \"%s\" %i %i %i \"%.1f\" \"%.1f\" \"%.1f\" \"%.1f\" \"%.1f\" \"%.1f\" \"%.1f\" \"%s\" %i "
            "%i %i",
            m_iNumObjectives,
            m_iObjectivesCompleted,
            iNumShotsFired,
            iNumHits,
            (iNumHits / iNumShotsFired * 100.f),
            szPreferredWeapon.c_str(),
            m_iNumHitsTaken,
            m_iNumObjectsDestroyed,
            m_iNumEnemiesKilled,
            iNumHeadShots * 100.f / iNumHits,
            iNumTorsoShots * 100.f / iNumHits,
            iNumLeftLegShots * 100.f / iNumHits,
            iNumRightLegShots * 100.f / iNumHits,
            iNumGroinShots * 100.f / iNumHits,
            iNumLeftArmShots * 100.f / iNumHits,
            iNumRightArmShots * 100.f / iNumHits,
            szGunneryEvaluation.c_str(),
            g_gotmedal->integer,
            g_success->integer,
            g_failed->integer
        );
    } else {
        Com_sprintf(
            entry,
            sizeof(entry),
            "%i %i %i %i %i \"%s\" %i %i %i \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%s\" %i %i %i",
            m_iNumObjectives,
            m_iObjectivesCompleted,
            iNumShotsFired,
            0,
            0,
            szPreferredWeapon.c_str(),
            m_iNumHitsTaken,
            m_iNumObjectsDestroyed,
            m_iNumEnemiesKilled,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            szGunneryEvaluation.c_str(),
            g_gotmedal->integer,
            g_success->integer,
            g_failed->integer
        );
    }

    gi.SendServerCommand(edict - g_entities, "stats %s", entry);
}

void Player::EventStuffText(Event *ev)
{
    if (level.spawning) {
        Event *event = new Event(EV_Player_StuffText);
        event->AddValue(ev->GetValue(1));
        PostEvent(event, level.frametime, 0);
    } else {
        gi.SendServerCommand(edict - g_entities, "stufftext \"%s\"", ev->GetString(1).c_str());
    }
}

void Player::EventSetVoiceType(Event *ev)
{
    str sVoiceName = ev->GetString(1);

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        if (!sVoiceName.icmp("american")) {
            m_voiceType = PVT_ALLIED_AMERICAN;
        } else if (!sVoiceName.icmp("british")) {
            m_voiceType = PVT_ALLIED_BRITISH;
        } else if (!sVoiceName.icmp("russian")) {
            m_voiceType = PVT_ALLIED_RUSSIAN;
        } else if (!sVoiceName.icmp("german")) {
            m_voiceType = PVT_AXIS_GERMAN;
        } else if (!sVoiceName.icmp("italian")) {
            m_voiceType = PVT_AXIS_ITALIAN;
        } else {
            m_voiceType = PVT_NONE_SET;
        }
    } else {
        if (!sVoiceName.icmp("airborne")) {
            m_voiceType = PVT_ALLIED_AIRBORNE;
        } else if (!sVoiceName.icmp("manon")) {
            m_voiceType = PVT_ALLIED_MANON;
        } else if (!sVoiceName.icmp("SAS")) {
            m_voiceType = PVT_ALLIED_SAS;
        } else if (!sVoiceName.icmp("pilot")) {
            m_voiceType = PVT_ALLIED_PILOT;
        } else if (!sVoiceName.icmp("army")) {
            m_voiceType = PVT_ALLIED_ARMY;
        } else if (!sVoiceName.icmp("ranger")) {
            m_voiceType = PVT_ALLIED_RANGER;
        } else if (!sVoiceName.icmp("axis1")) {
            m_voiceType = PVT_AXIS_AXIS1;
        } else if (!sVoiceName.icmp("axis2")) {
            m_voiceType = PVT_AXIS_AXIS2;
        } else if (!sVoiceName.icmp("axis3")) {
            m_voiceType = PVT_AXIS_AXIS3;
        } else if (!sVoiceName.icmp("axis4")) {
            m_voiceType = PVT_AXIS_AXIS4;
        } else if (!sVoiceName.icmp("axis5")) {
            m_voiceType = PVT_AXIS_AXIS5;
        } else {
            m_voiceType = PVT_NONE_SET;
        }
    }
}

void Player::GetTeamDialogPrefix(str& outPrefix)
{
    if (GetTeam() == TEAM_AXIS) {
        outPrefix = "axis_";
    } else {
        outPrefix = "allied_";
    }

    if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
        switch (m_voiceType) {
        case PVT_ALLIED_AMERICAN:
            outPrefix += "american_";
            break;
        case PVT_ALLIED_BRITISH:
            outPrefix += "british_";
            break;
        case PVT_ALLIED_RUSSIAN:
            outPrefix += "russian_";
            break;
        case PVT_AXIS_START:
            outPrefix += "german_";
            break;
        case PVT_AXIS_ITALIAN:
            outPrefix += "italian_";
            break;
        default:
            if (GetTeam() == TEAM_AXIS) {
                outPrefix += "german_";
            } else {
                outPrefix += "american_";
            }
            break;
        }
    } else {
        switch (m_voiceType) {
        case PVT_ALLIED_AIRBORNE:
            outPrefix += "airborne_";
            break;

        case PVT_ALLIED_MANON:
            outPrefix += "manon_";
            break;

        case PVT_ALLIED_SAS:
            outPrefix += "sas_";
            break;

        case PVT_ALLIED_PILOT:
            outPrefix += "pilot_";
            break;

        case PVT_ALLIED_ARMY:
            outPrefix += "army_";
            break;

        case PVT_ALLIED_RANGER:
            outPrefix += "ranger_";
            break;

        case PVT_AXIS_AXIS1:
            outPrefix += "axis1_";
            break;

        case PVT_AXIS_AXIS2:
            outPrefix += "axis2_";
            break;

        case PVT_AXIS_AXIS3:
            outPrefix += "axis3_";
            break;

        case PVT_AXIS_AXIS4:
            outPrefix += "axis4_";
            break;

        case PVT_AXIS_AXIS5:
            outPrefix += "axis5_";
            break;

        default:
            if (dm_team != TEAM_AXIS) {
                outPrefix += "army_";
            } else {
                outPrefix += "axis4_";
            }
        }
    }
}

void Player::PlayInstantMessageSound(const char *name)
{
    str soundName;

    if (g_protocol < PROTOCOL_MOHTA_MIN) {
        gi.DPrintf2("Instant message sound isn't supported on protocol below version 15");
        return;
    }

    GetTeamDialogPrefix(soundName);

    soundName += name;

    gi.MSG_SetClient(client->ps.clientNum);
    gi.MSG_StartCGM(CGM_VOICE_CHAT);
    gi.MSG_WriteCoord(m_vViewPos[0]);
    gi.MSG_WriteCoord(m_vViewPos[1]);
    gi.MSG_WriteCoord(m_vViewPos[2]);
    gi.MSG_WriteBits(0, 1);
    gi.MSG_WriteBits(edict - g_entities, 6);
    gi.MSG_WriteString(soundName.c_str());
    gi.MSG_EndCGM();
}

void Player::EventDMMessage(Event *ev)
{
    int i;
    //int              iStringLength;
    int              iMode = 0;
    str              sToken;
    char             szPrintString[MAX_SAY_TEXT]; // it's MAX_STRING_CHARS in mohaa
    size_t           iStringLength;
    const char      *pTmpInstantMsg = "";
    qboolean         bInstaMessage  = qfalse;
    AliasListNode_t *pSoundAlias    = NULL;
    const char      *pszAliasname   = NULL;
    str              sAliasName;
    str              sRandomAlias;
    gentity_t       *ent;

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }

    if (ev->NumArgs() <= 1) {
        return;
    }

    sToken = ev->GetString(2);

    // Check for taunts
    if (sToken.length() == 3 && *sToken == '*' && sToken[1] > '0' && sToken[1] <= '9' && sToken[2] > '0'
        && sToken[2] <= '9') {
        unsigned int n1, n2;

        if (IsSpectator() || IsDead()) {
            // spectators or death players can't talk
            return;
        }

        if (edict->r.svFlags & SVF_NOCLIENT) {
            // Changed in OPM
            //  Don't send a voice chat message if the entity is not sent to client
            return;
        }

        if (!g_instamsg_allowed->integer) {
            // Added in OPM
            return;
        }

        if (g_instamsg_minDelay->integer > 0 && level.inttime < m_iInstantMessageTime + g_instamsg_minDelay->integer) {
            // Added in OPM
            return;
        }

        GetTeamDialogPrefix(sAliasName);
        if (g_target_game >= target_game_e::TG_MOHTT && sToken[1] == '6') {
            // Added in 2.30
            //  Liberation messages
            sAliasName += va("lib%c", (sToken[2] + '0'));
        } else {
            sAliasName += va("%c%c", (sToken[1] + '0'), (sToken[2] + '0'));
        }

        sRandomAlias = GetRandomAlias(sAliasName, &pSoundAlias);

        // find a random alias
        if (sRandomAlias.length() > 0) {
            pszAliasname = sRandomAlias.c_str();
        }

        if (!pszAliasname) {
            pszAliasname = gi.GlobalAlias_FindRandom(sAliasName, &pSoundAlias);
        }

        if (!pszAliasname || !pSoundAlias) {
            return;
        }

        n1 = sToken[1] - '1';
        n2 = sToken[2] - '1';

        if (g_protocol >= PROTOCOL_MOHTA_MIN) {
            if (n1 >= ARRAY_LEN(pInstantMsgEng) || n2 >= ARRAY_LEN(pInstantMsgEng[0])) {
                return;
            }

            pTmpInstantMsg = pInstantMsgEng[n1][n2];
        } else {
            if (n1 >= ARRAY_LEN(pInstantMsgEng_ver6) || n2 >= ARRAY_LEN(pInstantMsgEng_ver6[0])) {
                return;
            }

            // fallback to old version
            pTmpInstantMsg = pInstantMsgEng_ver6[n1][n2];
        }

        bInstaMessage = qtrue;

        if (g_gametype->integer == GT_FFA) {
            iMode = 0;
        } else {
            if (n1 == 4) {
                iMode = 0;
            } else {
                iMode = -1;
            }
        }
    } else {
        if (!g_textmsg_allowed->integer) {
            // Added in OPM

            str errorString  = gi.LV_ConvertString("Message Error");
            str reasonString = gi.LV_ConvertString("Text chat is disabled on this server");

            gi.SendServerCommand(
                edict - g_entities,
                "print \"" HUD_MESSAGE_CHAT_WHITE "%s: %s.\n\"",
                errorString.c_str(),
                reasonString.c_str()
            );
            return;
        }

        if (g_textmsg_minDelay->integer > 0 && level.inttime < m_iTextChatTime + g_textmsg_minDelay->integer) {
            // Added in OPM
            return;
        }
    }

    if (bInstaMessage) {
        if (g_voiceChatTime->value > 0) {
            m_fTalkTime = g_voiceChatTime->value + level.time;
        }
        m_iInstantMessageTime = level.inttime;
    } else {
        iMode = ev->GetInteger(1);
        if (g_textChatTime->value > 0) {
            m_fTalkTime = g_textChatTime->value + level.time;
        }
        m_iTextChatTime = level.inttime;
    }

    Q_strncpyz(szPrintString, "print \"" HUD_MESSAGE_CHAT_WHITE, sizeof(szPrintString));

    if (m_bSpectator) {
        if (iMode <= 0) {
            Q_strcat(szPrintString, sizeof(szPrintString), gi.CL_LV_ConvertString("(spectator)"));
            Q_strcat(szPrintString, sizeof(szPrintString), " ");
        } else if (iMode <= game.maxclients) {
            ent = &g_entities[iMode - 1];

            if (ent->inuse && ent->entity && !static_cast<Player *>(ent->entity)->IsSpectator()) {
                str errorString = gi.LV_ConvertString("Message Error");
                str reasonString =
                    gi.LV_ConvertString("Spectators are not allowed to send private messages to non-spectators");

                gi.SendServerCommand(
                    edict - g_entities,
                    "print \"" HUD_MESSAGE_CHAT_WHITE "%s: %s.\n\"",
                    errorString.c_str(),
                    reasonString.c_str()
                );
                return;
            }
        }
    } else if (IsDead() || m_bTempSpectator) {
        if (iMode <= 0) {
            Q_strcat(szPrintString, sizeof(szPrintString), gi.CL_LV_ConvertString("(dead)"));
            Q_strcat(szPrintString, sizeof(szPrintString), " ");
        } else if (iMode <= game.maxclients) {
            ent = &g_entities[iMode - 1];

            if (ent->inuse && ent->entity && !static_cast<Player *>(ent->entity)->IsSpectator()) {
                str errorString = gi.LV_ConvertString("Message Error");
                str reasonString =
                    gi.LV_ConvertString("Dead players are not allowed to send private messages to active players");

                gi.SendServerCommand(
                    edict - g_entities,
                    "print \"" HUD_MESSAGE_CHAT_WHITE "%s: %s.\n\"",
                    errorString.c_str(),
                    reasonString.c_str()
                );
                return;
            }
        }
    } else if (iMode < 0) {
        Q_strcat(szPrintString, sizeof(szPrintString), gi.CL_LV_ConvertString("(team)"));
        Q_strcat(szPrintString, sizeof(szPrintString), " ");
    } else if (iMode > 0) {
        Q_strcat(szPrintString, sizeof(szPrintString), gi.CL_LV_ConvertString("(private)"));
        Q_strcat(szPrintString, sizeof(szPrintString), " ");
    }

    Q_strcat(szPrintString, sizeof(szPrintString), client->pers.netname);

    if (bInstaMessage) {
        Q_strcat(szPrintString, sizeof(szPrintString), ": ");
        Q_strcat(szPrintString, sizeof(szPrintString), gi.LV_ConvertString(pTmpInstantMsg));
    } else {
        bool met_comment;

        Q_strcat(szPrintString, sizeof(szPrintString), ":");
        iStringLength = strlen(szPrintString);

        for (i = 2; i <= ev->NumArgs(); i++) {
            sToken = ev->GetString(i);
            // Added in 2.40
            //  Special battle language tokens
            //  So players can easily tell their position, health, etc.
            sToken = TranslateBattleLanguageTokens(sToken);

            if (iStringLength + sToken.length() > (ARRAY_LEN(szPrintString) - 1)) {
                break;
            }

            // Added in OPM.
            //  Checks for comments in string (as COM_Parse will parse them)
            if (strstr(sToken, "/*")) {
                met_comment = true;
            }

            if (strstr(sToken, "*/") && met_comment) {
                // ignore messages containing comments
                return;
            }

            Q_strcat(szPrintString, sizeof(szPrintString), " ");
            Q_strcat(szPrintString, sizeof(szPrintString), gi.LV_ConvertString(sToken));
        }
    }

    Q_strcat(szPrintString, sizeof(szPrintString), "\n");

    // ignore names containing comments
    if (strstr(client->pers.netname, "//")
        || (strstr(client->pers.netname, "/*") && strstr(client->pers.netname, "*/"))) {
        return;
    }

    //
    // Added in OPM
    //=============
    // Print the dm message to console
    sToken = "";

    for (i = 2; i <= ev->NumArgs(); i++) {
        if (i != 2) {
            sToken += " ";
        }

        sToken += ev->GetString(i);
    }
    //=============

    if (iMode == 0) {
        //
        // everyone
        //

        // Added in OPM
        gi.Printf("%s (%zu) says to everyone: %s\n", client->pers.netname, edict - g_entities, sToken.c_str());

        if (!IsSpectator() || g_spectate_allow_full_chat->integer) {
            for (i = 0; i < game.maxclients; i++) {
                ent = &g_entities[i];

                if (!ent->inuse || !ent->entity) {
                    continue;
                }

                gi.SendServerCommand(i, "%s", szPrintString);

                if (bInstaMessage) {
                    gi.MSG_SetClient(i);
                    gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_VOICE_CHAT));
                    gi.MSG_WriteCoord(m_vViewPos[0]);
                    gi.MSG_WriteCoord(m_vViewPos[1]);
                    gi.MSG_WriteCoord(m_vViewPos[2]);
                    gi.MSG_WriteBits(qtrue, 1);
                    gi.MSG_WriteBits(edict - g_entities, 6);
                    gi.MSG_WriteString(sAliasName.c_str());
                    gi.MSG_EndCGM();
                }
            }
        } else {
            //
            // send a message to spectators
            //
            for (i = 0; i < game.maxclients; i++) {
                ent = &g_entities[i];

                if (!ent->inuse || !ent->entity) {
                    continue;
                }

                if (!static_cast<Player *>(ent->entity)->IsSpectator()) {
                    continue;
                }

                gi.SendServerCommand(i, "%s", szPrintString);
            }
        }
    } else if (iMode < 0) {
        //
        // team message
        //

        // Added in OPM
        gi.Printf("%s (%zu) says to team: %s\n", client->pers.netname, edict - g_entities, sToken.c_str());

        if (IsSpectator()) {
            for (i = 0; i < game.maxclients; i++) {
                ent = &g_entities[i];

                if (!ent->inuse || !ent->entity) {
                    continue;
                }

                if (!static_cast<Player *>(ent->entity)->IsSpectator()) {
                    continue;
                }

                gi.SendServerCommand(i, "%s", szPrintString);
            }
        } else {
            for (i = 0; i < game.maxclients; i++) {
                bool bSameTeam;

                ent = &g_entities[i];

                if (!ent->inuse || !ent->entity) {
                    continue;
                }

                bSameTeam = static_cast<Player*>(ent->entity)->GetTeam() == GetTeam();
                if (bSameTeam) {
                    gi.SendServerCommand(i, "%s", szPrintString);
                }

                if (bInstaMessage) {
                    gi.MSG_SetClient(i);
                    gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_VOICE_CHAT));
                    gi.MSG_WriteCoord(m_vViewPos[0]);
                    gi.MSG_WriteCoord(m_vViewPos[1]);
                    gi.MSG_WriteCoord(m_vViewPos[2]);
                    gi.MSG_WriteBits(!bSameTeam, 1);
                    gi.MSG_WriteBits(edict - g_entities, 6);
                    gi.MSG_WriteString(sAliasName.c_str());
                    gi.MSG_EndCGM();
                }
            }
        }
    } else if (iMode <= game.maxclients) {
        ent = &g_entities[iMode - 1];

        if (!ent->inuse || !ent->entity) {
            str errorString  = gi.LV_ConvertString("Message Error");
            str reasonString = gi.LV_ConvertString("is not a connected client");

            gi.SendServerCommand(
                edict - g_entities,
                "print \"" HUD_MESSAGE_CHAT_WHITE "%s: %i %s.\n\"",
                errorString.c_str(),
                iMode,
                reasonString.c_str()
            );
            return;
        }

        // Added in OPM
        gi.Printf("%s (%zu) says to client #%d: %s\n", client->pers.netname, edict - g_entities, iMode - 1, sToken.c_str());

        gi.SendServerCommand(iMode - 1, "%s", szPrintString);

        if (ent->entity != this) {
            gi.SendServerCommand(
                edict - g_entities,
                "print \"" HUD_MESSAGE_CHAT_WHITE "%s %i:\n\"",
                gi.LV_ConvertString("Message to player"),
                iMode
            );
            gi.SendServerCommand(edict - g_entities, "%s", szPrintString);
        }
    } else {
        str errorString  = gi.LV_ConvertString("Message Error");
        str reasonString = gi.LV_ConvertString("is a bad client number");

        gi.SendServerCommand(
            edict - g_entities,
            "print \"" HUD_MESSAGE_CHAT_WHITE "%s: %i %s.\n\"",
            errorString.c_str(),
            iMode,
            reasonString.c_str()
        );
        return;
    }
}

str Player::GetBattleLanguageCondition() const
{
    int healthRatio;

    if (health <= 0) {
        return "dead";
    }

    if (health >= max_health) {
        return "just peachy";
    }

    healthRatio = (health * 5.f) / max_health;
    switch (healthRatio) {
    case 0:
        return "almost dead";
    case 1:
        return "severely wounded";
    case 2:
        return "wounded";
    case 3:
        return "slightly wounded";
    case 4:
    default:
        return "pretty good";
    }
}

str Player::GetBattleLanguageDirection() const
{
    int dir = ((m_vViewAng.y - world->m_fNorth) + 22.5f + 360.f) / 45.f;
    switch (dir % 8) {
    case 0:
        return "North";
    case 1:
        return "North West";
    case 2:
        return "West";
    case 3:
        return "South West";
    case 4:
        return "South";
    case 5:
        return "South East";
    case 6:
        return "East";
    case 7:
        return "North East";
    default:
        return "???";
    }
}

str Player::GetBattleLanguageLocation() const
{
    return gi.CL_LV_ConvertString(level.GetDMLocation(m_vViewPos).c_str());
}

str Player::GetBattleLanguageLocalFolks()
{
    static char buf[256];
    char       *p;
    char       *curP;
    size_t      remaining;
    size_t      length;
    Player     *pPlayer;
    Player     *pFolk;
    gentity_t  *ent;
    int         i;

    remaining = ARRAY_LEN(buf) - 1;
    p         = buf;
    curP      = NULL;
    pFolk     = NULL;

    for (i = 0; i < game.maxclients; i++) {
        ent = &g_entities[i];
        if (!ent->inuse || !ent->entity) {
            continue;
        }

        pPlayer = static_cast<Player *>(ent->entity);
        if (pPlayer != this && pPlayer->GetTeam() == GetTeam() && CanSee(pPlayer, 360, 1600, false)) {
            if (p != buf) {
                if (remaining < 2) {
                    // No more space remaining
                    break;
                }

                Q_strncpyz(p, ", ", sizeof(buf) - (p - buf));
                p += 2;
                curP = p;
                remaining -= 2;
            }

            length = strlen(client->pers.netname);
            if (remaining < length) {
                break;
            }

            Q_strncpyz(p, client->pers.netname, sizeof(buf) - (p - buf));
            p += length;
            remaining -= length;
            pFolk = pPlayer;
        }
    }

    if (curP && remaining >= 2) {
        Q_strncpyz(curP, "and ", sizeof(buf) - (curP - buf));
        Q_strncpyz(curP + strlen(curP), pFolk->client->pers.netname, sizeof(buf) - (curP + strlen(curP) - buf));
    } else if (!pFolk) {
        return "nobody";
    }

    return buf;
}

str Player::GetBattleLanguageWeapon() const
{
    return GetCurrentDMWeaponType().c_str();
}

str Player::GetBattleLanguageDistance() const
{
    Vector  vStart, vEnd;
    Vector  vForward;
    trace_t trace;
    float   dist;

    vStart = m_vViewPos;
    AngleVectors(m_vViewAng, vForward, NULL, NULL);

    vEnd = vStart + vForward * 10240;

    trace = G_Trace(
        vStart,
        vec_zero,
        vec_zero,
        vEnd,
        static_cast<const Entity *>(this),
        MASK_BATTLELANGUAGE,
        qfalse,
        "Player::GetBattleLanguageDistance"
    );

    dist = (vStart - trace.endpos).length();

    if (g_qunits_to_feet->integer) {
        return GetBattleLanguageDistanceFeet(dist);
    } else {
        return GetBattleLanguageDistanceMeters(dist);
    }
}

str Player::GetBattleLanguageDistanceMeters(float dist) const
{
    int meters;

    meters = (int)((dist + 26.f) / 52.f);
    if (meters >= 5) {
        if (meters < 21) {
            meters = 5 * ((meters + 2) / 5);
        } else if (meters < 101) {
            meters = 10 * ((meters + 5) / 10);
        } else {
            meters = 25 * ((meters + 12) / 25);
        }
    }

    return va("%d meters", meters);
}

str Player::GetBattleLanguageDistanceFeet(float dist) const
{
    int ft;

    ft = (int)((dist + 26.f) / 52.f);
    if (ft >= 11) {
        if (ft < 51) {
            ft = 5 * ((ft + 2) / 5);
        } else if (ft < 251) {
            ft = 10 * ((ft + 5) / 10);
        } else {
            ft = 25 * ((ft + 12) / 25);
        }
    }

    return va("%d feet", ft);
}

str Player::GetBattleLanguageTarget() const
{
    Vector  vStart, vEnd;
    Vector  vForward;
    trace_t trace;

    vStart = m_vViewPos;
    AngleVectors(m_vViewAng, vForward, NULL, NULL);

    vEnd = vStart + vForward * 10240;

    trace = G_Trace(
        vStart,
        vec_zero,
        vec_zero,
        vEnd,
        static_cast<const Entity *>(this),
        MASK_BATTLELANGUAGE,
        qfalse,
        "Player::GetBattleLanguageDistance"
    );

    if (!trace.ent) {
        return "something";
    }

    if (!trace.ent->entity || trace.ent->entity == world) {
        return "something";
    }

    if (trace.ent->entity->IsSubclassOfPlayer()) {
        Player *pPlayer = static_cast<Player *>(trace.ent->entity);
        return pPlayer->client->pers.netname;
    }

    if (trace.ent->entity->IsSubclassOfSentient()) {
        return "someone";
    }

    return "something";
}

str Player::TranslateBattleLanguageTokens(const char *string)
{
    str token;
    int type;

    if (!g_chat_expansions->integer) {
        return string;
    }

    if (!string) {
        return str();
    }

    if (string[0] != '$') {
        return string;
    }

    type = string[1];
    if (!type || string[2]) {
        return string;
    }

    switch (type) {
    case 'a':
        token = GetBattleLanguageTarget();
        break;
    case 'c':
        token = GetBattleLanguageCondition();
        break;
    case 'd':
        token = GetBattleLanguageDirection();
        break;
    case 'l':
        token = GetBattleLanguageLocation();
        break;
    case 'n':
        token = GetBattleLanguageLocalFolks();
        break;
    case 'r':
        token = GetBattleLanguageDistance();
        break;
    case 'w':
        token = GetBattleLanguageWeapon();
        break;
    default:
        return string;
    }

    return gi.LV_ConvertString(token);
}

void Player::EventIPrint(Event *ev)
{
    str         sString = ev->GetString(1);
    qboolean    iBold   = qfalse;

    if (ev->NumArgs() > 1) {
        iBold = ev->GetInteger(2);
    }

    if (iBold) {
        gi.SendServerCommand(edict - g_entities, "print \"" HUD_MESSAGE_WHITE "%s\n\"", gi.LV_ConvertString(sString.c_str()));
    } else {
        gi.SendServerCommand(edict - g_entities, "print \"" HUD_MESSAGE_YELLOW "%s\n\"", gi.LV_ConvertString(sString.c_str()));
    }
}

void Player::SetViewangles(Event *ev)
{
    SetViewAngles(ev->GetVector(1));
}

void Player::GetViewangles(Event *ev)
{
    ev->AddVector(GetVAngles());
}

void Player::EventGetUseHeld(Event *ev)
{
    ev->AddInteger((buttons & BUTTON_USE) ? true : false);
}

void Player::EventGetFireHeld(Event *ev)
{
    ev->AddInteger(buttons & (BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT) ? qtrue : qfalse);
}

void Player::EventGetPrimaryFireHeld(Event *ev)
{
    ev->AddInteger(buttons & BUTTON_ATTACKLEFT ? true : false);
}

void Player::EventGetSecondaryFireHeld(Event *ev)
{
    ev->AddInteger(buttons & BUTTON_ATTACKRIGHT ? true : false);
}

void Player::BeginTempSpectator(void)
{
    m_bTempSpectator = true;
    Spectator();
}

void Player::EndSpectator(void)
{
    m_bSpectator     = false;
    m_bTempSpectator = false;

    client->ps.pm_flags &= ~(PMF_SPECTATING | PMF_SPECTATE_FOLLOW);
}

void Player::EventGetReady(Event *ev)
{
    ev->AddInteger(m_bReady);
}

void Player::EventSetReady(Event *ev)
{
    if (m_bReady) {
        return;
    }

    m_bReady = true;
    gi.Printf("%s is ready\n", client->pers.netname);
}

void Player::EventSetNotReady(Event *ev)
{
    if (!m_bReady) {
        return;
    }

    m_bReady = false;
    gi.Printf("%s is not ready\n", client->pers.netname);
}

void Player::EventGetDMTeam(Event *ev)
{
    if (dm_team == TEAM_FREEFORALL) {
        ev->AddConstString(STRING_FREEFORALL);
    } else if (dm_team == TEAM_AXIS) {
        ev->AddConstString(STRING_AXIS);
    } else if (dm_team == TEAM_ALLIES) {
        ev->AddConstString(STRING_ALLIES);
    } else if (dm_team == TEAM_SPECTATOR) {
        ev->AddConstString(STRING_SPECTATOR);
    } else {
        ScriptError("dmteam is invalid in single player");
    }
}

void Player::EventGetNetName(Event *ev)
{
    ev->AddString(client->pers.netname);
}

void Player::EventSetViewModelAnim(Event *ev)
{
    str      anim;
    int      force_restart = 0;
    qboolean bfullanim     = 0;

    anim = ev->GetString(1);

    if (ev->NumArgs() > 1) {
        force_restart = ev->GetInteger(2);
    }

    if (ev->NumArgs() > 2) {
        bfullanim = ev->GetInteger(3);
    }

    ViewModelAnim(anim, force_restart, bfullanim);
}

void Player::FullHeal(Event *ev)
{
    if (IsDead()) {
        if (!ev->IsFromScript()) {
            HUDPrint("TESTING:  Cannot resurrect yourself with the fullheal.\n");
        }
    } else {
        if (!ev->IsFromScript()) {
            HUDPrint("TESTING:  You used the fullheal.\n");
        }

        health = max_health;
    }
}

void Player::RemoveFromVehiclesAndTurretsInternal(void)
{
    if (m_pVehicle) {
        Event *event;

        m_pVehicle->flags &= ~FL_GODMODE;

        event = new Event(EV_Use);
        event->AddEntity(this);
        m_pVehicle->ProcessEvent(event);
    } else if (m_pTurret) {
        m_pTurret->TurretUsed(this);
    }
}

void Player::RemoveFromVehiclesAndTurrets(void)
{
    Weapon *activeWeap = GetActiveWeapon(WEAPON_MAIN);
    if (activeWeap && activeWeap->IsCarryableTurret()) {
        CarryableTurret *pTurret = static_cast<CarryableTurret *>(activeWeap);
        pTurret->DropTurret(NULL);
    }

    if (!m_pVehicle && !m_pTurret) {
        return;
    }

    if (m_pVehicle && m_pVehicle->isLocked()) {
        m_pVehicle->UnLock();

        if (m_pTurret && m_pTurret->IsSubclassOfVehicleTurretGun()) {
            VehicleTurretGun *turret = (VehicleTurretGun *)m_pTurret.Pointer();

            if (turret->isLocked()) {
                turret->UnLock();
                RemoveFromVehiclesAndTurretsInternal();
                turret->Lock();
            } else {
                RemoveFromVehiclesAndTurretsInternal();
            }
        } else {
            RemoveFromVehiclesAndTurretsInternal();
        }

        // the vehicle might have been modified
        if (m_pVehicle) {
            m_pVehicle->Lock();
        }
    } else if (m_pTurret && m_pTurret->IsSubclassOfVehicleTurretGun()) {
        VehicleTurretGun *turret = (VehicleTurretGun *)m_pTurret.Pointer();

        if (turret->isLocked()) {
            turret->UnLock();
            RemoveFromVehiclesAndTurretsInternal();

            // the turret might have been modified
            if (m_pTurret) {
                turret->Lock();
            }
        } else {
            RemoveFromVehiclesAndTurretsInternal();
        }
    } else {
        RemoveFromVehiclesAndTurretsInternal();
    }
}

void Player::EventEnterIntermission(Event *ev)
{
    if (!level.intermissiontime) {
        return;
    }

    if (level.intermissiontype) {
        G_DisplayScores(this);

        if (level.intermissiontype == TRANS_MISSION_FAILED || IsDead()) {
            gi.cvar_set("g_success", "0");
            gi.cvar_set("g_failed", "1");
        } else {
            gi.cvar_set("g_success", "1");
            gi.cvar_set("g_failed", "0");
        }
    } else {
        G_HideScores(this);
    }
}

bool Player::BlocksAIMovement()
{
    return false;
}

void Player::EventSetPerferredWeapon(Event *ev)
{
    m_sPerferredWeaponOverride = ev->GetString(1);
}

void Player::SetMouthAngle(Event *ev)
{
    int    tag_num;
    float  angle_percent;
    Vector mouth_angles;

    angle_percent = ev->GetFloat(1);

    if (angle_percent < 0) {
        angle_percent = 0;
    }

    if (angle_percent > 1) {
        angle_percent = 1;
    }

    tag_num = gi.Tag_NumForName(edict->tiki, "tag_mouth");

    if (tag_num != -1) {
        SetControllerTag(MOUTH_TAG, tag_num);

        mouth_angles        = vec_zero;
        mouth_angles[PITCH] = max_mouth_angle * angle_percent;

        SetControllerAngles(MOUTH_TAG, mouth_angles);
    }
}

int Player::GetMoveResult(void)
{
    return moveresult;
}

qboolean Player::CheckCanSwitchTeam(teamtype_t team)
{
    float startTime;

    startTime = dmManager.GetMatchStartTime();

    if (startTime >= 0.0f && (level.time - startTime) > 30.0
        && (level.time - m_fTeamSelectTime) < g_teamswitchdelay->integer) {
        int seconds = g_teamswitchdelay->integer - (level.time - m_fTeamSelectTime);

        gi.SendServerCommand(
            edict - g_entities,
            "print \"" HUD_MESSAGE_WHITE "%s %i %s\n\"",
            gi.LV_ConvertString("Can not change teams again for another"),
            seconds + 1,
            gi.LV_ConvertString("seconds")
        );
        return qfalse;
    }

    // Added in OPM
    //  Check and prevent joining the team with the highest number of players
    if (g_teambalance->integer && g_gametype->integer >= GT_TEAM && !dmManager.WaitingForPlayers()) {
        DM_Team *pNewTeam = dmManager.GetTeam(team);
        int      i;

        for (i = 0; i < 2; i++) {
            DM_Team *pTeam          = dmManager.GetTeam((teamtype_t)(TEAM_ALLIES + i));
            int      numTeamPlayers = pTeam->m_players.NumObjects();

            if (pTeam->m_players.IndexOfObject(this)) {
                // Don't count the current player
                numTeamPlayers--;
            }

            if (pNewTeam->m_players.NumObjects() > numTeamPlayers) {
                gi.SendServerCommand(
                    edict - g_entities,
                    "print \"" HUD_MESSAGE_WHITE "%s\n\"",
                    gi.LV_ConvertString(
                        "That team has enough players. Choose the team that has the lowest number of players."
                    )
                );
                return qfalse;
            }
        }
    }

    return qtrue;
}

qboolean Player::ViewModelAnim(str anim, qboolean force_restart, qboolean bFullAnim)
{
    Unregister(STRING_VIEWMODELANIM_DONE);

    if (client == NULL) {
        return true;
    }

    int            viewModelAnim;
    playerState_t *playerState = &client->ps;
    Weapon        *weapon;

    if (!anim.length()) {
        anim = "";
    }

    // Copy the item prefix and the anim name
    weapon = GetActiveWeapon(WEAPON_MAIN);

    if (!Q_stricmp(anim, "charge")) {
        viewModelAnim = VM_ANIM_CHARGE;
    } else if (!Q_stricmp(anim, "fire")) {
        viewModelAnim = VM_ANIM_FIRE;
    } else if (!Q_stricmp(anim, "fire_secondary")) {
        viewModelAnim = VM_ANIM_FIRE_SECONDARY;
    } else if (!Q_stricmp(anim, "rechamber")) {
        viewModelAnim = VM_ANIM_RECHAMBER;
    } else if (!Q_stricmp(anim, "reload")) {
        viewModelAnim = VM_ANIM_RELOAD;
    } else if (!Q_stricmp(anim, "reload_single")) {
        viewModelAnim = VM_ANIM_RELOAD_SINGLE;
    } else if (!Q_stricmp(anim, "reload_end")) {
        viewModelAnim = VM_ANIM_RELOAD_END;
    } else if (!Q_stricmp(anim, "pullout")) {
        viewModelAnim = VM_ANIM_PULLOUT;
    } else if (!Q_stricmp(anim, "putaway")) {
        viewModelAnim = VM_ANIM_PUTAWAY;
    } else if (!Q_stricmp(anim, "ladderstep")) {
        viewModelAnim = VM_ANIM_LADDERSTEP;
    } else {
        if (!Q_stricmp(anim, "idle")) {
            viewModelAnim = VM_ANIM_IDLE;
        } else if (!Q_stricmp(anim, "idle0")) {
            viewModelAnim = VM_ANIM_IDLE_0;
        } else if (!Q_stricmp(anim, "idle1")) {
            viewModelAnim = VM_ANIM_IDLE_1;
        } else if (!Q_stricmp(anim, "idle2")) {
            viewModelAnim = VM_ANIM_IDLE_2;
        } else {
            // Defaults to idle
            viewModelAnim = VM_ANIM_IDLE;
        }

        //
        // check the fire movement speed if the weapon has a max fire movement
        //
        if (weapon && weapon->m_fMaxFireMovement < 1) {
            float length;

            length = velocity.length();
            if (length / sv_runspeed->value > ((weapon->m_fMaxFireMovement * weapon->m_fMovementSpeed) + 0.1f)) {
                // Set the view model animation to disabled
                viewModelAnim = VM_ANIM_DISABLED;
            }
        }
    }

    if (!weapon) {
        weapon = newActiveWeapon.weapon;
    }

    if (weapon) {
        m_sVMAcurrent = GetItemPrefix(weapon->getName()) + str("_") + anim;
    } else {
        m_sVMAcurrent = "unarmed_" + anim;
    }

    m_sVMcurrent = anim;

    if (viewModelAnim != playerState->iViewModelAnim || force_restart) {
        playerState->iViewModelAnimChanged = (playerState->iViewModelAnimChanged + 1) & 3;
    }

    playerState->iViewModelAnim = viewModelAnim;

    if (m_fpsTiki && gi.Anim_NumForName(m_fpsTiki, m_sVMAcurrent) < 0) {
        //gi.DPrintf("WARNING: Invalid view model anim \"%s\"\n", m_sVMAcurrent.c_str());
    }

    animDoneVM = false;

    m_fVMAtime = 0;

    return true;
}

void Player::FindAlias(str& output, str name, AliasListNode_t **node)
{
    const char *alias = gi.Alias_FindRandom(edict->tiki, name, node);

    if (alias == NULL) {
        alias = gi.GlobalAlias_FindRandom(name, node);
    }

    if (alias != NULL) {
        output = alias;
    }
}

bool Player::HasVotedYes() const
{
    return voted;
}

bool Player::HasVotedNo() const
{
    return !voted;
}

void Player::TickSprint()
{
    float timeHeld;

    if (last_ucmd.buttons & BUTTON_RUN && last_ucmd.forwardmove) {
        timeHeld = 0;

        if (!m_fLastSprintTime) {
            m_fLastSprintTime = level.time;
        }
    } else {
        timeHeld          = 0;
        m_fLastSprintTime = 0;
    }

    if (last_ucmd.rightmove) {
        m_fLastSprintTime = timeHeld;
    }
    if (last_ucmd.upmove) {
        m_fLastSprintTime = timeHeld;
    }
}

float Player::GetRunSpeed() const
{
    float sprintTime;
    float sprintMult;

    sprintTime = sv_sprinttime->value;
    sprintMult = sv_sprintmult->value;
    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        sprintTime = sv_sprinttime_dm->value;
        sprintMult = sv_sprintmult_dm->value;
    }

    if (sv_sprinton->integer == 1 && m_fLastSprintTime && (level.time - m_fLastSprintTime) > sprintTime) {
        return sv_runspeed->value * sprintMult;
    } else {
        return sv_runspeed->value;
    }
}

void Player::FireWeapon(int number, firemode_t mode)
{
    if (m_pVehicle || m_pTurret) {
        return;
    }

    if (G_GetWeaponCommand(last_ucmd.buttons)) {
        // Added in OPM
        //  If there is a weapon command (like DROP), then just don't fire
        //  this prevent tricky behaviors, like silent firing
        return;
    }

    Sentient::FireWeapon(number, mode);

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        //
        // Make sure to remove the player's invulnerability
        //
        CancelInvulnerable();
    }
}

void Player::SetInvulnerable()
{
    if (IsInvulnerable()) {
        return;
    }

    if (!sv_invulnerabletime->integer) {
        return;
    }

    if (gi.Cvar_Get("g_invulnoverride", "0", 0)->integer == 1) {
        return;
    }

    if (IsDead()) {
        return;
    }

    if (IsSpectator() || GetTeam() == TEAM_SPECTATOR) {
        return;
    }

    //
    // The player can now be invulnerable
    //
    takedamage                   = DAMAGE_NO;
    m_iInvulnerableTimeRemaining = sv_invulnerabletime->integer;
    m_fLastInvulnerableTime      = level.time;
    m_fInvulnerableTimeElapsed   = level.time;

    TickInvulnerable();
}

void Player::TickInvulnerable()
{
    if (m_iInvulnerableTimeRemaining >= 0 && level.time >= m_fInvulnerableTimeElapsed) {
        if (m_iInvulnerableTimeRemaining) {
            m_fInvulnerableTimeElapsed = m_fInvulnerableTimeElapsed + 1.f;
        } else {
            SetVulnerable();
            m_fInvulnerableTimeElapsed = 0;
        }

        m_iInvulnerableTimeRemaining--;
    }
}

void Player::SetVulnerable()
{
    if (IsInvulnerable()) {
        takedamage              = DAMAGE_AIM;
        m_fLastInvulnerableTime = 0;
    }
}

bool Player::IsInvulnerable()
{
    return m_fLastInvulnerableTime != 0;
}

void Player::CancelInvulnerable()
{
    if (IsInvulnerable()) {
        SetVulnerable();
        m_iInvulnerableTimeRemaining = -1;
        gi.centerprintf(edict, " ");
    }
}

void Player::InitInvulnerable()
{
    m_fLastInvulnerableTime      = 0;
    m_iInvulnerableTimeRemaining = -1;
}

void Player::TickTeamSpawn()
{
    int timeLeft;

    if (!IsSpectator() && !IsDead() || (GetTeam() == TEAM_SPECTATOR || !client->pers.dm_primary[0])) {
        return;
    }

    timeLeft = dmManager.GetTeamSpawnTimeLeft();
    if (timeLeft == -1) {
        // Can spawn
        m_fSpawnTimeLeft = 0;
        return;
    }

    if (timeLeft == m_fSpawnTimeLeft) {
        // Still waiting
        return;
    }

    if (m_bShouldRespawn) {
        // The player can spawn
        m_fSpawnTimeLeft = 0;
        return;
    }

    m_fSpawnTimeLeft = timeLeft;
    if (timeLeft) {
        if (AllowTeamRespawn()) {
            const char *string;

            if (timeLeft == 1) {
                string = va("Next respawn in 1 second");
            } else {
                string = va("Next respawn in %d seconds", timeLeft);
            }

            gi.centerprintf(edict, string);
        }
    } else if (m_bWaitingForRespawn && AllowTeamRespawn()) {
        m_bWaitingForRespawn = false;
        m_bDeathSpectator    = false;
        EndSpectator();

        PostEvent(EV_Player_Respawn, 0);
    } else {
        m_bShouldRespawn = true;
    }
}

bool Player::ShouldForceSpectatorOnDeath() const
{
    return dmManager.GetTeamSpawnTimeLeft() > 0;
}

bool Player::HasVehicle() const
{
    return m_pVehicle != NULL;
}

void Player::setContentsSolid()
{
    edict->r.contents = CONTENTS_BODY;
}

void Player::UserSelectWeapon(bool bWait)
{
    nationality_t nationality;
    char          buf[256];

    if (g_protocol < PROTOCOL_MOHTA_MIN) {
        //
        // nationality was first introduced in 2.0
        //
        if (bWait) {
            gi.SendServerCommand(edict - g_entities, "stufftext \"wait 250;pushmenu_weaponselect\"");
        } else {
            gi.SendServerCommand(edict - g_entities, "stufftext \"pushmenu_weaponselect\"");
        }
        return;
    }

    if (GetTeam() == TEAM_AXIS) {
        nationality = GetPlayerAxisTeamType(client->pers.dm_playergermanmodel);
    } else {
        nationality = GetPlayerAlliedTeamType(client->pers.dm_playermodel);
    }

    if (bWait) {
        Q_strncpyz(buf, "stufftext \"wait 250;pushmenu ", sizeof(buf));
    } else {
        Q_strncpyz(buf, "stufftext \"pushmenu ", sizeof(buf));
    }

    if (dmflags->integer & DF_WEAPON_NO_RIFLE && dmflags->integer & DF_WEAPON_NO_SNIPER
        && dmflags->integer & DF_WEAPON_NO_SMG && dmflags->integer & DF_WEAPON_NO_MG
        && dmflags->integer & DF_WEAPON_NO_ROCKET && dmflags->integer & DF_WEAPON_NO_SHOTGUN
        && dmflags->integer & DF_WEAPON_NO_LANDMINE && !QueryLandminesAllowed()) {
        gi.cvar_set("dmflags", va("%i", dmflags->integer & ~DF_WEAPON_NO_RIFLE));
        Com_Printf("No valid weapons -- re-allowing the rifle\n");
        Q_strncpyz(client->pers.dm_primary, "rifle", sizeof(client->pers.dm_primary));
    }

    switch (nationality) {
    case NA_BRITISH:
        Q_strcat(buf, sizeof(buf), "SelectPrimaryWeapon_british\"");
        break;
    case NA_RUSSIAN:
        Q_strcat(buf, sizeof(buf), "SelectPrimaryWeapon_russian\"");
        break;
    case NA_GERMAN:
        Q_strcat(buf, sizeof(buf), "SelectPrimaryWeapon_german\"");
        break;
    case NA_ITALIAN:
        Q_strcat(buf, sizeof(buf), "SelectPrimaryWeapon_italian\"");
        break;
    default:
        Q_strcat(buf, sizeof(buf), "SelectPrimaryWeapon\"");
        break;
    }

    gi.SendServerCommand(edict - g_entities, buf);
}

void Player::PickWeaponEvent(Event *ev)
{
    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }

    UserSelectWeapon(false);
}

bool Player::AllowTeamRespawn() const
{
    if (m_bSpectator && !m_bDeathSpectator
        && (!dmManager.AllowTeamRespawn(TEAM_ALLIES) || !dmManager.AllowTeamRespawn(TEAM_AXIS))) {
        return false;
    }

    if (GetTeam() > TEAM_AXIS || GetTeam() < TEAM_ALLIES) {
        return false;
    }

    return dmManager.AllowTeamRespawn(GetTeam());
}

void Player::EventUseWeaponClass(Event *ev)
{
    if (m_pTurret || level.playerfrozen) {
        return;
    }

    Sentient::EventUseWeaponClass(ev);
}

void Player::EventAddKills(Event *ev)
{
    SafePtr<DM_Team> pTeam = GetDM_Team();
    if (pTeam) {
        // Add kills to the team
        pTeam->AddKills(this, ev->GetInteger(1));
    }
}

bool Player::CanKnockback(float minHealth) const
{
    if (m_pTurret || m_pVehicle) {
        return minHealth >= health;
    } else {
        return 1;
    }
}

void Player::EventKillAxis(Event *ev)
{
    float radius = 0;

    if (ev->NumArgs() >= 1) {
        radius = ev->GetFloat(1);
    }

    for (Sentient *pSent = level.m_HeadSentient[TEAM_GERMAN]; pSent; pSent = pSent->m_NextSentient) {
        if (radius > 0) {
            Vector delta = pSent->origin - origin;
            if (radius < delta.length()) {
                continue;
            }
        }

        pSent->Damage(this, this, pSent->max_health + 25, origin, vec_zero, vec_zero, 0, 0, MOD_NONE);
    }
}

void Player::EventGetTurret(Event *ev)
{
    ev->AddEntity(m_pTurret);
}

void Player::EventGetVehicle(Event *ev)
{
    ev->AddEntity(m_pVehicle);
}

bool Player::IsReady(void) const
{
    return m_bReady && !IsDead();
}

void Player::Spawned(void)
{
    Event *ev = new Event;

    ev->AddEntity(this);
    scriptedEvents[SE_SPAWN].Trigger(ev);
}

void Player::AddKills(int num)
{
    num_kills += num;
}

void Player::AddDeaths(int num)
{
    num_deaths += num;
}

////////////////////////////
//
// Added in OPM
//
////////////////////////////

qboolean Player::canUse()
{
    int touch[MAX_GENTITIES];
    int num = getUseableEntities(touch, MAX_GENTITIES);

    return num ? true : false;
}

qboolean Player::canUse(Entity *entity, bool requiresLookAt)
{
    gentity_t *hit;
    int        touch[MAX_GENTITIES];
    int        num;
    int        i;

    num = getUseableEntities(touch, MAX_GENTITIES, requiresLookAt);

    for (i = 0; i < num; i++) {
        hit = &g_entities[touch[i]];

        if (!hit->inuse || hit->entity == NULL) {
            continue;
        }

        if (hit->entity == entity) {
            return true;
        }
    }

    return false;
}

int Player::getUseableEntities(int *touch, int maxcount, bool requiresLookAt)
{
    Vector  end;
    Vector  start;
    trace_t trace;
    Vector  offset;
    Vector  max;
    Vector  min;

    if ((g_gametype->integer != GT_SINGLE_PLAYER && IsSpectator()) || IsDead()) {
        return 0;
    }

    if (m_pTurret) {
        *touch = m_pTurret->entnum;
        return 1;
    }

    if (m_pTurret) {
        return 0;
    }

    AngleVectors(client->ps.viewangles, offset, NULL, NULL);

    start = origin;
    start.z += client->ps.viewheight;

    if (requiresLookAt) {
        min = Vector(-4.f, -4.f, -4.f);
        max = Vector(4.f, 4.f, 4.f);

        end[0] = start[0] + (offset[0] * 64.f);
        end[1] = start[1] + (offset[1] * 64.f);

        if (v_angle[0] <= 0.0f) {
            end[2] = start[2] + (offset[2] * 40.f);
        } else {
            end[2] = start[2] + (offset[2] * 88.f);
        }

        trace = G_Trace(start, min, max, end, this, MASK_USE, false, "Player::getUseableEntity");

        offset = trace.endpos;

        min = offset - Vector(16.f, 16.f, 16.f);
        max = offset + Vector(16.f, 16.f, 16.f);
    } else {
        min = start - Vector(31.f, 31.f, 31.f);
        max = start + Vector(31.f, 31.f, 31.f);
    }

    return gi.AreaEntities(min, max, touch, maxcount);
}

void Player::Postthink(void)
{
    if (bindmaster) {
        SetViewAngles(GetViewAngles() + Vector(0, bindmaster->avelocity[YAW] * level.frametime, 0));
    }
}

void Player::AdminRights(Event *ev)
{
    // FIXME: Admin manager ?
    ev->AddInteger(0);
    UNIMPLEMENTED();
}

void Player::IsAdmin(Event *ev)
{
    // FIXME: Admin manager ?
    ev->AddInteger(0);
    UNIMPLEMENTED();
}

void Player::BindWeap(Event *ev)
{
    Entity   *ent = ev->GetEntity(1);
    Listener *scriptOwner;

    //
    // FIXME: deprecate and use something else instead
    //  like implement this in the Item class directly
    //  this is dangerous to use especially if the weapon
    //  is a VehicleTurretGun, could easily mess up the camera

    if (ent) {
        scriptOwner = ent->GetScriptOwner();

        if (scriptOwner != this) {
            ent->SetScriptOwner(this);
        } else {
            ent->SetScriptOwner(NULL);
        }
    }
}

void Player::AddDeaths(Event *ev)
{
    AddDeaths(ev->GetInteger(1));
}

void Player::Dive(Event *ev)
{
    float height, airborne_duration, speed;

    Vector forwardvector = orientation[0];

    height = ev->GetFloat(1);

    if (ev->NumArgs() < 2 || ev->IsNilAt(2)) {
        airborne_duration = 1;
    } else {
        airborne_duration = ev->GetFloat(2);
    }

    speed = height * airborne_duration;

    velocity[0] += height * forwardvector[0] * (speed / 16);
    velocity[1] += height * forwardvector[1] * (speed / 16);
    velocity[2] += height * speed / 6.80f;
}

void Player::EventSetTeam(Event *ev)
{
    str        team_name;
    teamtype_t teamType;

    team_name = ev->GetString(1);
    if (!team_name.length()) {
        ScriptError("Invalid team name !");
        return;
    }

    if (Q_stricmp(team_name, "none") == 0) {
        teamType = TEAM_NONE;
    } else if (Q_stricmp(team_name, "spectator") == 0) {
        teamType = TEAM_SPECTATOR;
    } else if (Q_stricmp(team_name, "freeforall") == 0) {
        teamType = TEAM_FREEFORALL;
    } else if (Q_stricmp(team_name, "allies") == 0) {
        teamType = TEAM_ALLIES;
    } else if (Q_stricmp(team_name, "axis") == 0) {
        teamType = TEAM_AXIS;
    } else {
        ScriptError("Unknown team name \"%s\"\n", team_name.c_str());
        return;
    }

    SetTeam(teamType);

    gi.DPrintf("Player::SetTeam : Player is now on team \"%s\"\n", team_name.c_str());
}

void Player::EventGetViewModelAnim(Event *ev)
{
    ev->AddString(m_sVMcurrent);
}

void Player::EventGetViewModelAnimFinished(Event *ev)
{
    ev->AddInteger(animDoneVM);
}

void Player::EventGetViewModelAnimValid(Event *ev)
{
    str  anim_name = ev->GetString(1);
    str  fullanim;
    bool bFullAnim = false;

    if (ev->NumArgs() > 1) {
        bFullAnim = ev->GetBoolean(2);
    }

    if (!bFullAnim) {
        // Copy the item prefix and the anim name
        Item *item = GetActiveWeapon(WEAPON_MAIN);

        if (!item) {
            item = (Item *)newActiveWeapon.weapon.Pointer();
        }

        if (item) {
            fullanim = GetItemPrefix(item->getName()) + str("_") + anim_name;
        } else {
            fullanim = "unarmed_" + anim_name;
        }
    } else {
        fullanim = anim_name;
    }

    if (!m_fpsTiki || gi.Anim_NumForName(m_fpsTiki, fullanim.c_str()) < 0) {
        ev->AddInteger(0);
    } else {
        ev->AddInteger(1);
    }
}

void Player::FreezeControls(Event *ev)
{
    m_bFrozen = ev->GetBoolean(1);
}

void Player::GetConnState(Event *ev)
{
    // Assume CS_ACTIVE
    ev->AddInteger(4);

    gi.DPrintf(
        "getconnstate is deprecated and will always return 4 (CS_ACTIVE).\nThe player is created only when the client "
        "begins (CS_ACTIVE state).\n"
    );
}

void Player::GetDamageMultiplier(Event *ev)
{
    ev->AddFloat(damage_multiplier);
}

void Player::GetDeaths(Event *ev)
{
    ev->AddInteger(num_deaths);
}

void Player::GetKillHandler(Event *ev)
{
    if (m_killedLabel.IsSet()) {
        m_killedLabel.GetScriptValue(&ev->GetValue());
    } else {
        ev->AddNil();
    }
}

void Player::GetKills(Event *ev)
{
    ev->AddInteger(num_kills);
}

void Player::GetMoveSpeedScale(Event *ev)
{
    ev->AddFloat(speed_multiplier[0]);
}

void Player::GetLegsState(Event *ev)
{
    const char *name;

    if (currentState_Legs != NULL) {
        name = currentState_Legs->getName();
    } else {
        name = "none";
    }

    ev->AddString(name);
}

void Player::GetStateFile(Event *ev)
{
    int clientNum = G_GetClientNumber(this);

    if (m_sStateFile.length()) {
        ev->AddString(m_sStateFile);
    } else {
        ev->AddString(g_statefile->string);
    }
}

void Player::GetTorsoState(Event *ev)
{
    const char *name;

    if (currentState_Torso != NULL) {
        name = currentState_Torso->getName();
    } else {
        name = "none";
    }

    ev->AddString(name);
}

void Player::HideEntity(Event *ev)
{
    // FIXME: todo
    UNIMPLEMENTED();
}

void Player::ShowEntity(Event *ev)
{
    // FIXME: REDO
    UNIMPLEMENTED();
}

void Player::Inventory(Event *ev)
{
    Entity         *ent = NULL;
    ScriptVariable *ref = new ScriptVariable, *array = new ScriptVariable;
    int             i = 0;

    ref->setRefValue(array);

    for (i = 0; i < inventory.NumObjects(); i++) {
        ent = G_GetEntity(inventory[i]);

        if (ent == NULL) {
            continue;
        }

        ScriptVariable *index = new ScriptVariable, *value = new ScriptVariable;

        index->setIntValue(i + 1);
        value->setListenerValue((Listener *)ent);

        ref->setArrayAt(*index, *value);
    }

    ev->AddValue(*array);
}

void Player::InventorySet(Event *ev)
{
    ScriptVariable  array;
    ScriptVariable *value;
    Entity         *ent;
    int             arraysize;

    if (ev->IsNilAt(1)) {
        // Just clear the inventory
        inventory.ClearObjectList();
        return;
    }

    // Retrieve the array
    array = ev->GetValue(1);

    // Cast the array
    array.CastConstArrayValue();
    arraysize = array.arraysize();

    // Detach all active weapons and free the inventory

    if (inventory.NumObjects() > 0) {
        inventory.FreeObjectList();
    }

    if (arraysize < 1) {
        return;
    }

    // Allocate an inventory
    for (int i = 1; i <= arraysize; i++) {
        // Retrieve the value from the array
        value = array[i];

        // Get the entity from the value
        ent = (Entity *)value->entityValue();

        if (ent == NULL || !ent->edict->inuse) {
            continue;
        }

        // Add the entity to the inventory
        inventory.AddObject(ent->entnum);
    }

    // Clear the variable
    array.Clear();
}

void Player::LeanLeftHeld(Event *ev)
{
    Player *player     = NULL;
    int     buttonheld = 0;

    player = (Player *)this;

    buttonheld = !!(player->buttons & BUTTON_LEAN_LEFT);

    ev->AddInteger(buttonheld);
}

void Player::LeanRightHeld(Event *ev)
{
    Player *player     = NULL;
    int     buttonheld = 0;

    player = (Player *)this;

    buttonheld = !!(player->buttons & BUTTON_LEAN_RIGHT);

    ev->AddInteger(buttonheld);
}

void Player::PlayLocalSound(Event *ev)
{
    str      soundName = ev->GetString(1);
    qboolean loop      = false;
    float    time;

    if (ev->NumArgs() > 1) {
        loop = ev->GetBoolean(2);
    }

    if (ev->NumArgs() > 2) {
        time = ev->GetFloat(3);
    } else {
        time = 0.0f;
    }

    AliasListNode_t *alias = NULL;

    const char *found = gi.GlobalAlias_FindRandom(soundName, &alias);

    if (found == NULL) {
        gi.DPrintf("ERROR: Player::PlayLocalSound: %s needs to be aliased - Please fix.\n", soundName.c_str());
        return;
    }

#ifdef OPM_FEATURES
    gi.MSG_SetClient(client->ps.clientNum);

    gi.MSG_StartCGM(CGM_PLAYLOCALSOUND);
    gi.MSG_WriteString(found);
    gi.MSG_WriteBits(!!loop, 1);
    gi.MSG_WriteFloat(time);
    gi.MSG_WriteFloat(alias->volume);
    gi.MSG_EndCGM();

    return;
#endif

    if (loop) {
        edict->s.loopSound        = gi.soundindex(found, alias->streamed);
        edict->s.loopSoundVolume  = 1.0f;
        edict->s.loopSoundMinDist = 0;
        edict->s.loopSoundMaxDist = 96;
        edict->s.loopSoundPitch   = 1.0f;
        edict->s.loopSoundFlags   = 1; // local sound
    } else {
        gi.Sound(&edict->s.origin, entnum, CHAN_LOCAL, found, 1.0f, 0, 1.0f, 96, alias->streamed);
    }
}

void Player::RunHeld(Event *ev)
{
    Player *player     = NULL;
    int     buttonheld = 0;

    player = (Player *)this;

    buttonheld = !!(player->buttons & BUTTON_RUN);

    ev->AddInteger(buttonheld);
}

void Player::SecFireHeld(Event *ev)
{
    Player *player     = NULL;
    int     buttonheld = 0;

    player = (Player *)this;

    buttonheld = !!(player->buttons & BUTTON_ATTACKRIGHT);

    ev->AddInteger(buttonheld);
}

void Player::SetAnimSpeed(Event *ev)
{
    float   speed;
    Player *player = (Player *)this;

    speed = ev->GetFloat(1);

    if (speed < 0.0f) {
        speed = 0.0f;
    }

    UNIMPLEMENTED();
}

void Player::SetKillHandler(Event *ev)
{
    if (ev->IsNilAt(1) || (ev->IsStringAt(1) && !ev->GetString(1).icmp("none"))) {
        m_killedLabel.Clear();
    } else {
        m_killedLabel.SetScript(ev->GetValue(1));
    }
}

void Player::SetSpeed(Event *ev)
{
    float   speed;
    Player *player    = (Player *)this;
    int     clientNum = G_GetClientNumber(this);
    int     index     = 0;

    speed = ev->GetFloat(1);

    if (speed < 0.0f) {
        speed = 0.0f;
    }

    if (ev->NumArgs() > 1) {
        index = ev->GetInteger(2);

        /* Reserve a space for moveSpeedScale */
        if (index < 1 || index > MAX_SPEED_MULTIPLIERS) {
            gi.Printf("Player::SetSpeed : invalid index %d. Index must be between 1-%d\n", index, speed_multiplier);

            return;
        }
    }

    speed_multiplier[index] = speed;
}

void Player::SetStateFile(Event *ev)
{
    int      clientNum = G_GetClientNumber(this);
    qboolean bRemove   = false;
    str      string;

    if (ev->NumArgs() <= 0) {
        bRemove = true;
    } else {
        string = ev->GetString(1);

        if (!string) {
            bRemove = true;
        }
    }

    if (bRemove) {
        m_sStateFile = "";
    } else {
        m_sStateFile = string;
    }
}

void Player::StopLocalSound(Event *ev)
{
    str   soundName = ev->GetString(1);
    float time;

    if (ev->NumArgs() > 1) {
        time = ev->GetFloat(2);
    } else {
        time = 0.0f;
    }

    AliasListNode_t *alias = NULL;
    const char      *found = gi.GlobalAlias_FindRandom(soundName, &alias);

    if (found == NULL) {
        gi.DPrintf("ERROR: Player::StopLocalSound: %s needs to be aliased - Please fix.\n", soundName.c_str());
        return;
    }

    edict->s.loopSound = 0;
    gi.StopSound(entnum, CHAN_LOCAL);
}

void Player::Userinfo(Event *ev)
{
    if (!client) {
        ScriptError("Entity is probably not of player type - userinfo\n");
        return;
    }

    ev->AddString(client->pers.userinfo);
}

int Player::GetNumKills(void) const
{
    return num_kills;
}

int Player::GetNumDeaths(void) const
{
    return num_deaths;
}

#ifdef OPM_FEATURES

void Player::EventEarthquake(Event *ev)
{
    float    duration    = ev->GetFloat(1);
    float    magnitude   = ev->GetFloat(2);
    qboolean no_rampup   = ev->GetBoolean(3);
    qboolean no_rampdown = ev->GetBoolean(4);

    // full realistic, smooth earthquake
    if (ev->NumArgs() > 4) {
        Vector location = ev->GetVector(5);
        float  radius   = 1.0f;

        if (ev->NumArgs() > 5) {
            radius = ev->GetFloat(6);
        }

        gi.SendServerCommand(
            edict - g_entities,
            "eq %f %f %d %d %f %f %f %f",
            duration,
            magnitude,
            no_rampup,
            no_rampdown,
            location[0],
            location[1],
            location[2],
            radius
        );
    } else {
        gi.SendServerCommand(edict - g_entities, "eq %f %f %d %d", duration, magnitude, no_rampup, no_rampdown);
    }
}

void Player::SetClientFlag(Event *ev)
{
    str name = ev->GetString(1);

    gi.SendServerCommand(client->ps.clientNum, "cf %s", name.c_str());
}

void Player::SetEntityShader(Event *ev)
{
    Entity  *entity     = ev->GetEntity(1);
    str      shadername = ev->GetString(2);
    qboolean fReset     = false;

    if (entity == NULL) {
        ScriptError("Invalid entity !");
    }

    if (!shadername.length()) {
        shadername = "default";
        fReset     = true;
    }

    gi.SendServerCommand(edict - g_entities, "setshader %d %s %d", entity->entnum, shadername.c_str(), fReset);
}

void Player::SetLocalSoundRate(Event *ev)
{
    str   name = ev->GetString(1);
    float rate = ev->GetFloat(2);
    float time;

    if (ev->NumArgs() > 2) {
        time = ev->GetFloat(3);
    } else {
        time = 0.0f;
    }

    AliasListNode_t *alias = NULL;
    const char      *found = gi.GlobalAlias_FindRandom(name, &alias);

    if (found == NULL) {
        gi.DPrintf("ERROR: Player::SetLocalSoundRate: %s needs to be aliased - Please fix.\n", name.c_str());
        return;
    }

    gi.MSG_SetClient(client->ps.clientNum);

    // FIXME...
    /*
    gi.MSG_StartCGM( CGM_SETLOCALSOUNDRATE );
        gi.MSG_WriteString( found );
        gi.MSG_WriteFloat( rate );
        gi.MSG_WriteFloat( time );
    gi.MSG_EndCGM();
    */
}

void Player::SetVMASpeed(Event *ev)
{
    str   name  = ev->GetString(1);
    float speed = ev->GetFloat(2);

    if (!client || !sv_specialgame->integer) {
        return;
    }

    vma_t *vma = &vmalist[name];

    if (speed < 0.0f) {
        speed = 0.0f;
    }

    vma->name  = name;
    vma->speed = speed;

    // FIXME...
    /*
    gi.MSG_SetClient( edict - g_entities );

    gi.MSG_StartCGM( CGM_SETVMASPEED );
        gi.MSG_WriteString( name );
        gi.MSG_WriteFloat( speed );
    gi.MSG_EndCGM();
    */
}

void Player::VisionGetNaked(Event *ev)
{
    // return the global vision
    if (!m_sVision.length()) {
        ev->AddString(vision_current);
    } else {
        ev->AddString(m_sVision);
    }
}

void Player::VisionSetBlur(Event *ev)
{
    float blur_level = ev->GetFloat(1);
    float fade_time;

    if (ev->NumArgs() > 1) {
        fade_time = ev->GetFloat(2);
    } else {
        fade_time = 0.0f;
    }

    gi.SendServerCommand(edict - g_entities, "vsb %f %f", blur_level, fade_time);
}

void Player::VisionSetNaked(Event *ev)
{
    str   vision = ev->GetString(1);
    float fade_time;
    float phase;

    if (ev->NumArgs() > 1) {
        fade_time = ev->GetFloat(2);
    } else {
        fade_time = 0.0f;
    }

    if (ev->NumArgs() > 2) {
        phase = ev->GetFloat(3);
    } else {
        phase = 0.0f;
    }

    if (!vision.length()) {
        vision = vision_current;
    }

    if (vision.length() >= MAX_STRING_TOKENS) {
        ScriptError("vision_name exceeds the maximum vision name limit (256) !\n");
    }

    m_sVision = vision;

    gi.SendServerCommand(edict - g_entities, "vsn %s %f %f", vision.c_str(), fade_time, phase);
}
#endif
