/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * Copyright (C) 2006-2010 - Frictional Games
 *
 * This file is part of Penumbra Overture.
 */

#include "hpl1/penumbra-overture/Player.h"

#include "hpl1/penumbra-overture/GameEntity.h"
#include "hpl1/penumbra-overture/Init.h"
#include "hpl1/penumbra-overture/MapHandler.h"
#include "hpl1/penumbra-overture/PlayerHelper.h"
#include "hpl1/penumbra-overture/PlayerMoveStates.h"

#include "hpl1/penumbra-overture/PlayerState_InteractHaptX.h"
#include "hpl1/penumbra-overture/PlayerState_MiscHaptX.h"
#include "hpl1/penumbra-overture/PlayerState_WeaponHaptX.h"

#include "hpl1/penumbra-overture/PlayerState_Interact.h"
#include "hpl1/penumbra-overture/PlayerState_Misc.h"
#include "hpl1/penumbra-overture/PlayerState_Weapon.h"

#include "hpl1/penumbra-overture/ButtonHandler.h"
#include "hpl1/penumbra-overture/EffectHandler.h"
#include "hpl1/penumbra-overture/Inventory.h"
#include "hpl1/penumbra-overture/Notebook.h"
#include "hpl1/penumbra-overture/SaveHandler.h"

#include "hpl1/penumbra-overture/HapticGameCamera.h"

#include "hpl1/penumbra-overture/TriggerHandler.h"
#include "hpl1/penumbra-overture/Triggers.h"

#include "hpl1/penumbra-overture/GlobalInit.h"

//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

cPlayer::cPlayer(cInit *apInit) : iUpdateable("Player") {
	mpInit = apInit;

	mpScene = apInit->mpGame->GetScene();
	mpGraphics = apInit->mpGame->GetGraphics();
	mpGfxDrawer = mpGraphics->GetDrawer();
	mpResources = apInit->mpGame->GetResources();

	// Create and setup camera
	mpCamera = mpScene->CreateCamera3D(eCameraMoveMode_Walk);
	mpScene->SetCamera(mpCamera);

	// Get Debug variables
	mbShowHealth = mpInit->mpConfig->GetBool("Debug", "ShowHealth", false);
	mbShowSoundsPlaying = mpInit->mpConfig->GetBool("Debug", "ShowSoundsPlaying", false);

	mvSize.x = mpInit->mpGameConfig->GetFloat("Player", "Width", 1);
	mvSize.y = mpInit->mpGameConfig->GetFloat("Player", "Height", 1);
	mvSize.z = mvSize.x;

	mpPushBody = nullptr;

	mfCameraHeightAdd = mpInit->mpGameConfig->GetFloat("Player", "CameraHeightAdd", 0);

	mfDefaultMass = mpInit->mpGameConfig->GetFloat("Player", "Mass", 1);
	mfMass = mfDefaultMass;

	mfCrouchHeight = mpInit->mpGameConfig->GetFloat("Player", "CrouchHeight", 1);
	;

	mfSpeedMul = 1.0f;

	mfHeadMoveSizeMul = 1.0f;
	mfHeadMoveSpeedMul = 1.0f;

	mItemFlash.SetUp(0.3f, 1, 0, 1, 1);

	// jump properties
	mbJumpButtonDown = false;
	mfJumpCount = 0;
	mfMaxJumpCount = mpInit->mpGameConfig->GetFloat("Player", "MaxJumpCount", 1);

	// Create and init player states
	mState = ePlayerState_Normal;

	mvStates.resize(ePlayerState_LastEnum);

	if (mpInit->mbHasHaptics) {
		mvStates[ePlayerState_Normal] = hplNew(cPlayerState_NormalHaptX, (mpInit, this));
		mvStates[ePlayerState_Push] = hplNew(cPlayerState_PushHaptX, (mpInit, this));
		mvStates[ePlayerState_Move] = hplNew(cPlayerState_MoveHaptX, (mpInit, this));
		mvStates[ePlayerState_InteractMode] = hplNew(cPlayerState_InteractModeHaptX, (mpInit, this));
		mvStates[ePlayerState_Grab] = hplNew(cPlayerState_GrabHaptX, (mpInit, this));
		mvStates[ePlayerState_WeaponMelee] = hplNew(cPlayerState_WeaponMeleeHaptX, (mpInit, this));
		mvStates[ePlayerState_UseItem] = hplNew(cPlayerState_UseItemHaptX, (mpInit, this));
		mvStates[ePlayerState_Message] = hplNew(cPlayerState_MessageHaptX, (mpInit, this));
		mvStates[ePlayerState_Throw] = hplNew(cPlayerState_ThrowHaptX, (mpInit, this));
		mvStates[ePlayerState_Climb] = hplNew(cPlayerState_ClimbHaptX, (mpInit, this));
	} else {
		mvStates[ePlayerState_Normal] = hplNew(cPlayerState_Normal, (mpInit, this));
		mvStates[ePlayerState_Push] = hplNew(cPlayerState_Push, (mpInit, this));
		mvStates[ePlayerState_Move] = hplNew(cPlayerState_Move, (mpInit, this));
		mvStates[ePlayerState_InteractMode] = hplNew(cPlayerState_InteractMode, (mpInit, this));
		mvStates[ePlayerState_Grab] = hplNew(cPlayerState_Grab, (mpInit, this));
		mvStates[ePlayerState_WeaponMelee] = hplNew(cPlayerState_WeaponMelee, (mpInit, this));
		mvStates[ePlayerState_UseItem] = hplNew(cPlayerState_UseItem, (mpInit, this));
		mvStates[ePlayerState_Message] = hplNew(cPlayerState_Message, (mpInit, this));
		mvStates[ePlayerState_Throw] = hplNew(cPlayerState_Throw, (mpInit, this));
		mvStates[ePlayerState_Climb] = hplNew(cPlayerState_Climb, (mpInit, this));
	}

	// The max distance you can be from something to grab it.
	mfMaxGrabDist = mpInit->mpGameConfig->GetFloat("Player", "MaxGrabDist", 0);

	// The max distance you can be from something to move it.
	mfMaxMoveDist = mpInit->mpGameConfig->GetFloat("Player", "MaxMoveDist", 0);

	// The max distance you can be from something to push it.
	mfMaxPushDist = mpInit->mpGameConfig->GetFloat("Player", "MaxPushDist", 0);
	// The maximum speed you can push something with
	mfMaxPushSpeed = mpInit->mpGameConfig->GetFloat("Player", "MaxPushSpeed", 0);
	mvMaxPushHeadMovement = cVector2f(cMath::ToRad(5), cMath::ToRad(15));
	mvMinPushHeadMovement = cVector2f(cMath::ToRad(-5), cMath::ToRad(-10));

	// This is the maximum distance on which an item can be used.
	mfMaxUseItemDist = mpInit->mpGameConfig->GetFloat("Player", "MaxUseItemDist", 0);

	// Set the maximum time the jumpbutton can be held and make the jump longer.
	mfJumpCount = 0;
	mfMaxJumpCount = mpInit->mpGameConfig->GetFloat("Player", "MaxJumpCount", 1);

	// The border that decides when the mouse moves the screen in interact mode.
	mvInteractMoveBorder = cVector2f(130, 95);

	// Create head movement
	mpHeadMove = hplNew(cPlayerHeadMove, (this));

	// create damage effect
	mpDamage = hplNew(cPlayerDamage, (mpInit));

	// Create death effect
	mpDeath = hplNew(cPlayerDeath, (mpInit));

	// Create flashlight
	mpFlashLight = hplNew(cPlayerFlashLight, (mpInit));

	// Create Glowstick
	mpGlowStick = hplNew(cPlayerGlowStick, (mpInit));

	// Create Flare
	mpFlare = hplNew(cPlayerFlare, (mpInit));

	// Create leaner
	mpLean = hplNew(cPlayerLean, (mpInit, this));

	// Create ear ringer
	mpEarRing = hplNew(cPlayerEarRing, (mpInit, this));

	// Health
	mpHealth = hplNew(cPlayerHealth, (mpInit));

	// NOise Filter
	mpNoiseFilter = hplNew(cPlayerNoiseFilter, (mpInit));

	// Fear Filter
	mpFearFilter = hplNew(cPlayerFearFilter, (mpInit));

	// Look at
	mpLookAt = hplNew(cPlayerLookAt, (this));

	// Hidden
	mpHidden = hplNew(cPlayerHidden, (mpInit));

	// Create ray callbacks
	mpGroundRayCallback = hplNew(cPlayerGroundRayCallback, ());
	mpPickRayCallback = hplNew(cPlayerPickRayCallback, ());

	// Create body callback
	mpBodyCallback = hplNew(cPlayerBodyCallback, (this));

	// Load font
	mpFont = mpResources->GetFontManager()->CreateFontData("verdana.fnt");

	// Create and init move states
	// This must be called after head move is created!
	mMoveState = ePlayerMoveState_Walk;

	mvMoveStates.resize(ePlayerMoveState_LastEnum);
	mvMoveStates[ePlayerMoveState_Walk] = hplNew(cPlayerMoveState_Walk, (this, mpInit));
	mvMoveStates[ePlayerMoveState_Run] = hplNew(cPlayerMoveState_Run, (this, mpInit));
	mvMoveStates[ePlayerMoveState_Still] = hplNew(cPlayerMoveState_Still, (this, mpInit));
	mvMoveStates[ePlayerMoveState_Jump] = hplNew(cPlayerMoveState_Jump, (this, mpInit));
	mvMoveStates[ePlayerMoveState_Crouch] = hplNew(cPlayerMoveState_Crouch, (this, mpInit));

	/////////////////////////
	// Create player gui stuff

	// Cross hair
	mCrossHairState = eCrossHairState_None;
	mvCrossHairPos = cVector2f(400, 300);

	mvCrossHairs.resize(eCrossHairState_LastEnum);

	for (size_t i = 0; i < mvCrossHairs.size(); i++)
		mvCrossHairs[i] = NULL;

	mvCrossHairs[eCrossHairState_Inactive] = mpGfxDrawer->CreateGfxObject("player_crosshair_inactive", "diffalpha2d");
	mvCrossHairs[eCrossHairState_Active] = mpGfxDrawer->CreateGfxObject("player_crosshair_active", "diffalpha2d");
	mvCrossHairs[eCrossHairState_Invalid] = mpGfxDrawer->CreateGfxObject("player_crosshair_invalid", "diffalpha2d");
	mvCrossHairs[eCrossHairState_Grab] = mpGfxDrawer->CreateGfxObject("player_crosshair_grab", "diffalpha2d");
	mvCrossHairs[eCrossHairState_Examine] = mpGfxDrawer->CreateGfxObject("player_crosshair_examine", "diffalpha2d");
	mvCrossHairs[eCrossHairState_Pointer] = mpGfxDrawer->CreateGfxObject("player_crosshair_pointer", "diffalpha2d");
	mvCrossHairs[eCrossHairState_DoorLink] = mpGfxDrawer->CreateGfxObject("player_crosshair_doorlink", "diffalpha2d");
	mvCrossHairs[eCrossHairState_PickUp] = mpGfxDrawer->CreateGfxObject("player_crosshair_pickup", "diffalpha2d");
	mvCrossHairs[eCrossHairState_Ladder] = mpGfxDrawer->CreateGfxObject("player_crosshair_ladder", "diffalpha2d");
	mvCrossHairs[eCrossHairState_Cross] = mpGfxDrawer->CreateGfxObject("player_crosshair_cross", "diffalpha2d");

	///////////////////////////////
	// Haptic Init
	if (mpInit->mbHasHaptics) {
		mpLowLevelHaptic = mpInit->mpGame->GetHaptic()->GetLowLevel();

		mpLowLevelHaptic->SetCamera(mpCamera);
		mpLowLevelHaptic->SetCameraOffset(cVector3f(0, 0.7f, mpInit->mfHapticOffsetZ));

		mpHapticCamera = hplNew(cHapticGameCamera, (mpInit, this));

		mpDamageForce = mpInit->mpGame->GetHaptic()->GetLowLevel()->CreateSinusWaveForce(
			cVector3f(0, 1, 0), 0.1f, 3);

		mpDamageDirForce = mpLowLevelHaptic->CreateImpulseForce(0);
		mpDamageDirForce->SetActive(false);

		mpHapticCamera->SetInteractModeCameraSpeed(mpInit->mpConfig->GetFloat("Haptics", "InteractModeCameraSpeed", 0.5f));
		mpHapticCamera->SetActionModeCameraSpeed(mpInit->mpConfig->GetFloat("Haptics", "ActionModeCameraSpeed", 1.0f));
	}

	// Set up variable values
	Reset();
}

