// Xmas event script
// Author: GeckoN

int g_nGiftCnt;

const string GIFT_TARGET_PREFIX = "_Xmas_Gift_";
const int GIFT_TARGET_PREFIX_LEN = 11;

void PluginInit()
{
	g_Module.ScriptInfo.SetAuthor( "Sven Co-op Development Team" );
	g_Module.ScriptInfo.SetContactInfo( "www.svencoop.com" );
	
	InitReplacements();
	
	g_Hooks.RegisterHook( Hooks::PickupObject::Materialize, @PickupObjectMaterialize );
}

HookReturnCode PickupObjectMaterialize( CBaseEntity@ pEntity )
{
	if ( ( pEntity.pev.spawnflags & SF_CREATEDWEAPON ) != 0 )
	{
		g_Game.AlertMessage( at_console, ">> Entity \"%1\" materialized. Not mapper placed.\n", pEntity.pev.classname );
		return HOOK_CONTINUE;
	}
	
	string strTarget = pEntity.pev.target;
	if ( !strTarget.IsEmpty() && strTarget.CompareN( GIFT_TARGET_PREFIX, GIFT_TARGET_PREFIX_LEN ) == 0 )
	{
		CBaseEntity@ pGift = g_EntityFuncs.FindEntityByTargetname( null, strTarget );
		if ( pGift is null )
		{
			g_Game.AlertMessage( at_console, ">> Entity \"%1\" materialized. Gift not found.\n", pEntity.pev.classname );
			return HOOK_CONTINUE;
		}	
		pGift.pev.effects &= ~EF_NODRAW;
		g_EntityFuncs.SetModel( pEntity, "" );
		g_Game.AlertMessage( at_console, ">> Entity \"%1\" materialized. Making gift visible.\n", pEntity.pev.classname );
		return HOOK_HANDLED;
	}
	
	int nId = g_ReplacementMgr.FindMatch( pEntity.pev.classname, false );
	CModelReplacement @pRepl = g_ReplacementMgr.Get( nId );
	pRepl.Apply( pEntity );
	
	g_Game.AlertMessage( at_console, ">> Entity \"%1\" materialized. Creating gift.\n", pEntity.pev.classname );
	return HOOK_HANDLED;
}

void MapInit()
{
	g_nGiftCnt = 0;
	RegisterPointXmasGiftEntity();
	
	g_Game.PrecacheModel( MODEL_GIFT );
	
	for ( uint32 i = 0; i < GIFT_PICKUP_SOUNDS.length(); i++ )
		g_SoundSystem.PrecacheSound( GIFT_PICKUP_SOUNDS[ i ] );
}

void MapActivate()
{
	//g_ReplacementMgr.ReplaceModels();
}

void InitReplacements()
{
	// Wildcard entries
	g_ReplacementMgr.Add( CModelReplacement( "weapon_*",				MODEL_GIFT,		GIFT_MEDIUM ) );
	g_ReplacementMgr.Add( CModelReplacement( "ammo_*",					MODEL_GIFT,		GIFT_SMALL ) );
	
	// Entities that should retain their original model
	g_ReplacementMgr.Add( CModelReplacement( "weapon_minigun",			"" ) );
	g_ReplacementMgr.Add( CModelReplacement( "ammo_spore",				"" ) );
	
	// Per-class entries
	g_ReplacementMgr.Add( CModelReplacement( "weaponbox",				MODEL_GIFT,		GIFT_MEDIUM,	1.5 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_egon",				MODEL_GIFT,		GIFT_MEDIUM,	2.0 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_grapple",			MODEL_GIFT,		GIFT_MEDIUM,	2.0 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_crossbow",			MODEL_GIFT,		GIFT_MEDIUM,	2.0 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_displacer",		MODEL_GIFT,		GIFT_MEDIUM,	2.0 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_snark",			MODEL_GIFT,		GIFT_MEDIUM,	2.0 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_pipewrench",		MODEL_GIFT,		GIFT_LARGE,		0.75 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_crowbar",			MODEL_GIFT,		GIFT_LARGE,		0.75 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_shotgun",			MODEL_GIFT,		GIFT_LARGE ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_m16",				MODEL_GIFT,		GIFT_LARGE ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_m249",				MODEL_GIFT,		GIFT_LARGE ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_saw",				MODEL_GIFT,		GIFT_LARGE ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_sniperrifle",		MODEL_GIFT,		GIFT_LARGE ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_gauss",			MODEL_GIFT,		GIFT_LARGE ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_shockrifle",		MODEL_GIFT,		GIFT_LARGE ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_rpg",				MODEL_GIFT,		GIFT_LARGE,		1.25 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_hornetgun",		MODEL_GIFT,		GIFT_LARGE,		1.15 ) );
	g_ReplacementMgr.Add( CModelReplacement( "weapon_sporelauncher",	MODEL_GIFT,		GIFT_LARGE,		1.25 ) );
	//g_ReplacementMgr.Add( CModelReplacement( "weapon_minigun",			MODEL_GIFT,		GIFT_LARGE,		1.25 ) );
}

