//============================================================
// TalkSpriteTracker.uc		- Handles the main implementation of managing talking icons
//============================================================
//	TalkingIcon
//		- A simple mutator for visibly notifying players when someone is typing.
//			Coded on request for Captain_Tea
//
//	Copyright (C) 2008 John "Shambler" Barrett (JBarrett847@Gmail.com or Shambler@OldUnreal.com)
//
//	This program is free software; you can redistribute and/or modify
//	it under the terms of the Open Unreal Mod License version 1.1.
//
//============================================================
//
// Replicates texture data from the server to the client, and
// then uses a clientside timer to monitor the console of the
// player, which notifies the server when the player is typing
// (Epic never finished implementing this in UT).
//
// Also handles the creation and removal of sprite components,
// which includes attaching them to players who are currently
// typing.
//
//============================================================
Class TalkSpriteTracker extends ReplicationInfo
	config(TalkingIcon);


// *** Replicated variables
var Texture2D ServerDefaultTalkTexture;
var Texture2D ServerRedTalkTexture;
var Texture2D ServerBlueTalkTexture;



// *** Clientside Variables

// Config variables
var config bool bDisableMyTalkIcon; // Prevents this player from displaying any talk icons (this may break if UTPawn.bIsTyping is properly implemented in a later UT patch)
var config bool bDisableTalkIcons; // Disables displaying of any talk icons for this player

var config bool bCustomTalkTextures;	// Allows the client to override the talk textures (much like how the server can too, except this can override the server as well)
var config string CustomTalkTexture;
var config string CustomTalkTextureRed;
var config string CustomTalkTextureBlue;


// Runtime variables
var Texture2D DefaultTalkTexture;
var Texture2D RedTalkTexture;
var Texture2D BlueTalkTexture;

var PlayerController LocalPlayer;
var Console LocalConsole;

var bool bWasTyping;

var GameReplicationInfo GRI;


struct TypingPlayer
{
	var UTPawn Player;
	var SpriteComponent TalkSprite;
};

var array<TypingPlayer> TypingPlayers;
var array<SpriteComponent> CachedTalkSprites;



// *** Replication block
replication
{
	if (bNetInitial && Role == ROLE_Authority)
		ServerDefaultTalkTexture, ServerRedTalkTexture, ServerBlueTalkTexture;
}


// *** General functions

simulated function PostBeginPlay()
{
	local Texture2d CurTex;

	if (WorldInfo.NetMode != NM_DedicatedServer)
	{
		// Load custom talk icon textures (if any) and assign the talk icon textures
		if (bCustomTalkTextures)
		{
			if (CustomTalkTexture != "")
			{
				CurTex = Texture2D(DynamicLoadObject(CustomTalkTexture, Class'Texture2D'));

				if (CurTex != none)
					DefaultTalkTexture = CurTex;
			}

			if (CustomTalkTextureRed != "")
			{
				CurTex = Texture2D(DynamicLoadObject(CustomTalkTextureRed, Class'Texture2D'));

				if (CurTex != none)
					RedTalkTexture = CurTex;
			}

			if (CustomTalkTextureBlue != "")
			{
				CurTex = Texture2D(DynamicLoadObject(CustomTalkTextureBlue, Class'Texture2D'));

				if (CurTex != none)
					BlueTalkTexture = CurTex;
			}
		}


		// Setup the main timer
		SetTimer(0.1, True);


		// If any of the textures have not been set, wait for the default versions to be replicated from the server
		if (DefaultTalkTexture == none || RedTalkTexture == none || BlueTalkTexture == none)
			SetTimer(0.01, True, 'TextureTimer');


		// Called to generate the config file on clients
		SaveConfig();
	}
}

// I can initialize the custom textures in PostBeginPlay without problems, but not the default replicated ones
simulated function TextureTimer()
{
	if (DefaultTalkTexture == none && ServerDefaultTalkTexture != none)
		DefaultTalkTexture = ServerDefaultTalkTexture;

	if (RedTalkTexture == none && ServerRedTalkTexture != none)
		RedTalkTexture = ServerRedTalkTexture;

	if (BlueTalkTexture == none && ServerBlueTalkTexture != none)
		BlueTalkTexture = ServerBlueTalkTexture;


	if (DefaultTalkTexture != none && RedTalkTexture != none && BlueTalkTexture != none)
		ClearTimer('TextureTimer');
}

