/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	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/>.
*/

//
// Filename:     god.cpp
// Author:       Tom Spencer-Smith
// Date:         Dec 1998
// Description:  This class handles the creations of players and soldiers.
//

#include "god.h"

#include "cnetwork.h"
#include "playermanager.h"
#include "gameobjmanager.h"
#include "spawn.h"
#include "playertype.h"
#include "objlibrary.h"
#include "soldierobserver.h"
#include "gametype.h"
#include "dialogtests.h"
#include "combatgmode.h"
#include "gameinitmgr.h"
#include "scripts.h"
#include "debug.h"
#include "renegadedialogmgr.h"
#include "cheatmgr.h"
#include "wwmemlog.h"
#include "dialogmgr.h"
#include "encyclopediamgr.h"
#include "wolgmode.h"
#include "specialbuilds.h"
#include "demosupport.h"

/*
**
*/
typedef enum {
	GOD_STATE_UNINITIALIZED,
	GOD_STATE_MULTIPLAYER,
	GOD_STATE_EXITING,

	GOD_STATE_SINGLE_INIT,
	GOD_STATE_SINGLE_RUNNING,
	GOD_STATE_SINGLE_DEAD,

} GodState;

int		cGod::State		= GOD_STATE_UNINITIALIZED;
InventoryClass	cGod::LevelStartInventory;

//-----------------------------------------------------------------------------
enum	{
	CHUNKID_VARIABLES = 112374,

	MICROCHUNK_STATE = 1,
};

//-----------------------------------------------------------------------------
bool cGod::Save(ChunkSaveClass & csave)
{
	csave.Begin_Chunk(CHUNKID_VARIABLES);
	WRITE_MICRO_CHUNK(csave, MICROCHUNK_STATE, State);
	csave.End_Chunk();
	return true;
}

//-----------------------------------------------------------------------------
bool cGod::Load(ChunkLoadClass &cload)
{
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_VARIABLES:
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK(cload, MICROCHUNK_STATE, State);
						default:
							Debug_Say(( "Unrecognized cGod Variable chunkID\n" ));
							break;
					}
					cload.Close_Micro_Chunk();
				}
				break;

			default:
				Debug_Say(( "Unrecognized cPlayer chunkID\n" ));
				break;
		}
		cload.Close_Chunk();
	}

	return true;
}


//-----------------------------------------------------------------------------
void cGod::Think(void)
{
	WWASSERT(cNetwork::I_Am_Server());

	//if (The_Game()->IsIntermission.Is_True()) {
	WWASSERT(PTheGameData != NULL);
	if (The_Game()->IsIntermission.Is_True() ||
		 cPlayerManager::Get_Player_Object_List()->Head() == NULL) {
		return;
	}

	if ( State == GOD_STATE_UNINITIALIZED ) {
		//XXX
		State = ( IS_MISSION ) ? GOD_STATE_SINGLE_INIT : GOD_STATE_MULTIPLAYER;
	}

	if ( State == GOD_STATE_SINGLE_INIT ) {

		WWASSERT( cPlayerManager::Get_Player_Object_List()->Head() != NULL );
		// Create a Commando for the Player
		SoldierGameObj * soldier = Create_Commando( cPlayerManager::Get_Player_Object_List()->Head()->Data() );

		const char * script_name = CombatManager::Get_Start_Script();
		ScriptClass* script = ScriptManager::Create_Script( script_name );
		if (script) {
			soldier->Add_Observer( script );
		}

		State = GOD_STATE_SINGLE_RUNNING;
	}

	// This code may need to get cleaned up
	if ( State == GOD_STATE_SINGLE_RUNNING ) {
		//If we just loaded, we may have a play and a solder, but they will not be linked.
		if (cPlayerManager::Get_Player_Object_List() != NULL &&
			 cPlayerManager::Get_Player_Object_List()->Head() != NULL ) {

			cPlayer * p_player = cPlayerManager::Get_Player_Object_List()->Head()->Data();
			if ( p_player != NULL ) {
		   		SmartGameObj * p_soldier = GameObjManager::Find_Soldier_Of_Client_ID(p_player->Get_Id());
				if ( p_soldier && p_player->Get_GameObj() == NULL ) {
					// Remap
					p_soldier->Set_Player_Data( p_player );
					Debug_Say(( "Fixing up player data after load\n" ));
				}
			}
		}
	}

	DEMO_SECURITY_CHECK;

   //
   // Take a look through the player list and create commando bodies
	// for anyone who merits one
   //
	if ( State == GOD_STATE_MULTIPLAYER ) {
		for (
			SLNode<cPlayer> * objnode = cPlayerManager::Get_Player_Object_List()->Head();
			objnode;
			objnode = objnode->Next()) {

			cPlayer * p_player = objnode->Data();
			WWASSERT(p_player != NULL);

			if (p_player->Get_Is_Active().Is_False()) {
				continue;
			}

			if (p_player->Get_Is_In_Game().Is_False()) {
				continue;
			}

			SmartGameObj * p_soldier = GameObjManager::Find_Soldier_Of_Client_ID(p_player->Get_Id());

			if (p_soldier == NULL) {
				//
				// A disembodied player... give him a body
				//
				Create_Commando(p_player);
			}
		}
	}
}