const string MODEL_GIFT = "models/xmas_gifts.mdl";

//const int GIFT_NUM_PICKUP_SOUNDS = 3;
const array<string> GIFT_PICKUP_SOUNDS = 
{
	"gift1.ogg",
	"gift2.ogg",
	"gift3.ogg"
};

const int GIFT_SKINS = 15;

enum GIFT_SUBMODELS
{
	GIFT_SMALL = 0,
	GIFT_MEDIUM,
	GIFT_LARGE,
};

class CModelReplacement
{
	string m_strClassname;
	string m_strModel;
	int m_nSubmodel;
	float m_fScale;
	
	CModelReplacement()
	{
	}
	
	CModelReplacement( const string& in strClassname, const string& in strModel = "", int nSubmodel = 0, float fScale = 1.0 )
	{
		m_strClassname = strClassname;
		m_strModel = strModel;
		m_nSubmodel = nSubmodel;
		m_fScale = fScale;
	}
	
	bool IsWildcard()
	{
		int len = m_strClassname.Length();
		if ( len == 0 )
			return false;
		
		return m_strClassname[ len - 1 ] == '*';
	}
	
	void Apply( CBaseEntity@ ent )
	{
		string strName = GIFT_TARGET_PREFIX + g_nGiftCnt;
		
		
		
		
		CBaseEntity@ pEntity = g_EntityFuncs.CreateEntity( "point_xmas_gift", null, false );
		pEntity.pev.targetname = strName;
		pEntity.pev.maxs = ent.pev.maxs;
		pEntity.pev.mins = ent.pev.mins;
		pEntity.pev.origin = ent.pev.origin;
		pEntity.pev.angles = ent.pev.angles;
		pEntity.pev.target = ent.pev.target;
		
		ent.pev.target = strName;
		//ent.pev.effects |= EF_NODRAW;
		g_EntityFuncs.SetModel( ent, "" );
		
		
		g_EntityFuncs.DispatchSpawn( pEntity.edict() );
		
		//pEntity.pev.movetype = ent.pev.movetype;
		
		// Submodel (this is bit dirty but there is only one bodygroup so eh...)
		
		pEntity.pev.body = m_nSubmodel;
		
		// Random skin
		
		pEntity.pev.skin = Math.RandomLong( 0, GIFT_SKINS );

		// Animate
		
		/*CBaseAnimating @anim = cast<CBaseAnimating>(pEntity);
		if ( anim !is null )
		{
			anim.pev.sequence = 1;
			anim.pev.frame = 0;
			anim.ResetSequenceInfo();
		}*/
		
		// Slight yaw variance
		
		float fAngleVar = 0.0;
		
		switch ( m_nSubmodel )
		{
		case GIFT_SMALL: fAngleVar = 20.0; break;
		case GIFT_MEDIUM: fAngleVar = 16.0; break;
		case GIFT_LARGE: fAngleVar = 8.0; break;
		}
		
		float fAngleVarMul = 1.0 - Math.clamp( 0.0, 1.0, m_fScale - 1.0 );
		fAngleVar *= fAngleVarMul;
		pEntity.pev.angles[1] += Math.RandomFloat( -fAngleVar, +fAngleVar );
		
		// Scale
		
		if ( pEntity.pev.scale == 0 )
			pEntity.pev.scale = 1.0;
		pEntity.pev.scale *= m_fScale;
		// Slight size variance
		//pEntity.pev.scale += Math.RandomFloat( -0.085, +0.085 );
		
		g_nGiftCnt++;
	}
		
		
	void Apply2( CBaseEntity@ ent )
	{
		if ( ent.pev.model == m_strModel )
			return;
		
		// Model
		
		g_EntityFuncs.SetModel( ent, m_strModel );
		// iuser2 is used to define entity-specific weapon model
		ent.pev.iuser2 = int( string_t( m_strModel ) );
		
		// Submodel (this is bit dirty but there is only one bodygroup so eh...)
		
		ent.pev.body = m_nSubmodel;
		
		// Random skin
		
		ent.pev.skin = Math.RandomLong( 0, GIFT_SKINS );
		
		// Random color
		
		/*int nTopColor = Math.RandomLong( 0, 255 );
		int nBottomColor = Math.RandomLong( 0, 255 );
		ent.pev.colormap = ( nBottomColor << 8 ) + nTopColor;*/
		
		// Slight yaw variance
		
		float fAngleVar = 0.0;
		
		switch ( m_nSubmodel )
		{
		case GIFT_SMALL: fAngleVar = 20.0; break;
		case GIFT_MEDIUM: fAngleVar = 16.0; break;
		case GIFT_LARGE: fAngleVar = 8.0; break;
		}
		
		float fAngleVarMul = 1.0 - Math.clamp( 0.0, 1.0, m_fScale - 1.0 );
		fAngleVar *= fAngleVarMul;
		ent.pev.angles[1] += Math.RandomFloat( -fAngleVar, +fAngleVar );
		
		// Scale
		
		if ( ent.pev.scale == 0 )
			ent.pev.scale = 1.0;
		ent.pev.scale *= m_fScale;
		// Slight size variance
		//ent.pev.scale += Math.RandomFloat( -0.085, +0.085 );
	}
};