// This clientside timer serves two purposes:
//	1: To monitor the players console and route a call through 'PlayerController::ServerMutate' to 'UTPlayerController::Typing' when the player is typing
//	2: To monitor other players 'bIsTyping' value, and attach/detach talking icon sprites when appropriate
simulated function Timer()
{
	local PlayerController PC;
	local bool bIsTyping, bResetStr;
	local int i;
	local UTPawn CurPawn;
	local TypingPlayer NewEntry;

	// Cache references to the local player and that players console
	if (LocalConsole == none)
	{
		foreach LocalPlayerControllers(Class'PlayerController', PC)
		{
			if (PC.Player != none && LocalPlayer(PC.Player) != none && LocalPlayer(PC.Player).ViewportClient != none
				&& LocalPlayer(PC.Player).ViewportClient.ViewportConsole != none)
			{
				LocalPlayer = PC;
				LocalConsole = LocalPlayer(PC.Player).ViewportClient.ViewportConsole;

				break;
			}
		}
	}

	// Check the current clients typing status
	if (LocalConsole != none)
	{
		bIsTyping = LocalConsole.IsInState('Typing') || LocalConsole.IsInState('Open');

		if (!bDisableMyTalkIcon && bIsTyping != bWasTyping)
		{
			bWasTyping = bIsTyping;

			// Since this will be calling ServerMutate frequently, I use Chr(1) to keep down the string length (it's a character not likely to be typed)
			LocalPlayer.ServerMutate(Chr(1)$int(bIsTyping));
		}


		// Monitor for console commands specific to this mutator (this is a nice little hack :))
		if (bIsTyping && LocalConsole.TypedStr != "")
		{
			if (LocalConsole.TypedStr ~= "DisableMyTalkIcon")
			{
				bResetStr = True;

				if (!bDisableMyTalkIcon)
				{
					bDisableMyTalkIcon = True;
					LocalPlayer.ClientMessage("Other players will no longer see a talking icon when you are typing");

					SaveConfig();
				}
				else
				{
					LocalPlayer.ClientMessage("Your talking icon is already disabled");
				}
			}
			else if (LocalConsole.TypedStr ~= "EnableMyTalkIcon")
			{
				bResetStr = True;

				if (bDisableMyTalkIcon)
				{
					bDisableMyTalkIcon = False;
					LocalPlayer.ClientMessage("Other players will now see a talking icon over your head, when you are typing");

					SaveConfig();
				}
				else
				{
					LocalPlayer.ClientMessage("Your talking icon is already enabled");
				}
			}
			else if (LocalConsole.TypedStr ~= "DisableTalkingIcons")
			{
				bResetStr = True;

				if (!bDisableTalkIcons)
				{
					bDisableTalkIcons = True;
					LocalPlayer.ClientMessage("You will no longer see talking icons over other players heads");

					SaveConfig();
				}
				else
				{
					LocalPlayer.ClientMessage("You already have talking icons disabled");
				}
			}
			else if (LocalConsole.TypedStr ~= "EnableTalkingIcons")
			{
				bResetStr = True;

				if (bDisableTalkIcons)
				{
					bDisableTalkIcons = False;
					LocalPlayer.ClientMessage("You will now see a talking icon over a players head when he/she is typing");

					SaveConfig();
				}
				else
				{
					LocalPlayer.ClientMessage("You already have talking icons enabled");
				}
			}
			else if (Left(LocalConsole.TypedStr, 11) ~= "TalkingIcon")
			{
				bResetStr = True;

				LocalPlayer.ClientMessage("Commands for the TalkingIcon mutator:");
				LocalPlayer.ClientMessage("---");
				LocalPlayer.ClientMessage("DisableMyTalkIcon: Disables the display of a talk icon over your head, when you are typing");
				LocalPlayer.ClientMessage("EnableMyTalkIcon: Re-enables the display of a talk icon above your head");
				LocalPlayer.ClientMessage("DisableTalkingIcons: Disables the display of all other players talk icons");
				LocalPlayer.ClientMessage("EnableTalkingIcons: Re-enables the display of all talk icons");
				LocalPlayer.ClientMessage("TalkingIcon: This command");
				LocalPlayer.ClientMessage("---");
				LocalPlayer.ClientMessage("Coded by John 'Shambler' Barrett");
			}


			if (bResetStr)
			{
				LocalConsole.TypedStr = "";
				LocalConsole.TypedStrPos = 0;
			}
		}
	}

	// If the player disabled his talk icon while typing, then make sure to notify the server (so as to turn off the players talk icon)
	if (bDisableMyTalkIcon && bWasTyping)
	{
		bWasTyping = False;
		LocalPlayer.ServerMutate(Chr(1)$"0");
	}


	// Clear outdated entries within the 'TypingPlayers' array
	for (i=0; i<TypingPlayers.Length; ++i)
	{
		CurPawn = (TypingPlayers[i].Player != none ? TypingPlayers[i].Player : None);

		if (bDisableTalkIcons || CurPawn == none || !CurPawn.bIsTyping || CurPawn.bFeigningDeath || CurPawn.bIsInvisible || CurPawn.Health <= 0)
		{
			if (TypingPlayers[i].TalkSprite != none)
				ReleaseTalkSprite(TypingPlayers[i].TalkSprite, CurPawn);

			TypingPlayers.Remove(i--, 1);
		}
	}

	// Check the typing status of all players
	if (!bDisableTalkIcons && DefaultTalkTexture != none && RedTalkTexture != none && BlueTalkTexture != none)
	{
		foreach WorldInfo.AllPawns(Class'UTPawn', CurPawn)
		{
			if (CurPawn.PlayerReplicationInfo != none && !CurPawn.PlayerReplicationInfo.bOnlySpectator && CurPawn.bIsTyping && !CurPawn.bFeigningDeath
				&& !CurPawn.bIsInvisible && CurPawn.Health > 0 && TypingPlayers.Find('Player', CurPawn) == -1)
			{
				NewEntry.Player = CurPawn;
				NewEntry.TalkSprite = AttachTalkSprite(CurPawn);

				TypingPlayers.AddItem(NewEntry);
			}
		}
	}
}