//-----------------------------------------------------------------------

cPlayer::~cPlayer(void) {
	if (mpInit->mbHasHaptics)
		hplDelete(mpHapticCamera);
	hplDelete(mpGroundRayCallback);
	hplDelete(mpPickRayCallback);
	hplDelete(mpHeadMove);
	hplDelete(mpBodyCallback);
	hplDelete(mpDamage);
	hplDelete(mpDeath);
	hplDelete(mpFlashLight);
	hplDelete(mpLean);
	hplDelete(mpEarRing);
	hplDelete(mpGlowStick);
	hplDelete(mpFlare);
	hplDelete(mpHealth);
	hplDelete(mpNoiseFilter);
	hplDelete(mpFearFilter);
	hplDelete(mpLookAt);
	hplDelete(mpHidden);

	/*mpInit->mpConfig->SetFloat("Game","PlayerWidth",mvSize.x);
	mpInit->mpConfig->SetFloat("Game","PlayerHeight",mvSize.y);
	mpInit->mpConfig->SetFloat("Game","PlayerCrouchHeight",mfCrouchHeight);*/

	mpInit->mpConfig->SetBool("Debug", "ShowHealth", mbShowHealth);
	mpInit->mpConfig->SetBool("Debug", "ShowSoundsPlaying", mbShowSoundsPlaying);

	STLDeleteAll(mvMoveStates);
	STLDeleteAll(mvStates);

	for (size_t i = 0; i < mvCrossHairs.size(); i++)
		if (mvCrossHairs[i])
			mpGfxDrawer->DestroyGfxObject(mvCrossHairs[i]);

	STLMapDeleteAll(m_mapCollideCallbacks);
}