class CModelReplacementManager
{
	array<CModelReplacement@> g_replacements;
	
	void Add( CModelReplacement @item )
	{
		g_replacements.insertLast( item );
	}
	
	CModelReplacement @ Get( int nId )
	{
		if ( nId < 0 || nId >= int( g_replacements.length() ) )
			return null;
		
		return g_replacements[ nId ];
	}
	
	int FindMatch( const string& in strClassname, bool bExactMatch )
	{
		for ( uint32 i = 0; i < g_replacements.length(); i++ )
		{
			CModelReplacement @item = g_replacements[ i ];
			if ( !bExactMatch && item.IsWildcard() )
			{
				int nLen = item.m_strClassname.Length() - 1;
				if ( item.m_strClassname.CompareN( strClassname, nLen ) == 0 && FindMatch( strClassname, true ) < 0 )
					return int( i );
			}
			else
			{
				if ( strClassname == item.m_strClassname )
					return int( i );
			}
		}
		
		return -1;
	}
	
	void ReplaceModels()
	{
		for ( uint32 i = 0; i < g_replacements.length(); i++ )
		{
			ReplaceModel( g_replacements[ i ] );
		}
	}
	
	void ReplaceModel( CModelReplacement @item )
	{
		CBaseEntity@ ent = null;
		int nCount = 0;
		
		bool bWild = item.IsWildcard();
		
		while( ( @ent = g_EntityFuncs.FindEntityByClassname( ent, item.m_strClassname ) ) !is null )
		{
			// Exception, do not replace!
			if ( item.m_strModel.IsEmpty() )
				continue;
				
			// Wildcard classname? Skip exact matches!
			if ( bWild && FindMatch( ent.pev.classname, true ) >= 0 )
				continue;

			item.Apply( ent );
			nCount++;
		}
		
		//g_Game.AlertMessage( at_console, "Classname: \"%1\", Model \"%2\", Replaced: %3\n", strClassname, strModel, nCount );
	}
};

CModelReplacementManager g_ReplacementMgr;

const int SEQ_IDLE = 0;
const int SEQ_SHAKE = 1;

/*const int ACTIVITY_NOT_AVAILABLE = -1;

class CActAnimating : ScriptBaseAnimating
{
private Activity	m_Activity;

	void SetActivity( Activity act )
	{
		int sequence = self.LookupActivity( act ); 
		if ( sequence != ACTIVITY_NOT_AVAILABLE )
		{
			pev.sequence = sequence;
			m_Activity = act; 
			pev.frame = 0;
			self.ResetSequenceInfo( );
		}
	}
	
	Activity GetActivity() { return m_Activity; }
	
};*/

class point_xmas_gift : ScriptBaseAnimating
{
	
	bool KeyValue( const string& in szKey, const string& in szValue )
	{
		return BaseClass.KeyValue( szKey, szValue );
	}
	
	// If youre gonna use this in your script, make sure you don't try
	// to access invalid animations. -zode
	void SetAnim( int animIndex ) 
	{
		self.pev.sequence = animIndex;
		self.pev.frame = 0;
		self.ResetSequenceInfo();
	}
	