//-----------------------------------------------------------------------------
cPlayer * cGod::Create_Player(int client_id, const WideStringClass & name,
															int team_choice, unsigned long clanID, bool is_invulnerable)
{
	WWMEMLOG(MEM_NETWORK);
	WWASSERT(cNetwork::I_Am_Server());
	WWASSERT(PTheGameData != NULL);
	WWASSERT(cPlayerManager::Count() < The_Game()->Get_Max_Players());

   //
	// Assign a player type
	//
	cPlayer * p_player = cPlayerManager::Find_Player(name);

	if (p_player != NULL) {
		//
		// I think this can happen when a player crashes out and then rejoins
		// before the server breaks his connection...
		//
		cNetwork::Delete_Player_Objects(p_player->Get_Id());
	} else {
		p_player = cPlayerManager::Find_Inactive_Player(name);
	}

	bool is_new = false;

	if (p_player == NULL) {
      p_player = new cPlayer();
      WWASSERT(p_player != NULL);
		is_new = true;

		p_player->Set_Name(name);
		p_player->Set_Id(client_id);
		p_player->Set_WOL_ClanID(clanID);

		int player_type = The_Game()->Choose_Player_Type(p_player, team_choice, false);
		p_player->Set_Player_Type(player_type);
	} else {
		p_player->Set_Id(client_id);
		p_player->Set_Is_In_Game(true);
		p_player->Set_Is_Waiting_For_Intermission(false);
	}

	p_player->Reset_Join_Time();
	p_player->Invulnerable.Set(is_invulnerable);
	p_player->Set_Is_Active(true);

	//
	// Tell everyone about this guy
	//
	if (is_new) {
		p_player->Init();

		GameModeClass* gameMode = GameModeManager::Find("WOL");

		if (gameMode && gameMode->Is_Active()) {
			WolGameModeClass* wolGame = reinterpret_cast<WolGameModeClass*>(gameMode);
			wolGame->Init_WOL_Player(p_player);
		}
	}

	return p_player;
}

//-----------------------------------------------------------------------------
void cGod::Create_Ai_Player(void)
{
	WWASSERT(PTheGameData != NULL);
	WWASSERT(cPlayerManager::Count() < The_Game()->Get_Max_Players());
   WWASSERT(cNetwork::I_Am_Server());

	//
	// For id, count downwards from -2.
	//
	int client_id = -1;//SmartGameObj::MOBIUS_CONTROL_OWNER;
	WideStringClass name;
	do {
		client_id--;
		WWASSERT(client_id > cPlayer::INVALID_ID);
		name.Format(L"Guard%d", -client_id);
	} while (cPlayerManager::Is_Player_Present(name));

	Create_Player(client_id, name, -1, 0);
}