//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS - PROPERTIES
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cPlayer::SetMass(float afX) {
	mfMass = afX;
	if (mpCharBody)
		mpCharBody->SetMass(afX);
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS - ACTION
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cPlayer::SetActive(bool abActive) {
	mbActive = abActive;

	if (mbActive == false) {
		ChangeState(ePlayerState_Normal);
	}
}

//-----------------------------------------------------------------------

void cPlayer::SetPower(float afX) {
	mfPower = afX;
}

void cPlayer::AddPower(float afX) {
	mfPower += afX;
	if (mfPower > 100)
		mfPower = 100;
	if (mfPower < 0)
		mfPower = 0;
}

//-----------------------------------------------------------------------

void cPlayer::SetPrevMoveState(ePlayerMoveState aState) {
	mvMoveStates[mMoveState]->mPrevMoveState = aState;
}

//-----------------------------------------------------------------------

void cPlayer::SetStartPos(const tString &asName) {
	ChangeState(ePlayerState_Normal);

	cWorld3D *pWorld = mpInit->mpGame->GetScene()->GetWorld3D();
	if (pWorld) {
		cStartPosEntity *pStart = pWorld->GetStartPosEntity(asName);
		if (pStart == NULL) {
			Warning("Couldn't find start position '%s'\n", asName.c_str());

			pStart = pWorld->GetFirstStartPosEntity();
		}

		cVector3f vPosition(0, 0, 0), vCamRotation(0, 0, 0), vBodyRotation(0, 0, 0);

		if (pStart) {
			vPosition = pStart->GetWorldMatrix().GetTranslation();

			cMatrixf mtxInv = cMath::MatrixInverse(pStart->GetWorldMatrix());
			vCamRotation = cMath::GetAngleFromPoints3D(cVector3f(0, 0, 0), mtxInv.GetForward() * -1);
			vBodyRotation = cMath::GetAngleFromPoints3D(cVector3f(0, 0, 0), mtxInv.GetForward());
		}

		// Set position of the player, this should include body and stuff:
		// mpCamera->SetPosition(vPosition + cVector3f(0,mvSize.y/2.0f,0));
		mpCharBody->SetPosition(vPosition + cVector3f(0, mpCharBody->GetSize().y / 2.0f, 0));
		// mpCharBody->SetPosition(cVector3f(0,0.43f,0));
		mpCamera->SetYaw(vCamRotation.y);
		mpCamera->SetPitch(vCamRotation.x);
	}
}

//-----------------------------------------------------------------------

void cPlayer::ChangeMoveState(ePlayerMoveState aState, bool abSetHeadHeightDirectly) {
	if (mMoveState == aState)
		return;

	// Log("Change movestate from %d to: %d\n",(int)mMoveState,(int)aState);

	ePlayerMoveState PrevState = mMoveState;
	mMoveState = aState;

	mvMoveStates[aState]->InitState(mvMoveStates[PrevState]);

	if (abSetHeadHeightDirectly) {
		SetHeightAdd(mvMoveStates[aState]->mfHeightAdd);
	}
}

//-----------------------------------------------------------------------

void cPlayer::FootStep(float afMul, const tString &asType, bool abSkipCount) {
	if (mlGroundCount <= 0 && abSkipCount == false)
		return;

	iPhysicsMaterial *pMaterial = mpGroundRayCallback->mpMaterial;
	if (pMaterial == NULL)
		return;

	cSurfaceData *pData = pMaterial->GetSurfaceData();
	if (pData == NULL) {
		Error("No surface data in material '%s'!\n", pMaterial->GetName().c_str());
		return;
	}

	tString sMatStepType = pMaterial->GetSurfaceData()->GetStepType();
	if (sMatStepType == "")
		return;

	const tString &sType = asType != "" ? asType : mvMoveStates[mMoveState]->msStepType;

	tString sSoundName = "player_step_" + sType + "_" + sMatStepType;

	cResources *pResources = gpInit->mpGame->GetResources();

	cSoundEntityData *pSoundData = pResources->GetSoundEntityManager()->CreateSoundEntity(sSoundName);
	if (pSoundData) {
		cSoundHandler *pSoundHandler = gpInit->mpGame->GetSound()->GetSoundHandler();

		pSoundHandler->PlayGui(pSoundData->GetMainSoundName(), false,
							   afMul * pSoundData->GetVolume());

		cGameTrigger_Sound *pSound = hplNew(cGameTrigger_Sound, ());
		pSound->mpSound = pSoundData;
		mpInit->mpTriggerHandler->Add(pSound, eGameTriggerType_Sound,
									  mpCharBody->GetFeetPosition() + cVector3f(0, 0.2f, 0),
									  10, 1.0f / 60.0f, pSoundData->GetMaxDistance());
	}

	/*cWorld3D *pWorld = mpScene->GetWorld3D();

	cSoundEntity *pSound = pWorld->CreateSoundEntity("Step",sSoundName,true);
	if(pSound)
	{
		pSound->SetVolume(afMul * pSound->GetVolume());
		pSound->SetPosition(cVector3f(0,0.2f,0.4f));

		mFeetNode.AddEntity(pSound);
		mFeetNode.SetPosition(mFeetNode.GetLocalPosition());
	}*/
}

//-----------------------------------------------------------------------

void cPlayer::ChangeState(ePlayerState aState) {
	if (aState == mState)
		return;

	// Log("State %d --> %d\n",(int)mState, (int)aState);

	mvStates[aState]->InitState(mvStates[mState]);

	mState = aState;
}

//-----------------------------------------------------------------------

bool cPlayer::AddCrossHairPos(const cVector2f &avPos) {
	bool abEdge = false;

	mvCrossHairPos += avPos;
	if (mvCrossHairPos.x < mvInteractMoveBorder.x) {
		mvCrossHairPos.x = mvInteractMoveBorder.x;
		abEdge = true;
	}
	if (mvCrossHairPos.y < mvInteractMoveBorder.y) {
		mvCrossHairPos.y = mvInteractMoveBorder.y;
		abEdge = true;
	}
	if (mvCrossHairPos.x > (799 - mvInteractMoveBorder.x)) {
		mvCrossHairPos.x = (799 - mvInteractMoveBorder.x);
		abEdge = true;
	}
	if (mvCrossHairPos.y > (599 - mvInteractMoveBorder.y)) {
		mvCrossHairPos.y = (599 - mvInteractMoveBorder.y);
		abEdge = true;
	}

	return abEdge;
}

//-----------------------------------------------------------------------

void cPlayer::AddCollideScript(eGameCollideScriptType aType, const tString &asFunc, const tString &asEntity) {
	cGameCollideScript *pCallback;

	// Check if the function already exist
	tGameCollideScriptMapIt it = m_mapCollideCallbacks.find(asEntity);
	if (it != m_mapCollideCallbacks.end()) {
		pCallback = it->second;
	} else {
		pCallback = hplNew(cGameCollideScript, ());

		// Get the entity
		iGameEntity *pEntity = mpInit->mpMapHandler->GetGameEntity(asEntity);
		if (pEntity == NULL) {
			Warning("Couldn't find entity '%s'\n", asEntity.c_str());
			hplDelete(pCallback);
			return;
		}

		// Set the entity
		pCallback->mpEntity = pEntity;

		// Add to container
		m_mapCollideCallbacks.insert(tGameCollideScriptMap::value_type(asEntity, pCallback));
	}

	pCallback->msFuncName[aType] = asFunc;
}

//-----------------------------------------------------------------------

void cPlayer::RemoveCollideScriptWithChildEntity(iGameEntity *apEntity) {
	tGameCollideScriptMapIt it = m_mapCollideCallbacks.begin();
	for (; it != m_mapCollideCallbacks.end();) {
		cGameCollideScript *pCallback = it->second;
		tGameCollideScriptMapIt currentIt = it;
		++it;

		if (pCallback && pCallback->mpEntity == apEntity) {
			if (mbUpdatingCollisionCallbacks) {
				pCallback->mbDeleteMe = true;
			} else {
				hplDelete(pCallback);
				m_mapCollideCallbacks.erase(currentIt);
			}
		}
	}
}

//-----------------------------------------------------------------------

void cPlayer::RemoveCollideScript(eGameCollideScriptType aType, const tString &asEntity) {
	tGameCollideScriptMapIt it = m_mapCollideCallbacks.find(asEntity);
	if (it != m_mapCollideCallbacks.end()) {
		cGameCollideScript *pCallback = it->second;

		pCallback->msFuncName[aType] = "";
		// if there are no functions left, erase
		if (pCallback->msFuncName[0] == "" && pCallback->msFuncName[1] == "" && pCallback->msFuncName[2] == "") {
			if (mbUpdatingCollisionCallbacks) {
				pCallback->mbDeleteMe = true;
			} else {
				hplDelete(pCallback);
				m_mapCollideCallbacks.erase(it);
			}
		}

	} else {
		Warning("Entity '%s' callback doesn't exist in 'Player'\n", asEntity.c_str());
	}
}

//-----------------------------------------------------------------------

void cPlayer::ClearCollideScripts() {
	STLMapDeleteAll(m_mapCollideCallbacks);
}

//-----------------------------------------------------------------------

void cPlayer::SetSpeedMul(float afSpeedMul) {
	mfSpeedMul = afSpeedMul;

	mvMoveStates[mMoveState]->SetupBody();
}

//-----------------------------------------------------------------------

void cPlayer::SetHealthSpeedMul(float afHealthSpeedMul) {
	mfHealthSpeedMul = afHealthSpeedMul;

	mvMoveStates[mMoveState]->SetupBody();
}

//-----------------------------------------------------------------------

void cPlayer::SetHealth(float afX) {
	mfHealth = afX;

	if (mfHealth > 100) {
		mfHealth = 100;
	} else if (mfHealth <= 0) {
		mpDeath->Start();
	}
}

void cPlayer::AddHealth(float afX) {
	SetHealth(mfHealth + afX);
}

void cPlayer::Damage(float afDamage, ePlayerDamageType aType) {
	if (afDamage <= 0)
		return;
	if (mpInit->mpMapHandler->IsChangingMap())
		return;
	if (mfHealth <= 0)
		return;

	if (mpInit->mDifficulty == eGameDifficulty_Easy)
		afDamage /= 2.0f;
	if (mpInit->mDifficulty == eGameDifficulty_Hard)
		afDamage *= 2.0f;

	if (mpInit->mbHasHaptics)
		afDamage /= 4.0f;

	if (mpDeath->IsActive())
		return;

	float fSize = 0.5f;
	if (afDamage > 10)
		fSize = 1.5f;
	if (afDamage > 20)
		fSize = 2.0f;
	if (afDamage > 50)
		fSize = 3.0f;
	if (afDamage > 80)
		fSize = 4.0f;
	mpDamage->Start(fSize, aType);

	AddHealth(-afDamage);

	if (mpInit->mbHasHaptics && aType == ePlayerDamageType_BloodSplash) {
		if (mbDamageFromPos) {
			mbDamageFromPos = false;

			cVector3f vDir = cMath::Vector3Normalize(mpCharBody->GetPosition() - mvDamagePos);

			if (mpDamageDirForce->IsActive())
				mpDamageDirForce->SetActive(false);
			mpDamageDirForce->SetActive(true);

			cMatrixf mtxProxy = cMath::MatrixRotate(cVector3f(-mpCamera->GetPitch(), -mpCamera->GetYaw(),
															  -mpCamera->GetRoll()),
													eEulerRotationOrder_YXZ);
			vDir = cMath::MatrixMul(mtxProxy, vDir);

			mpDamageDirForce->SetForce(vDir * fSize * 2.2f);
			mpDamageDirForce->SetTimeControl(false, 0.2f, 0.5f, 0.0f, 0.15f);
		} else {
			mpDamageForce->SetAmp(fSize * 0.15f);

			if (mpDamageForce->IsActive())
				mpDamageForce->SetActive(false);
			mpDamageForce->SetActive(true);
			mpDamageForce->SetTimeControl(false, 0.3f, 0.0f, 0.05f, 0.25f);
		}
	}
}

//-----------------------------------------------------------------------

bool cPlayer::IsDead() {
	return mpDeath->IsActive();
}

//-----------------------------------------------------------------------

iPhysicsBody *cPlayer::GetPickedBody() {
	return mpPickRayCallback->mpPickedBody;
}
void cPlayer::SetPickedBody(iPhysicsBody *apBody) {
	mpPickRayCallback->mpPickedBody = apBody;
}

//-----------------------------------------------------------------------

float cPlayer::GetPickedDist() {
	// return mpPickRayCallback->mfPickedDist;
	return cMath::Vector3Dist(mpCharBody->GetPosition(), mpPickRayCallback->mvPickedPos);
}

const cVector3f &cPlayer::GetPickedPos() {
	return mpPickRayCallback->mvPickedPos;
}
cPlayerPickRayCallback *cPlayer::GetPickRay() {
	return mpPickRayCallback;
}

//-----------------------------------------------------------------------

void cPlayer::DestroyWorldObjects() {
	// Body
	if (mpCharBody)
		mpScene->GetWorld3D()->GetPhysicsWorld()->DestroyCharacterBody(mpCharBody);

	// mpFlashLight->Destroy();
	// mpGlowStick->Destroy();
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS - INTERACTIONS
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cPlayer::MoveForwards(float afMul, float afTimeStep) {
	if (mvStates[mState]->OnMoveForwards(afMul, afTimeStep)) {
		// Only move if on ground
		if (mlGroundCount <= 0 || afMul == 0)
			return;

		mpCharBody->Move(eCharDir_Forward, afMul, afTimeStep);
		mbMoving = true;
		mvMoveStates[mMoveState]->Start();
	}
}

//-----------------------------------------------------------------------

void cPlayer::MoveSideways(float afMul, float afTimeStep) {
	if (mvStates[mState]->OnMoveSideways(afMul, afTimeStep)) {
		// Only move if on ground
		if (mlGroundCount <= 0 || afMul == 0)
			return;

		mpCharBody->Move(eCharDir_Right, afMul, afTimeStep);
		mbMoving = true;
		mvMoveStates[mMoveState]->Start();
	}
}

//-----------------------------------------------------------------------

void cPlayer::Lean(float afMul, float afTimeStep) {
	mpLean->Lean(afMul, afTimeStep);
}

//-----------------------------------------------------------------------

void cPlayer::AddYaw(float afVal) {
	if (mvStates[mState]->OnAddYaw(afVal)) {
		mpCamera->AddYaw(-afVal * mfLookSpeed);
		mpCharBody->SetYaw(mpCamera->GetYaw());
	}
}

//-----------------------------------------------------------------------

void cPlayer::AddPitch(float afVal) {
	if (mvStates[mState]->OnAddPitch(afVal)) {
		float fInvert = mpInit->mpButtonHandler->GetInvertMouseY() ? -1.0f : 1.0f;
		mpCamera->AddPitch(-afVal * mfLookSpeed * fInvert);
	}
}

//-----------------------------------------------------------------------

void cPlayer::StartInteract() {
	mvStates[mState]->OnStartInteract();
}

void cPlayer::StopInteract() {
	mvStates[mState]->OnStopInteract();
}

//-----------------------------------------------------------------------

void cPlayer::StartExamine() {
	mvStates[mState]->OnStartExamine();
}

void cPlayer::StopExamine() {
	mvStates[mState]->OnStopExamine();
}

//-----------------------------------------------------------------------

void cPlayer::StartHolster() {
	mvStates[mState]->OnStartHolster();
}

//-----------------------------------------------------------------------

void cPlayer::Jump() {
	if (mvStates[mState]->OnJump() && mlGroundCount > 0) {
		if (mvMoveStates[mMoveState]->mType != ePlayerMoveState_Jump) {
			ChangeMoveState(ePlayerMoveState_Jump);
		}
	}
	mfJumpCount = 0;
}

void cPlayer::SetJumpButtonDown(bool abX) {
	mbJumpButtonDown = abX;

	if (mbJumpButtonDown) {
		mfJumpCount += mpInit->mpGame->GetStepSize();
	} else {
		mfJumpCount = mfMaxJumpCount;
	}
}

//-----------------------------------------------------------------------

void cPlayer::StartRun() {
	mvStates[mState]->OnStartRun();
}

//-----------------------------------------------------------------------

void cPlayer::StopRun() {
	mvStates[mState]->OnStopRun();
}

//-----------------------------------------------------------------------

void cPlayer::StartCrouch() {
	mvStates[mState]->OnStartCrouch();
}

//-----------------------------------------------------------------------

void cPlayer::StopCrouch() {
	mvStates[mState]->OnStopCrouch();
}

//-----------------------------------------------------------------------

void cPlayer::StartInteractMode() {
	mvStates[mState]->OnStartInteractMode();
}

//-----------------------------------------------------------------------

void cPlayer::StartInventory() {
	if (mvStates[mState]->OnStartInventory()) {
		mpInit->mpInventory->SetActive(true);
	}
}

//-----------------------------------------------------------------------

void cPlayer::StartInventoryShortCut(int alNum) {
	if (mvStates[mState]->OnStartInventoryShortCut(alNum)) {
		mpInit->mpInventory->OnShortcutDown(alNum);
	}
}

//-----------------------------------------------------------------------

void cPlayer::StartFlashLightButton() {
	if (mpInit->mpInventory->GetItem("flashlight") != NULL) {
		mpFlashLight->SetActive(!mpFlashLight->IsActive());

		if (mpFlashLight->IsActive()) {
			mpGlowStick->SetActive(false);
			mpFlare->SetActive(false);
		}
	}
}

void cPlayer::StartGlowStickButton() {
	if (mpInit->mpInventory->GetItem("glowstick") != NULL ||
		mpInit->mpInventory->GetItem("glowst1") != NULL) {
		mpGlowStick->SetActive(!mpGlowStick->IsActive());

		if (mpGlowStick->IsActive()) {
			mpFlashLight->SetActive(false);
			mpFlare->SetActive(false);
		}
	}
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS - EVENTS
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cPlayer::OnWorldLoad() {
	/////////////////////////////////////////////////////////
	// Create body
	mpCharBody = mpScene->GetWorld3D()->GetPhysicsWorld()->CreateCharacterBody("Player", mvSize);

	mpCharBody->SetCamera(mpCamera);
	mpCharBody->SetMass(mfMass);
	// mpCamera->SetPosition(cVector3f(1,1.2f,-2));

	mpCharBody->SetCameraPosAdd(cVector3f(0, mfCameraHeightAdd, 0));
	mpCharBody->SetCameraSmoothPosNum(6);
	mpCharBody->SetCallback(mpBodyCallback);

	mpCharBody->SetMaxGravitySpeed(40.0f);
	mpCharBody->SetCustomGravityActive(true);
	mpCharBody->SetCustomGravity(cVector3f(0, -18.0f, 0));

	mpCharBody->SetMaxPushMass(mpInit->mpGameConfig->GetFloat("Player", "MaxPushMass", 1));
	mpCharBody->SetPushForce(mpInit->mpGameConfig->GetFloat("Player", "PushForce", 1));

	mpCharBody->SetMaxStepSize(mpInit->mpGameConfig->GetFloat("Player", "MaxStepSize", 0));
	mpCharBody->SetStepClimbSpeed(mpInit->mpGameConfig->GetFloat("Player", "StepClimbSpeed", 0));
	mpCharBody->SetClimbForwardMul(1.75f);
	mpCharBody->SetClimbHeightAdd(0.02f);
	mpCharBody->SetAccurateClimbing(true);

	mpCharBody->SetGroundFriction(mpInit->mpGameConfig->GetFloat("Player", "GroundFriction", 0));
	mpCharBody->SetAirFriction(mpInit->mpGameConfig->GetFloat("Player", "AirFriction", 0));

	// Add the crouch size
	mpCharBody->AddExtraSize(cVector3f(mvSize.x, mfCrouchHeight, mvSize.z));

	// Set so it is not saved:
	mpCharBody->SetIsSaved(false);
	mpCharBody->GetExtraBody(0)->SetIsSaved(false);
	mpCharBody->GetExtraBody(1)->SetIsSaved(false);

	mvMoveStates[mMoveState]->EnterState(NULL);
	mvMoveStates[mMoveState]->Start();

	mpFlashLight->OnWorldLoad();
	// mpGlowStick->OnWorldLoad();
	mpFlare->OnWorldLoad();
	mpHidden->OnWorldLoad();
	mpLean->OnWorldLoad();
	if (mpInit->mbHasHaptics)
		mpHapticCamera->OnWorldLoad();
}

//-----------------------------------------------------------------------

void cPlayer::OnWorldExit() {
	DestroyWorldObjects();

	mpGroundRayCallback->OnWorldExit();
	mpPickRayCallback->OnWorldExit();
	mpHidden->OnWorldExit();
	if (mpInit->mbHasHaptics)
		mpHapticCamera->OnWorldExit();
}

//-----------------------------------------------------------------------

void cPlayer::OnStart() {
}

//-----------------------------------------------------------------------

static inline tString GetCollideCommand(const tString &asFuncName, const tString &asParent,
										const tString &asChild) {
	return asFuncName + "(\"" + asParent + "\", \"" + asChild + "\")";
}

class cTempCheckProxy : public iPhysicsRayCallback {
public:
	bool mbCollided;
	bool OnIntersect(iPhysicsBody *pBody, cPhysicsRayParams *apParams) {
		// if(apParams->mfT <0) return true;
		if (pBody->GetCollide() == false || pBody->IsCharacter())
			return true;

		mbCollided = true;
		return false;
	}
};

void cPlayer::Update(float afTimeStep) {
	//cSystem *pSystem = mpInit->mpGame->GetSystem();
	//unsigned int lTime = pSystem->GetLowLevel()->getTime();
	iPhysicsWorld *pPhysicsWorld = mpScene->GetWorld3D()->GetPhysicsWorld();

	/////////////////////////////////////
	// HaptX camera
	if (cHaptic::GetIsUsed()) {
		mpHapticCamera->Update(afTimeStep);
	}

	// LogUpdate("  Death\n");
	////////////////////////////////////////
	// Make sure player is dead if he should be
	if (mfHealth <= 0 && mpDeath->IsActive() == false) {
		mpDeath->Start();
	}

	////////////////////////////////////////
	// Update Node and Footstep sounds
	/*//LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	lTime = pSystem->GetLowLevel()->GetTime();
	//LogUpdate("  Footstep sounds\n");
	cMatrixf mtxChar = mpInit->mpGame->GetSound()->GetLowLevel()->GetListenerMatrix();
	mtxChar.SetTranslation(mtxChar.GetTranslation() -
							cVector3f(0,mpCharBody->GetSize().y/2,0));
	mFeetNode.SetMatrix(mtxChar);
	cSoundHandler *pSoundHandler = mpInit->mpGame->GetSound()->GetSoundHandler();
	cEntityIterator entIt = mFeetNode.GetEntityInterator();
	while(entIt.HasNext())
	{
		cSoundEntity *pSound = static_cast<cSoundEntity*>(entIt.Next());
		cSoundEntry *pEntry = pSound->GetSoundEntry(eSoundEntityType_Main);
		if(	pEntry && pSoundHandler->IsValid(pEntry->mpSound))
		{
			pEntry->mpSound->SetPosition(pSound->GetWorldPosition());
		}
	}
	//LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);*/

	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  misc\n");
	//////////////////////
	// Reset roll
	mpInit->mpPlayer->GetCamera()->SetRoll(0);

	/////////////////////////////////////////////////
	// Misc
	mItemFlash.Update(afTimeStep);

	/////////////////////////////////////////////////
	// Damage
	mpDamage->Update(afTimeStep);

	/////////////////////////////////////////////////
	// Death
	mpDeath->Update(afTimeStep);

	/////////////////////////////////////////////////
	// Flashlight
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  flashlight");
	mpFlashLight->Update(afTimeStep);

	/////////////////////////////////////////////////
	// Glowstick
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  glowstick\n");
	mpGlowStick->Update(afTimeStep);

	/////////////////////////////////////////////////
	// Flare
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  flare\n");
	mpFlare->Update(afTimeStep);

	/////////////////////////////////////////////////
	// Lean
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  more misc\n");
	mpLean->Update(afTimeStep);

	/////////////////////////////////////////////////
	// Ear ring
	mpEarRing->Update(afTimeStep);

	//////////////////////////////////////////////////
	// health
	mpHealth->Update(afTimeStep);

	////////////////////////////////////////
	// Noise filter
	mpNoiseFilter->Update(afTimeStep);

	////////////////////////////////////////
	// Fear filter
	mpFearFilter->Update(afTimeStep);

	////////////////////////////////////////
	// Look at
	mpLookAt->Update(afTimeStep);

	////////////////////////////////////////
	// Hidden
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  hidden\n");
	mpHidden->Update(afTimeStep);

	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  collide scripts\n");
	/////////////////////////////////////////////////
	// Collide script
	pPhysicsWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
	/*cWorld3D *pWorld = */mpInit->mpGame->GetScene()->GetWorld3D();
	cCollideData collideData;
	collideData.SetMaxSize(1);

	mbUpdatingCollisionCallbacks = true;
	tGameCollideScriptMapIt CollideIt = m_mapCollideCallbacks.begin();
	for (; CollideIt != m_mapCollideCallbacks.end(); ++CollideIt) {
		cGameCollideScript *pCallback = CollideIt->second;

		if (pCallback == NULL)
			continue;
		if (pCallback->mpEntity == NULL)
			continue;

		iGameEntity *pEntity = pCallback->mpEntity;

		if (pEntity->IsActive() == false)
			continue;

		// LogUpdate("  callback %s %s %s\n",pCallback->msFuncName[0].c_str(),pCallback->msFuncName[1].c_str(),pCallback->msFuncName[2].c_str());

		bool bCollide = false;

		for (size_t j = 0; j < pEntity->mvBodies.size(); ++j) {
			iPhysicsBody *pParentBody = mpCharBody->GetBody();
			iPhysicsBody *pChildBody = pEntity->mvBodies[j];

			if (cMath::CheckCollisionBV(*pParentBody->GetBV(), *pChildBody->GetBV())) {
				bCollide = pPhysicsWorld->CheckShapeCollision(pParentBody->GetShape(),
															  pParentBody->GetLocalMatrix(),
															  pChildBody->GetShape(),
															  pChildBody->GetLocalMatrix(), collideData, 1);
			}

			if (bCollide)
				break;
		}

		// Run Collide scripts
		// LogUpdate("  running script");
		if (bCollide) {
			if (pCallback->mbCollides) {
				if (pCallback->msFuncName[eGameCollideScriptType_During] != "") {
					tString sCommand = GetCollideCommand(
						pCallback->msFuncName[eGameCollideScriptType_During],
						"Player", CollideIt->first);
					mpInit->RunScriptCommand(sCommand);
				}
			} else {
				if (pCallback->msFuncName[eGameCollideScriptType_Enter] != "") {
					tString sCommand = GetCollideCommand(
						pCallback->msFuncName[eGameCollideScriptType_Enter],
						"Player", CollideIt->first);
					mpInit->RunScriptCommand(sCommand);
				}

				pCallback->mbCollides = true;
			}
		} else {
			if (pCallback->mbCollides) {
				if (pCallback->msFuncName[eGameCollideScriptType_Leave] != "") {
					tString sCommand = GetCollideCommand(
						pCallback->msFuncName[eGameCollideScriptType_Leave],
						"Player", CollideIt->first);
					mpInit->RunScriptCommand(sCommand);
				}

				pCallback->mbCollides = false;
			}
		}
	}
	mbUpdatingCollisionCallbacks = false;

	//////////////////////////////////////////////////
	// Check if any collide script should be deleted.
	// LogUpdate("  Check collide script deleting\n");

	CollideIt = m_mapCollideCallbacks.begin();
	for (; CollideIt != m_mapCollideCallbacks.end();) {
		cGameCollideScript *pCallback = CollideIt->second;
		if (pCallback->mbDeleteMe) {
			hplDelete(pCallback);
			m_mapCollideCallbacks.erase(CollideIt++);
		} else {
			++CollideIt;
		}
	}

	/////////////////////////////////////////////////
	// Update ground count, this is so that a little air born time still counts as on ground
	if (mpCharBody->IsOnGround())
		mlGroundCount = 25;
	else if (mlGroundCount > 0)
		mlGroundCount--;

	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  Check For ground\n");
	//////////////////////////////
	// Cast ray and check for ground.
	cVector3f vStart, vEnd;

	iCollideShape *pBodyShape = mpCharBody->GetShape();
	vStart = mpCharBody->GetPosition() - cVector3f(0, pBodyShape->GetSize().y / 2 - 0.3f, 0);
	vEnd = vStart + cVector3f(0, -0.6f, 0);

	mpGroundRayCallback->Clear();
	pPhysicsWorld->CastRay(mpGroundRayCallback, vStart, vEnd, true, false, false);

	//////////////////////////////
	// Update movement
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  Movement\n");

	if (mbMoving == false)
		mvMoveStates[mMoveState]->Stop();

	mvMoveStates[mMoveState]->Update(afTimeStep);
	mpHeadMove->Update(afTimeStep);

	mbMoving = false;

	//////////////////////////////
	// Update camera pos add
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  Camera pos\n");
	if (mpCharBody) {
		float fYAdd = mfCameraHeightAdd + mpHeadMove->GetPos() + mfHeightAdd + mpDeath->GetHeighAdd() +
					  mpInit->mpEffectHandler->GetShakeScreen()->GetScreenAdd().y;

		cVector3f vRight = mpCharBody->GetRight();
		float fXAdd = mpLean->mfMovement * vRight.x +
					  mpInit->mpEffectHandler->GetShakeScreen()->GetScreenAdd().x;

		float fZAdd = mpLean->mfMovement * vRight.z +
					  mpInit->mpEffectHandler->GetShakeScreen()->GetScreenAdd().x;

		// Log("HEadMove: %f HeightAdd %f Death: %f\n",mpHeadMove->GetPos(),mfHeightAdd,mpDeath->GetHeighAdd());

		mpCharBody->SetCameraPosAdd(cVector3f(fXAdd, fYAdd, fZAdd));
	}

	///////////////////////////
	// Update state

	// Clear picked body
	// mpPushBody = NULL;
	SetPickedBody(NULL);

	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
	//lTime = pSystem->GetLowLevel()->getTime();
	// LogUpdate("  state %d\n",mState);
	if (mpInit->mpInventory->IsActive() == false &&
		mpInit->mpNotebook->IsActive() == false) {
		mvStates[mState]->OnUpdate(afTimeStep);
	}
	// LogUpdate("  took %d ms\n",pSystem->GetLowLevel()->GetTime() - lTime);
}

//-----------------------------------------------------------------------

void cPlayer::Reset() {
	// Camera
	mpCamera->SetPitchLimits(cVector2f(cMath::ToRad(70), cMath::ToRad(-70)));
	mpCamera->SetYawLimits(0);
	mpCamera->SetFOV(cMath::ToRad(70));

	cVector2f vScreenSize = mpInit->mpGame->GetGraphics()->GetLowLevel()->GetScreenSize();
	mpCamera->SetAspect(vScreenSize.x / vScreenSize.y);

	// Properties
	mbItemFlash = false;
	mfHealth = 100;
	mbMoving = false;
	mfMass = mfDefaultMass;
	mbLandedFromJump = false;
	mfSpeedMul = 1.0f;
	mfHealthSpeedMul = 1.0f;
	mbActive = true;
	mfPower = 0;

	mfHeightAdd = 0;

	mfLookSpeed = 1.0f;
	mpCharBody = NULL;
	mpWeaponCallback = NULL;
	mbUpdatingCollisionCallbacks = false;

	mbDamageFromPos = false;

	// jump properties
	mbJumpButtonDown = false;
	mfJumpCount = 0;

	// States
	mMoveState = ePlayerMoveState_Walk;
	mState = ePlayerState_Normal;

	// Crosshair
	mCrossHairState = eCrossHairState_None;
	mvCrossHairPos = cVector2f(400, 300);

	// Stats
	mlStat_NumOfSaves = 0;

	// Callbacks
	STLMapDeleteAll(m_mapCollideCallbacks);

	// Helpers
	mpDeath->Reset();
	mpFlashLight->Reset();
	mpEarRing->Stop(true);
	mpGlowStick->Reset();
	mpFlare->Reset();
	mpLookAt->Reset();
	mpFearFilter->Reset();
	mpLean->Reset();
	mpEarRing->Reset();
	mpHealth->Reset();
	mpHidden->Reset();
	mpGroundRayCallback->Reset();
	if (mpInit->mbHasHaptics)
		mpHapticCamera->Reset();
}

//-----------------------------------------------------------------------

void cPlayer::OnDraw() {
	/////////////////////////////////
	// Damage
	mpDamage->Draw();

	/////////////////////////////////
	// Death
	mpDeath->Draw();

	////////////////////////////////////////
	// Noise filter
	mpNoiseFilter->Draw();

	////////////////////////////////////////
	// Fear filter
	mpFearFilter->Draw();

	////////////////////////////////////////
	// Hidden
	mpHidden->Draw();

	mpHealth->Draw();

	////////////////////////////////
	// Cross hair
	if (IsActive() == false) {
		// Do noting...
	} else if (mpInit->mbHasHaptics && mpHapticCamera->ShowCrosshair() == false) {
		if (mCrossHairState != eCrossHairState_None) {
			cVector3f vProjPos = cMath::MatrixMul(mpCamera->GetViewMatrix(),
												  mpHapticCamera->GetHandEntity()->GetWorldPosition());
			vProjPos = cMath::MatrixMulDivideW(mpCamera->GetProjectionMatrix(), vProjPos);

			cVector2f vPos((vProjPos.x + 1) * 0.5f, (-vProjPos.y + 1) * 0.5f);
			vPos *= cVector2f(800, 600);

			vPos += cVector2f(10, 10);
			cResourceImage *pImage = mvCrossHairs[mCrossHairState]->GetMaterial()->GetImage(eMaterialTexture_Diffuse);
			cVector2l vSize = pImage->GetSize();
			cVector2f vPosAdd(((float)vSize.x) / 2.0f, ((float)vSize.y) / 2.0f);
			mpGfxDrawer->DrawGfxObject(mvCrossHairs[mCrossHairState],
									   cVector3f(0, 0, 100) + (vPos - vPosAdd));
		}
	} else if (mCrossHairState == eCrossHairState_Item) {
		cGfxObject *pObject = mpCurrentItem->GetGfxObject();
		cGfxObject *pAdditive = mpCurrentItem->GetGfxObjectAdditive();
		if (pObject) {
			cVector2l vIntSize = pObject->GetMaterial()->GetImage(eMaterialTexture_Diffuse)->GetSize();
			cVector2f vSize((float)vIntSize.x, (float)vIntSize.y);

			cVector2f vPosAdd(((float)vSize.x) / 2.0f, ((float)vSize.y) / 2.0f);

			if (mbItemFlash) {
				mpGfxDrawer->DrawGfxObject(pObject, cVector3f(0, 0, 100) + (mvCrossHairPos - vPosAdd), vSize,
										   cColor(1, 1, 1, 1));
				for (int i = 0; i < 2; ++i)
					mpGfxDrawer->DrawGfxObject(pAdditive, cVector3f(0, 0, 101) + (mvCrossHairPos - vPosAdd), vSize,
											   cColor(1, 1, 1, mItemFlash.val));

				/*mpGfxDrawer->DrawGfxObject(pAdditive,cVector3f(3,3,99)+(mvCrossHairPos - vPosAdd),vSize,
											cColor(0,1,0,mItemFlash.val*0.8f));
				mpGfxDrawer->DrawGfxObject(pAdditive,cVector3f(-3,-3,99)+(mvCrossHairPos - vPosAdd),vSize,
											cColor(0,1,0,mItemFlash.val*0.8f));
				mpGfxDrawer->DrawGfxObject(pAdditive,cVector3f(-3,3,99)+(mvCrossHairPos - vPosAdd),vSize,
											cColor(0,1,0,mItemFlash.val*0.8f));
				mpGfxDrawer->DrawGfxObject(pAdditive,cVector3f(3,-3,99)+(mvCrossHairPos - vPosAdd),vSize,
											cColor(0,1,0,mItemFlash.val*0.8f));*/
			} else {
				mpGfxDrawer->DrawGfxObject(pObject, cVector3f(0, 0, 100) + (mvCrossHairPos - vPosAdd), vSize,
										   cColor(1, 0.3f, 0.3f, 1.0f));
			}
		}
	} else if (mCrossHairState != eCrossHairState_None) {
		cResourceImage *pImage = mvCrossHairs[mCrossHairState]->GetMaterial()->GetImage(eMaterialTexture_Diffuse);
		cVector2l vSize = pImage->GetSize();
		cVector2f vPosAdd(((float)vSize.x) / 2.0f, ((float)vSize.y) / 2.0f);
		mpGfxDrawer->DrawGfxObject(mvCrossHairs[mCrossHairState], cVector3f(0, 0, 100) + (mvCrossHairPos - vPosAdd));
	} else if (mpInit->mbShowCrossHair) {
		cVector3f vPos = cVector3f(400, 300, 0);
		cResourceImage *pImage = mvCrossHairs[eCrossHairState_Cross]->GetMaterial()->GetImage(eMaterialTexture_Diffuse);
		cVector2l vSize = pImage->GetSize();
		cVector2f vPosAdd(((float)vSize.x) / 2.0f, ((float)vSize.y) / 2.0f);
		mpGfxDrawer->DrawGfxObject(mvCrossHairs[eCrossHairState_Cross], cVector3f(0, 0, 100) + (vPos - vPosAdd));
	}

	// DEBUG: Memory
	/*float fMbMem = ((float)cMemoryManager::mlTotalMemoryUsage) / (1024.0f * 1024.0f);
	mpFont->Draw(cVector3f(5,5,0),12,cColor(1,1,1,1),eFontAlign_Left,_W("Memory used: %d (%.2f mb)"),
											cMemoryManager::mlTotalMemoryUsage, fMbMem);
	*/

	// DEBUG: Mouse
	// iMouse *pMouse = mpInit->mpGame->GetInput()->GetMouse();
	// mpFont->Draw(cVector3f(5,5,0),12,cColor(1,1,1,1),eFontAlign_Left,_W("Left: %d Right: %d"),
	//													pMouse->ButtonIsDown(eMButton_Left),
	//													pMouse->ButtonIsDown(eMButton_Right));

	// DEBUG: State
	if (mpInit->mbHasHaptics) {
		/*tWString sState =_W("Unknown");
		if(mState == ePlayerState_Normal) sState = _W("Normal");
		else if(mState == ePlayerState_Push) sState = _W("Push");
		else if(mState == ePlayerState_Move) sState = _W("Move");
		else if(mState == ePlayerState_InteractMode) sState = _W("InteractMode");
		else if(mState == ePlayerState_UseItem) sState = _W("UseItem");
		else if(mState == ePlayerState_Message) sState = _W("Message");
		else if(mState == ePlayerState_Grab) sState = _W("Grab");
		else if(mState == ePlayerState_WeaponMelee) sState = _W("WeaponMelee");
		else if(mState == ePlayerState_Throw) sState = _W("Throw");
		else if(mState == ePlayerState_Climb) sState = _W("Climb");

		mpFont->Draw(cVector3f(5,5,0),12,cColor(1,1,1,1),eFontAlign_Left,_W("State: %s"),
						sState.c_str());*/
	}
	// DEBUG: MoveState
	/*tString sState ="";
	if(mMoveState == ePlayerMoveState_Jump) sState = "Jump";
	else if(mMoveState == ePlayerMoveState_Crouch) sState = "Crouch";
	else if(mMoveState == ePlayerMoveState_Walk) sState = "Walk";
	else if(mMoveState == ePlayerMoveState_Run) sState = "Run";
	else if(mMoveState == ePlayerMoveState_Still) sState = "Still";

	mpFont->Draw(cVector3f(5,5,0),12,cColor(1,1,1,1),eFontAlign_Left,"MoveState: %s",
					sState.c_str());*/

	// DEBUG: Picked body
	/*if(mpInit->mbHasHaptics)
	{
		if(mpPickRayCallback->mpPickedBody){
			mpFont->Draw(cVector3f(5,35,0),12,cColor(1,1,1,1),eFontAlign_Left,_W("Body: %s"),
				cString::To16Char(mpPickRayCallback->mpPickedBody->GetName()).c_str());
		}
		else{
			mpFont->Draw(cVector3f(5,35,0),12,cColor(1,1,1,1),eFontAlign_Left,_W("Body: NULL"));
		}
		mpFont->Draw(cVector3f(5,46,0),12,cColor(1,1,1,1),eFontAlign_Left,_W("Dist: %f"),
			mpPickRayCallback->mfPickedDist);

		tWString sCState = _W("Unknown");
		if(mCrossHairState == eCrossHairState_Inactive)sCState = _W("Inactive");
		if(mCrossHairState == eCrossHairState_Active)sCState = _W("Active");
		if(mCrossHairState == eCrossHairState_Invalid)sCState = _W("Invalid");
		if(mCrossHairState == eCrossHairState_Grab)sCState = _W("Grab");
		if(mCrossHairState == eCrossHairState_Examine)sCState = _W("Examine");
		if(mCrossHairState == eCrossHairState_Pointer)sCState = _W("Pointer");
		if(mCrossHairState == eCrossHairState_Item)sCState = _W("Item");
		if(mCrossHairState == eCrossHairState_DoorLink)sCState = _W("DoorLink");
		if(mCrossHairState == eCrossHairState_PickUp)sCState = _W("PickUp");
		if(mCrossHairState == eCrossHairState_Ladder)sCState = _W("Ladder");
		if(mCrossHairState == eCrossHairState_None)sCState = _W("None");
		mpFont->Draw(	cVector3f(5,66,0),12,cColor(1,1,1,1),eFontAlign_Left,
						_W("CState: %s"),sCState.c_str());

	}*/

	// DEBUG: On ground and step material
	/*mpFont->Draw(cVector3f(5,17,0),12,cColor(1,1,1,1),eFontAlign_Left,"Position: %f ClimbPos: %f ClimbCount: %f\n",
											mpHeadMove->GetPosition(),
											mpHeadMove->GetClimbPosition(),
											mpHeadMove->GetClimbCount());*/
	// mpFont->Draw(cVector3f(5,29,0),12,cColor(1,1,1,1),eFontAlign_Left,"Gravity: %f",
	// mpCharBody->GetForceVelocity().y);
	// mpFont->Draw(cVector3f(5,29,0),12,cColor(1,1,1,1),eFontAlign_Left,"CameraPos: %s",
	//	mpCamera->GetPosition().ToString().c_str());
	// mpFont->Draw(cVector3f(5,43,0),12,cColor(1,1,1,1),eFontAlign_Left,"CharPos: %s",
	//	GetCharacterBody()->GetPosition().ToString().c_str());

	/*if(mpRayCallback->mpMaterial)
	mpFont->Draw(cVector3f(5,17,0),12,cColor(1,1,1,1),eFontAlign_Left,"Material: %s",
	mpGroundRayCallback->mpMaterial->GetName().c_str());*/

	// DEBUG: health
	if (mbShowHealth) {
		mpFont->draw(cVector3f(5, 5, 0), 12, cColor(1, 1, 1, 1), eFontAlign_Left, Common::U32String::format("Health: %.0f",
					 mfHealth));
	}

	// DEBUG: misc
	// mpFont->Draw(cVector3f(5,20,0),12,cColor(1,1,1,1),eFontAlign_Left,
	//			_W("Ground: %d Speed: %f ForceSpeed: %f"),
	//													mpCharBody->IsOnGround()?1:0,
	//													mpCharBody->GetMoveSpeed(eCharDir_Forward),
	//													mpCharBody->GetForceVelocity().Length()
	//												);
	// cVector3f vGravity = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld()->GetGravity();
	// mpFont->Draw(cVector3f(5,20,0),12,cColor(1,1,1,1),eFontAlign_Left,"Gravity: %s",
	//														vGravity.ToString().c_str());

	// DEBUG: sounds playing
	if (mbShowSoundsPlaying) {
		tStringVec vSoundNames;
		Common::Array<cSoundEntry *> vEntries;

		//////////////////////////////
		// Sound channels
		tSoundEntryList *pEntryList = mpInit->mpGame->GetSound()->GetSoundHandler()->GetWorldEntryList();
		for (tSoundEntryListIt it = pEntryList->begin(); it != pEntryList->end(); ++it) {
			iSoundChannel *pSound = it->mpSound;
			vSoundNames.push_back(pSound->GetData()->GetName());
			vEntries.push_back(&(*it));
		}

		vSoundNames.push_back("");
		vEntries.push_back(NULL);

		pEntryList = mpInit->mpGame->GetSound()->GetSoundHandler()->GetGuiEntryList();
		for (tSoundEntryListIt it = pEntryList->begin(); it != pEntryList->end(); ++it) {
			iSoundChannel *pSound = it->mpSound;
			vSoundNames.push_back(pSound->GetData()->GetName());
			vEntries.push_back(&(*it));
		}

		mpFont->draw(cVector3f(5, 18, 0), 10, cColor(1, 1, 1, 1), eFontAlign_Left, Common::U32String::format("Num of sounds: %d", vSoundNames.size() - 1));

		int lRow = 0, lCol = 0;
		for (int i = 0; i < (int)vSoundNames.size(); i++) {
			cSoundEntry *pEntry = vEntries[i];
			if (pEntry == NULL) {
				lRow = 4;
				lCol = 0;
				continue;
			}
			mpFont->draw(cVector3f((float)lCol * 250, 26 + (float)lRow * 11, 0), 10, cColor(1, 1, 1, 1), eFontAlign_Left,
						 Common::U32String::format("%S(%.2f (%.2f %.2f)->%.2f", cString::To16Char(vSoundNames[i]).c_str(),
						 pEntry->mpSound->GetVolume(),
						 pEntry->mfNormalVolumeMul,
						 pEntry->mfNormalVolumeFadeSpeed,
						 pEntry->mfNormalVolumeFadeDest,
						 pEntry->mpSound->GetPriority(),
						 pEntry->mpSound->GetElapsedTime(),
						 pEntry->mpSound->GetTotalTime()

			));
			//								pEntry->mpSound->GetPriority(),
			//								pEntry->mpSound->IsBufferUnderrun()?1:0);

			lCol++;
			if (lCol == 3) {
				lCol = 0;
				lRow++;
			}
		}

		//////////////////////////////
		// Music
		cMusicEntry *pMusic = mpInit->mpGame->GetSound()->GetMusicHandler()->GetCurrentSong();
		if (pMusic) {
			iSoundChannel *pChannel = pMusic->mpStream;
			mpFont->draw(cVector3f(5, 18 + 70, 0), 10, cColor(1, 1, 1, 1), eFontAlign_Left,
						 Common::U32String::format("Music: '%S' vol: %.2f playing: %d prio: %d elapsed: %.2f total time: %.2f",
						 cString::To16Char(pChannel->GetData()->GetName()).c_str(),
						 pChannel->GetVolume(),
						 pChannel->IsPlaying(),
						 pChannel->GetPriority(),
						 pChannel->GetElapsedTime(),
						 pChannel->GetTotalTime()));
		}
	}

	// DEBUG: Portals
	/*tString sPortals = "Portals: ";
	cPortalContainer *pContainer = mpInit->mpGame->GetScene()->GetWorld3D()->GetPortalContainer();
	tStringList* pStringList = pContainer->GetVisibleSectorsList();
	for(tStringListIt it=pStringList->begin(); it != pStringList->end(); ++it)
	{
		sPortals += *it + ", ";
	}

	mpFont->Draw(cVector3f(5,5,0),12,cColor(1,1,1,1),eFontAlign_Left,"%s",
															sPortals.c_str());*/

	mvStates[mState]->OnDraw();
}

void cPlayer::OnPostSceneDraw() {
	cCamera3D *pCam = static_cast<cCamera3D *>(mpScene->GetCamera());
	iLowLevelGraphics *pLowGfx = mpInit->mpGame->GetGraphics()->GetLowLevel();
	pLowGfx->SetMatrix(eMatrix_ModelView, pCam->GetViewMatrix());

	pLowGfx->SetTexture(0, NULL);
	pLowGfx->SetBlendActive(false);

	/*mpInit->mpGame->GetGraphics()->GetLowLevel()->DrawLine(mvLineStart,mvLineEnd,cColor(1,1,1,1));
	mpInit->mpGame->GetGraphics()->GetLowLevel()->DrawSphere(mvLineStart,0.1f,cColor(1,0,1,1));
	mpInit->mpGame->GetGraphics()->GetLowLevel()->DrawSphere(mvLineEnd,0.1f,cColor(1,0,1,1));*/

	mpFlashLight->OnPostSceneDraw();

	mvStates[mState]->OnPostSceneDraw();

	///////////////////////////////
	// Gui Hand effects
	if (mpInit->mbHasHaptics) {
		mpHapticCamera->OnPostSceneDraw();
	}
}

//////////////////////////////////////////////////////////////////////////
// SAVING
//////////////////////////////////////////////////////////////////////////

//-------------------------------------------------------------------

void cPlayer::AddSaveData(cSavedWorld *apSavedWorld) {
	// Collide callbacks
	tGameCollideScriptMapIt colIt = m_mapCollideCallbacks.begin();
	for (; colIt != m_mapCollideCallbacks.end(); ++colIt) {
		cGameCollideScript *pScript = colIt->second;
		cSaveGame_cGameCollideScript savedScript;
		savedScript.LoadFrom(pScript);

		apSavedWorld->mlstCollideCallbacks.Add(savedScript);
	}
}

//-------------------------------------------------------------------

void cPlayer::LoadSaveData(cSavedWorld *apSavedWorld) {
	// Collide callbacks
	// Collide scripts
	cContainerListIterator<cSaveGame_cGameCollideScript> colIt = apSavedWorld->mlstCollideCallbacks.GetIterator();
	while (colIt.HasNext()) {
		cSaveGame_cGameCollideScript &savedScript = colIt.Next();
		cGameCollideScript *pCallback = hplNew(cGameCollideScript, ());

		pCallback->mpEntity = mpInit->mpMapHandler->GetGameEntity(savedScript.msEntity);
		if (pCallback->mpEntity == NULL) {
			Warning("Couldn't find entity '%s'\n", savedScript.msEntity.c_str());
			hplDelete(pCallback);
			continue;
		}
		savedScript.SaveTo(pCallback);

		m_mapCollideCallbacks.insert(tGameCollideScriptMap::value_type(savedScript.msEntity, pCallback));
	}
}

//-------------------------------------------------------------------

void cPlayer::SaveToGlobal(cPlayer_GlobalSave *apSave) {
	cPlayer_GlobalSave *pData = apSave;

	//////////////////////////////
	// Stats
	kSaveData_SaveTo(mlStat_NumOfSaves);

	//////////////////////////////
	// Global
	kSaveData_SaveTo(mfForwardUpMul);
	kSaveData_SaveTo(mfForwardRightMul);

	kSaveData_SaveTo(mfUpMul);
	kSaveData_SaveTo(mfRightMul);

	kSaveData_SaveTo(mbPickAtPoint);
	kSaveData_SaveTo(mbRotateWithPlayer);
	kSaveData_SaveTo(mbUseNormalMass);

	kSaveData_SaveTo(mfGrabMassMul);

	//////////////////////////////
	// Private
	kSaveData_SaveTo(mbActive);

	kSaveData_SaveTo(mfHeightAdd);

	kSaveData_SaveTo(mfSpeedMul);
	kSaveData_SaveTo(mfHealthSpeedMul);
	kSaveData_SaveTo(mfHeadMoveSizeMul);
	kSaveData_SaveTo(mfHeadMoveSpeedMul);

	kSaveData_SaveTo(mState);
	kSaveData_SaveTo(mMoveState);
	kSaveData_SaveTo(mCrossHairState);

	kSaveData_SaveTo(mbItemFlash);
	kSaveData_SaveTo(mfHealth);
	kSaveData_SaveTo(mfPower);
	kSaveData_SaveTo(mfMass);

	///////////////////////////////////////
	// Particles on camera
	cNode3D *pNode = mpCamera->GetAttachmentNode();

	pData->mvOnCameraPS.Clear();
	cEntityIterator it = pNode->GetEntityIterator();
	while (it.HasNext()) {
		iEntity3D *pEntity3D = static_cast<iEntity3D *>(it.Next());
		if (pEntity3D->GetEntityType() == "ParticleSystem3D") {
			cParticleSystem3D *pPS = static_cast<cParticleSystem3D *>(pEntity3D);

			cPlayer_GlobalSave_CameraPS cameraPS;
			cameraPS.msName = pPS->GetName();
			cameraPS.msFile = pPS->GetDataName();
			pData->mvOnCameraPS.Add(cameraPS);
		}
	}

	//////////////////////////////
	// Lights
	apSave->mbFlashlightActive = mpFlashLight->IsActive();
	apSave->mbFlashlightDisabled = mpFlashLight->IsDisabled();
	apSave->mbGlowstickActive = mpGlowStick->IsActive();
	apSave->mbFlareActive = mpFlare->IsActive();
	apSave->mfFlareTime = mpFlare->GetTime();

	//////////////////////////////
	// Body and Camera Specific
	pData->mvPosition = mpCharBody->GetPosition();
	pData->mfYaw = mpCharBody->GetYaw();
	pData->mfPitch = mpCamera->GetPitch();
}

//-------------------------------------------------------------------

void cPlayer::LoadFromGlobal(cPlayer_GlobalSave *apSave) {
	cPlayer_GlobalSave *pData = apSave;

	//////////////////////////////
	// Stats
	kSaveData_LoadFrom(mlStat_NumOfSaves);

	//////////////////////////////
	// Global
	kSaveData_LoadFrom(mfForwardUpMul);
	kSaveData_LoadFrom(mfForwardRightMul);

	kSaveData_LoadFrom(mfUpMul);
	kSaveData_LoadFrom(mfRightMul);

	kSaveData_LoadFrom(mbPickAtPoint);
	kSaveData_LoadFrom(mbRotateWithPlayer);
	kSaveData_LoadFrom(mbUseNormalMass);

	kSaveData_LoadFrom(mfGrabMassMul);

	//////////////////////////////
	// Private
	SetActive(pData->mbActive);

	kSaveData_LoadFrom(mfHeightAdd);

	// Probably better of skipping these:
	/*kSaveData_LoadFrom(mfSpeedMul);
	kSaveData_LoadFrom(mfHealthSpeedMul);
	kSaveData_LoadFrom(mfHeadMoveSizeMul);
	kSaveData_LoadFrom(mfHeadMoveSpeedMul);*/

	// Skip these for now.
	ChangeMoveState(apSave->mMoveState, true);
	// kSaveData_LoadFrom(mState);
	// kSaveData_LoadFrom(mMoveState);
	// kSaveData_LoadFrom(mCrossHairState);

	kSaveData_LoadFrom(mbItemFlash);
	kSaveData_LoadFrom(mfHealth);
	kSaveData_LoadFrom(mfPower);
	// kSaveData_LoadFrom(mfMass); //Skip this

	///////////////////////////////////////
	// Particles on camera
	cWorld3D *pWorld = mpInit->mpGame->GetScene()->GetWorld3D();
	for (size_t i = 0; i < pData->mvOnCameraPS.Size(); ++i) {
		cParticleSystem3D *pPS = pWorld->CreateParticleSystem(
			pData->mvOnCameraPS[i].msName,
			pData->mvOnCameraPS[i].msFile,
			1, cMatrixf::Identity);
		if (pPS) {
			mpCamera->AttachEntity(pPS);
		}
	}

	//////////////////////////////
	// Lights
	if (apSave->mbFlashlightActive)
		mpFlashLight->SetActive(true);
	if (apSave->mbFlashlightDisabled)
		mpFlashLight->SetDisabled(true);
	if (apSave->mbGlowstickActive)
		mpGlowStick->SetActive(true);
	if (apSave->mbFlareActive) {
		mpFlare->SetActive(true);
		mpFlare->SetTime(apSave->mfFlareTime);
	}

	//////////////////////////////
	// Body and Camera Specific
	mpCharBody->SetPosition(pData->mvPosition);

	mpCharBody->SetYaw(pData->mfYaw);
	mpCamera->SetYaw(pData->mfYaw);
	mpCamera->SetPitch(pData->mfPitch);
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// SAVE OBJECT STUFF
//////////////////////////////////////////////////////////////////////////

//-------------------------------------------------------------------

kBeginSerializeBase(cSaveData_cPlayer)
	kSerializeVar(mlStat_NumOfSaves, eSerializeType_Int32)
		kSerializeClassContainer(mlstCollideCallbacks, cSaveGame_cGameCollideScript, eSerializeType_Class)
			kEndSerialize()

	//-----------------------------------------------------------------------

	iSaveObject *cSaveData_cPlayer::CreateSaveObject(cSaveObjectHandler *apSaveObjectHandler, cGame *apGame) {
	cContainerListIterator<cSaveGame_cGameCollideScript> it = mlstCollideCallbacks.GetIterator();
	while (it.HasNext()) {
		cSaveGame_cGameCollideScript &saveScript = it.Next();

		iGameEntity *pEntity = gpInit->mpMapHandler->GetGameEntity(saveScript.msEntity);
		if (pEntity == NULL) {
			Error("Couldn't find game entity '%s'\n", saveScript.msEntity.c_str());
			continue;
		}

		cGameCollideScript *pCallback = hplNew(cGameCollideScript, ());
		pCallback->mpEntity = pEntity;
		saveScript.SaveTo(pCallback);

		gpInit->mpPlayer->m_mapCollideCallbacks.insert(tGameCollideScriptMap::value_type(saveScript.msEntity, pCallback));
	}

	return NULL;
}

//-----------------------------------------------------------------------

int cSaveData_cPlayer::GetSaveCreatePrio() {
	return 10;
}

//-----------------------------------------------------------------------

iSaveData *cPlayer::CreateSaveData() {
	cSaveData_cPlayer *pData = hplNew(cSaveData_cPlayer, ());

	// Collide callbacks
	{
		tGameCollideScriptMapIt it = m_mapCollideCallbacks.begin();
		for (; it != m_mapCollideCallbacks.end(); ++it) {
			cGameCollideScript *pScript = it->second;
			cSaveGame_cGameCollideScript saveScript;
			saveScript.LoadFrom(pScript);
			pData->mlstCollideCallbacks.Add(saveScript);
		}
	}

	return pData;
}

//-----------------------------------------------------------------------