	int GetAnim()
	{
		return self.pev.sequence;
	}
	
	void Precache()
	{
		BaseClass.Precache();
		
		// Allow for custom models
		if( string( self.pev.model ).IsEmpty() )
			g_Game.PrecacheModel( MODEL_GIFT );
		else
			g_Game.PrecacheModel( self.pev.model );
		
		for ( uint32 i = 0; i < GIFT_PICKUP_SOUNDS.length(); i++ )
			g_SoundSystem.PrecacheSound( GIFT_PICKUP_SOUNDS[ i ] );
	}
	
	void Spawn()
	{
		Precache();
		
		self.pev.movetype 		= MOVETYPE_TOSS;//MOVETYPE_NONE;
		self.pev.solid 			= SOLID_TRIGGER; //SOLID_NOT;
		
		self.pev.framerate 		= 1.0f;
		
		// Enabled by default
		self.pev.health			= 1.0f;
		
		// Not activated by default
		self.pev.frags			= 0.0f;
		
		// Allow for custom models
		if( string( self.pev.model ).IsEmpty() )
			g_EntityFuncs.SetModel( self, MODEL_GIFT );
		else
			g_EntityFuncs.SetModel( self, self.pev.model );
		
		g_EntityFuncs.SetOrigin( self, self.pev.origin );
		
		// Custom hull size
		/*if( self.pev.vuser1 != g_vecZero && self.pev.vuser2 != g_vecZero )
			g_EntityFuncs.SetSize( self.pev, self.pev.vuser1, self.pev.vuser2 );
		else
			g_EntityFuncs.SetSize( self.pev, Vector( -64, -64, -36 ), Vector( 64, 64, 36 ) );*/
			
		SetAnim( 1 ); // set sequence to 0 aka idle
		
		self.pev.movetype = MOVETYPE_TOSS;
		self.pev.solid = SOLID_TRIGGER;
		g_EntityFuncs.SetSize( self.pev, self.pev.mins, self.pev.maxs);
		g_EntityFuncs.SetOrigin( self, self.pev.origin );
			
		//SetThink( ThinkFunction( this.IdleThink ) );
		self.pev.nextthink = g_Engine.time + 0.1f;
	}
	
	void Touch( CBaseEntity@ pOther )
	{
		//g_SoundSystem.EmitSound( self.edict(), CHAN_STATIC, "../media/valve.mp3", 1.0f, ATTN_NONE );

		/*self.pev.rendermode		= kRenderTransTexture;
		self.pev.renderamt		= 255;*/
		
		/*SetThink( null );
		self.pev.nextthink = g_Engine.time + 0.1f;
		
		// Trigger targets
		self.SUB_UseTargets( pOther, USE_TOGGLE, 0 );*/
	}
	
	void Use( CBaseEntity@ pActivator, CBaseEntity@ pCaller, USE_TYPE useType, float value )
	{
		if ( useType == USE_TOGGLE )
		{
			int nSample = Math.RandomLong( 0, GIFT_PICKUP_SOUNDS.length() - 1 );
			g_SoundSystem.EmitSound( pActivator.edict(), CHAN_STATIC, GIFT_PICKUP_SOUNDS[ nSample ], 1.0f, ATTN_NONE );
			
			self.SUB_UseTargets( pActivator, USE_TOGGLE, 0 );
			//SetThink( ThinkFunction( ThinkRemove ) );
			self.pev.effects |= EF_NODRAW;
			pev.nextthink = g_Engine.time + 0.1;
		}
	}
	
	void ThinkRemove( void )
	{
		//g_Game.AlertMessage( at_console, "ThinkRemove\n" );
		self.SUB_Remove();
	}
	
	// GeckoN: Idle Think - just to make sure the animation gets updated properly.
	// Should fix the "checkpoint jitter" issue.
	void IdleThink()
	{
		self.StudioFrameAdvance();
		self.pev.nextthink = g_Engine.time + 0.1;

		/*switch( GetAnim() )
		{
		case SEQ_SHAKE:
			if ( self.m_fSequenceFinished )
				SetAnim( SEQ_IDLE );
			break;

		case SEQ_IDLE:
			if ( Math.RandomLong( 0, 10 ) < 2 )
				SetAnim( SEQ_SHAKE );
			break;
			
		default:
			break;
		}*/
	}
}

void RegisterPointXmasGiftEntity()
{
	g_CustomEntityFuncs.RegisterCustomEntity( "point_xmas_gift", "point_xmas_gift" );
}