//-----------------------------------------------------------------------------
SoldierGameObj * cGod::Create_Commando(int client_id, int player_type/*, int model_num*/)
{
   WWASSERT(cNetwork::I_Am_Server());
	WWASSERT(player_type >= PLAYERTYPE_NEUTRAL && player_type <= PLAYERTYPE_LAST);

	WWASSERT(PTheGameData != NULL);

	StringClass preset_name;
	preset_name.Format("Commando");

	if (IS_MISSION) {

#ifndef MULTIPLAYERDEMO
		SpawnerClass * p_spawner = SpawnManager::Get_Primary_Spawner();
		if (p_spawner != NULL) {
			const DynamicVectorClass<int>	& def_list = p_spawner->Get_Definition().Get_Spawn_Definition_ID_List();
			WWASSERT(def_list.Count() >= 1);
			PhysicalGameObjDef * p_def = (PhysicalGameObjDef *)DefinitionMgrClass::Find_Definition(def_list[0]);
			if (p_def != NULL) {
				preset_name.Format("%s", p_def->Get_Name());
			}
		}
#endif // !MULTIPLAYERDEMO

	} else if (The_Game()->Is_Cnc() || The_Game()->Is_Skirmish()) {
		if (player_type == PLAYERTYPE_NOD) {
			preset_name.Format("CnC_Nod_Minigunner_0");
		} else {
			preset_name.Format("CnC_GDI_MiniGunner_0");
		}
	}

	WWASSERT(!preset_name.Is_Empty());
	PhysicalGameObj * p_phys_obj = ObjectLibraryManager::Create_Object(preset_name);
	WWASSERT(p_phys_obj != NULL);

	SoldierGameObj * p_soldier = p_phys_obj->As_SoldierGameObj();
	WWASSERT(p_soldier != NULL);
	WWASSERT(p_soldier->Peek_Physical_Object() != NULL);

	if (IS_SOLOPLAY) {
		// Setup initial health depending on difficulty level
		float max = 100.0f;
		switch ( CombatManager::Get_Difficulty_Level() ) {
			case 0:	max = 200;	break;
			case 1:	max = 100;	break;
			case 2:	max = 75;	break;
		};
		p_soldier->Get_Defense_Object()->Set_Health_Max( max );
		p_soldier->Get_Defense_Object()->Set_Health( max );
		p_soldier->Get_Defense_Object()->Set_Shield_Strength_Max( max );
		p_soldier->Get_Defense_Object()->Set_Shield_Strength( max );
	}

	Matrix3D transform;
	if (IS_MISSION && player_type == PLAYERTYPE_GDI) {
		transform = SpawnManager::Get_Primary_Spawn_Location();
	} else {
		transform = SpawnManager::Get_Multiplayer_Spawn_Location(player_type,p_soldier);
	}
	p_soldier->Set_Transform(transform);

	p_soldier->Set_Control_Owner(client_id);
	cPlayer * player = cPlayerManager::Find_Player( client_id );
	p_soldier->Set_Player_Data( player );

	if ( The_Game()->Remember_Inventory() ) {
		if (IS_MISSION) {
			cGod::Restore_Inventory( p_soldier );
		}
	}

	p_soldier->Set_Player_Type(player_type);

	//
	// TSS082901 - We cannot remove all observers - there may be scripts granting
	// initial weapons. Instead, make sure the presets don't UseInnateBehavior
	//
	//p_soldier->Remove_All_Observers();

	if (!p_soldier->Is_Human_Controlled()) {
		//
		// Add an observer to do innate AI
		//
		p_soldier->Set_Innate_Observer(new SoldierObserverClass);
		p_soldier->Add_Observer(p_soldier->Get_Innate_Observer());
	}

	//
	// Added this 090401
	//
	p_soldier->Start_Observers();

	The_Game()->Soldier_Added(p_soldier);

	if (cNetwork::I_Am_Client() && client_id == cNetwork::Get_My_Id()) {
		ActionParamsStruct parameters;
		WWASSERT(p_soldier->Get_Action() != NULL);
		p_soldier->Get_Action()->Follow_Input(parameters);
		CombatManager::Set_The_Star(p_soldier);

		//
		//	Let the cheat manager apply its cheats to the new player
		//
		CheatMgrClass::Get_Instance()->Apply_Cheats();


#ifdef WWDEBUG
		Reinitialize_Ai_On_Star();
#endif // WWDEBUG
	}

	return p_soldier;
}

//-----------------------------------------------------------------------------
SoldierGameObj * cGod::Create_Commando(cPlayer * p_player)
{
   WWASSERT(cNetwork::I_Am_Server());
	WWASSERT(p_player != NULL);

	int client_id		= p_player->Get_Id();
	int player_type	= p_player->Get_Player_Type();
	//int model_num		= p_player->Get_Model();

	return Create_Commando(client_id, player_type/*, model_num*/);
}

//-----------------------------------------------------------------------------
void cGod::Create_Grunt(Vector3 & pos)
{
	WWASSERT(cNetwork::I_Am_Server());

	int client_id		= SmartGameObj::SERVER_CONTROL_OWNER;

	WWASSERT(PTheGameData != NULL);
	int player_type	= The_Game()->Choose_Player_Type(NULL, -1, true);
	//int model_num		= rand() % NUM_MP_PLAYABLE_MODELS;

	SoldierGameObj * p_soldier = Create_Commando(
		client_id, player_type/*, model_num*/);
	WWASSERT(p_soldier != NULL);

	p_soldier->Set_Position(pos);
	p_soldier->Perturb_Position();
}