simulated function ReleaseTalkSprite(SpriteComponent Sprite, UTPawn Target)
{
	if (Target != none)
	{
		Target.DetachComponent(Sprite);

		// Restore the targets team beacons
		Target.LastPostRenderTraceTime = 0;
	}

	CachedTalkSprites.AddItem(Sprite);
	Sprite.SetHidden(True);
}

simulated function SpriteComponent AttachTalkSprite(UTPawn Target)
{
	local SpriteComponent CurSprite;
	local int i;

	// Reuse any cached sprites
	if (CachedTalkSprites.Length > 0)
	{
		i = CachedTalkSprites.Length - 1;

		CurSprite = CachedTalkSprites[i];
		CachedTalkSprites.Length = i;

		CurSprite.SetHidden(False);
	}

	// If there are no available cached sprites, create a new one
	if (CurSprite == none)
	{
		CurSprite = new Class'SpriteComponent';
		CurSprite.SetScale(0.125);
	}


	// Cache a reference to the GameReplicationInfo (in order to determine if it's a team game)
	if (GRI == none)
		foreach AllActors(Class'GameReplicationInfo', GRI)
			break;


	// Set the sprites textures
	if (Class<UTTeamGame>(GRI.GameClass) == none)
		CurSprite.Sprite = DefaultTalkTexture;
	else if (Target.GetTeamNum() == 0)
		CurSprite.Sprite = RedTalkTexture;
	else
		CurSprite.Sprite = BlueTalkTexture;


	// Attach the sprite, and offset towards the targets head
	Target.AttachComponent(CurSprite);

	CurSprite.SetTranslation(((Target.Mesh.GetBoneLocation(Target.HeadBone) - Target.Location) * vect(0,0,1)) + vect(0,0,50));


	// Disable the targets team beacons temporarily
	Target.LastPostRenderTraceTime = MAXINT;
	Target.bPostRenderTraceSucceeded = False;


	return CurSprite;
}

defaultproperties
{
	bDisableMyTalkIcon=False
}