//-----------------------------------------------------------------------------
#ifdef WWDEBUG
void cGod::Reinitialize_Ai_On_Star(void)
{
	WWASSERT(cNetwork::I_Am_Client());

	SmartGameObj * p_my_soldier = GameObjManager::Find_Soldier_Of_Client_ID(cNetwork::Get_My_Id());

	if (p_my_soldier != NULL) {

		//
		// Remove any innate observers
		//
		const GameObjObserverList & observer_list = p_my_soldier->Get_Observers();
		for (int index = 0; index < observer_list.Count(); index++) {
			if (!stricmp(observer_list[index]->Get_Name(), "Innate Soldier")) {
				p_my_soldier->Remove_Observer(observer_list[index]);
				break; // probably not safe to continue
			}
		}

		cPlayer * p_player = cNetwork::Get_My_Player_Object();
		WWASSERT(p_player != NULL);

		ActionParamsStruct parameters;
		WWASSERT(p_my_soldier->Get_Action() != NULL);
		p_my_soldier->Get_Action()->Follow_Input(parameters);

		CombatManager::Set_Is_Star_Determining_Target(true);
	}
}
#endif // WWDEBUG

//-----------------------------------------------------------------------------
Matrix3D		_StarRespawnTM;
InventoryClass	_DeathInventory;

void cGod::Reset( void )
{
	State = GOD_STATE_UNINITIALIZED;
}

void cGod::Exit( void )
{
	State = GOD_STATE_EXITING;
}

void cGod::Star_Killed( void )
{
	if ( State == GOD_STATE_SINGLE_RUNNING ) {
		State = GOD_STATE_SINGLE_DEAD;
		WWDEBUG_SAY(( "Star Killed\n" ));

		_DeathInventory.Store_Inventory( COMBAT_STAR );

		_StarRespawnTM = COMBAT_STAR->Get_Transform();

		DeathOptionsPopupClass * popup = new DeathOptionsPopupClass;
		popup->Start_Dialog();
		popup->Release_Ref();
	} else if ( State == GOD_STATE_MULTIPLAYER ) {

		if (GameModeManager::Find ("Combat")->Is_Active ()) {
			if (GameModeManager::Find ("Menu")->Is_Active ()) {
				GameModeManager::Find ("Menu")->Deactivate ();
			} else {
				DialogMgrClass::Flush_Dialogs ();
			}
		}
	}
}

void cGod::Respawn( void )
{
	WWASSERT( State == GOD_STATE_SINGLE_DEAD );
	SoldierGameObj * soldier = Create_Commando( cPlayerManager::Get_Player_Object_List()->Head()->Data() );
	soldier->Set_Transform( _StarRespawnTM );
	_DeathInventory.Restore_Inventory( soldier );

	const char * script_name = CombatManager::Get_Respawn_Script();
	ScriptClass* script = ScriptManager::Create_Script( script_name );
	if (script) {
		soldier->Add_Observer( script );
	}

	State = GOD_STATE_SINGLE_RUNNING;
}

void cGod::Restart( void )
{
	if ( State == GOD_STATE_SINGLE_DEAD ) {
//		WWASSERT( State == GOD_STATE_SINGLE_DEAD );

		State = GOD_STATE_SINGLE_RUNNING;	// Incase we get a second call!
		((CombatGameModeClass *)GameModeManager::Find("Combat"))->Core_Restart();

		//
		//	Reset the player's stats
		//
		cPlayer *player_info = cPlayerManager::Get_Player_Object_List()->Head()->Data();
		if ( player_info != NULL ) {
			player_info->Stats_Reset();
		}

		SoldierGameObj * soldier = Create_Commando( player_info );

		const char * script_name = CombatManager::Get_Start_Script();
		ScriptClass* script = ScriptManager::Create_Script( script_name );
		if (script) {
			soldier->Add_Observer( script );
		}
		State = GOD_STATE_SINGLE_RUNNING;	// restart makes it exit

	}
}

void cGod::Load_Game( void )
{
	WWASSERT( State == GOD_STATE_SINGLE_DEAD );
	GameInitMgrClass::End_Game();
	RenegadeDialogMgrClass::Goto_Location (RenegadeDialogMgrClass::LOC_LOAD_GAME);
}


void cGod::Mission_Failed( void )
{
	if ( State == GOD_STATE_SINGLE_RUNNING ) {
		State = GOD_STATE_SINGLE_DEAD;
		WWDEBUG_SAY(( "Mission Failed\n" ));

		FailedOptionsPopupClass * popup = new FailedOptionsPopupClass;
		popup->Start_Dialog();
		popup->Release_Ref();
	}
}


void cGod::Store_Inventory( SoldierGameObj * soldier )
{
	LevelStartInventory.Store_Inventory( soldier );
	EncyclopediaMgrClass::Store_Data();
	return ;
}

void cGod::Restore_Inventory( SoldierGameObj * soldier )
{
	LevelStartInventory.Restore_Inventory( soldier );
	EncyclopediaMgrClass::Restore_Data();
	return ;
}

void cGod::Reset_Inventory( void )
{
	LevelStartInventory.Reset();
}


