/* AMXModX Script

   Title:    LAN Notifier
   Author:   Brad Jones

   Current Version:   1.0
   Release Date:      2006-02-22

   This plugin will prepend "Ln:" to the front of players' names when they are on 
   the same LAN where "n" is an integer signifying which LAN group they belong to.

   You can set whether this plugin should look for an exact IP match 
   (xxx.xxx.xxx.xxx = yyy.yyy.yyy.yyy) or just a match of the first 3 octets
   (xxx.xxx.xxx.* = yyy.yyy.yyy.*).  My recommendation and the default setting
   is to look at just the first 3 octets.


   INSTALLATION

      File Locations:
      
         .\plugins\lann.amxx

      Enabled Modules:

         regex


   OPTIONS (CVARS)

	 	  lann_exact_match <0|1>
   
	       Determines whether to look at all 4 octets of an IP address or just the first 3.
	       
	       0: xxx.xxx.xxx.* = yyy.yyy.yyy.*
	       1: xxx.xxx.xxx.xxx = yyy.yyy.yyy.yyy

         The default is 0.
         

   COMPILATION
   Most people will have no need for this section.   
   
      File Locations:
      
         .\scripting\lann.sma
         

   CHANGE LOG:

      2006-02-22   1.0   Initial release.   
*/


#include <amxmodx>
#include <regex>

new PLUGIN_ABBR[] = "LANN";

#define SHIFT_8  256
#define SHIFT_16 65536
#define SHIFT_24 16777216

#define SUBMASK_24BIT 4294967040
#define SUBMASK_32BIT 4294967295

#define MAX_PLAYERS      32
#define LEN_CHANGEREASON 127
#define LEN_PLAYERNAME   31

new g_ip[MAX_PLAYERS+1];
new g_lanID[MAX_PLAYERS+1];

new g_changeReason[MAX_PLAYERS+1][LEN_CHANGEREASON+1];
new g_validatedName[MAX_PLAYERS+1][LEN_PLAYERNAME+1];

public plugin_init()
{
	new pluginVersion[] = "1.0";
	
	register_plugin("LAN Notifier", pluginVersion, "Brad Jones");
	register_cvar("lann_version", pluginVersion, FCVAR_SERVER|FCVAR_SPONLY);

	register_dictionary("lann.txt");

	register_event("ResetHUD", "event_resethud", "be")

	register_cvar("lann_exact_match", "0");  // 0: xxx.xxx.xxx.* = yyy.yyy.yyy.*  1: xxx.xxx.xxx.xxx = yyy.yyy.yyy.yyy
}

public client_putinserver(id)
{
	set_player_ip(id);
	new bool:isExactMatch = set_lan_status(id);
	validate_name(id, isExactMatch);

	return PLUGIN_CONTINUE;
}        

public client_infochanged(id)
{
	// if we've already validated their name at least once
	if (g_validatedName[id][0])
	{
		// grab their name
		new name[LEN_PLAYERNAME+1];
		get_user_info(id, "name", name, LEN_PLAYERNAME);
		
		// validate name if their name is not the same as the one we previously validated
		if (!equali(g_validatedName[id], name))
			validate_name(id, false);
	}
	return PLUGIN_CONTINUE;
} 

public client_disconnect(id)
{
	g_ip[id] = 0;
	g_validatedName[id][0] = 0;
	g_changeReason[id][0] = 0;
	clear_lan_status(id);
	
	return PLUGIN_CONTINUE;
}

public event_resethud(id)
{
	// if we renamed the player, let them know why
	if (!g_changeReason[id][0]) display_change_reason(id);
}

display_change_reason(id)
{
	client_print(id, print_chat, g_changeReason[id]);
	g_changeReason[id][0] = 0;
}

set_player_ip(id)
{
	// grab the player's IP string
	new ip[16];
	get_user_ip(id, ip, 15, 1);
	
	// store the ip in integer form
	g_ip[id] = str_to_ip(ip);
}

bool:set_lan_status(id)
{
	new bool:isMatch = false;
	new bool:isExactMatch = false;
	
	for (new otherID = 1; otherID <= 32; otherID++)
	{
		if ((otherID != id) && (g_ip[otherID] != 0))
		{
			// check if first 3 octets match
			isMatch = ((g_ip[otherID] & SUBMASK_24BIT) == (g_ip[id] & SUBMASK_24BIT))
			if (isMatch)
			{
				// check if all 4 octets match
				isExactMatch = ((g_ip[otherID] & SUBMASK_32BIT) == (g_ip[id] & SUBMASK_32BIT))
			
				if (get_cvar_num("lann_exact_match") && !isExactMatch) return false;
			
				// assign the LAN ids			
				if (g_lanID[otherID] == 0)
				{
					g_lanID[otherID] = get_free_lan_id();
					validate_name(otherID, isExactMatch);
				}
				g_lanID[id] = g_lanID[otherID];
				break;
			}
		}
	}
	return isExactMatch;
}

clear_lan_status(id)
{
	if (g_lanID[id])
	{
		// determine how many players are left on the LAN
		new lanID_cnt, otherLanPlayerID;
		
		for (new otherID = 1; otherID <= 32; otherID++)
		{
			if (otherID != id && g_lanID[otherID] == g_lanID[id])
			{
				lanID_cnt++;
				if (lanID_cnt > 1) break;
				otherLanPlayerID = otherID;
			}
		}
		
		// if there was only one other person on the same LAN, their LAN tag can also be removed
		if (lanID_cnt == 1)
		{
			g_lanID[otherLanPlayerID] = 0;
			validate_name(otherLanPlayerID, false);
		}
		
		// clear this player's LAN status
		g_lanID[id] = 0;
	}
}

validate_name(id, bool:isExactMatch = false)
{
	if (is_user_hltv(id) || is_user_bot(id)) return;

	// get the name to validate	
	new name[LEN_PLAYERNAME+1];
	get_user_info(id, "name", name, LEN_PLAYERNAME);
	
	// check for LAN tag issues
	new bool:tagRemoved, bool:tagAdded
	new lanTag[32];
	new matchCnt, error[5];
	new Regex:regexID
	regexID = regex_match(name, "\A(L(\d{1,2}):)+", matchCnt, error, 4);

	new bool:hasTag = (regexID >= REGEX_OK);

	// deal with adding or removing tag as needed
	if (hasTag)
	{
		regex_substr(regexID, 0, lanTag, 31);

		new s_lanID[3], lanID;
		regex_substr(regexID, matchCnt-1, s_lanID, 2);
		lanID = str_to_num(s_lanID);

		if (g_lanID[id] == 0 || lanID != g_lanID[id] || matchCnt > 1)
		{
			remove_lan_tag(lanTag, name);
			tagRemoved = true;
			hasTag = false;
		}

		regex_free(regexID);
	}

	if (hasTag == false && g_lanID[id])
	{
		add_lan_tag(g_lanID[id], name);
		tagAdded = true;
	}

	if (tagRemoved || tagAdded) // fix the player's name
	{	
		// store the player's name so that we don't keep checking the same name
		copy(g_validatedName[id], LEN_PLAYERNAME, name);

		// set the player's name to the fixed version
		set_user_info(id, "name", name);

		// let the player know why we altered their name
		if (tagRemoved && tagAdded)
			format(g_changeReason[id], LEN_CHANGEREASON, "[%s] %L", PLUGIN_ABBR, id, (isExactMatch) ? "TAG_CHANGED" : "TAG_CHANGED_FUZZY", g_lanID[id]);
		else if (tagRemoved)
			format(g_changeReason[id], LEN_CHANGEREASON, "[%s] %L", PLUGIN_ABBR, id, "TAG_REMOVED", lanTag);
		else if (tagAdded)
			format(g_changeReason[id], LEN_CHANGEREASON, "[%s] %L", PLUGIN_ABBR, id, (isExactMatch) ? "TAG_ADDED" : "TAG_ADDED_FUZZY", g_lanID[id]);

		// if the player is alive, show the reason immediately, otherwise we wait until their next respawn
		if (is_user_alive(id))
			display_change_reason(id);
	}
}

remove_lan_tag(const lanTag[], name[])
{
	// trim the tag from the player's name
	new startPos = strlen(lanTag);
	copy(name, LEN_PLAYERNAME, name[startPos]);
}

add_lan_tag(lanID, name[])
{
	// add the tag to the player's name
	format(name, LEN_PLAYERNAME, "L%i:%s", lanID, name);
}


// copied from bugblatter's 'name management' plugin for adminMod
str_to_ip(Data[])
{
	new sepIP[4][10];
	new numIP[4];

	if ( explode(sepIP, Data, '.', 15, 4) != 4)
	{
		return 0;
	}
	new i;
	for (i=0;i<4;i++)
	{
		numIP[i]=str_to_num(sepIP[i]);
		if ((numIP[i]<0) || (numIP[i]>255))
		{
			return 0;
		}
	}
	return numIP[0] * SHIFT_24 + numIP[1] * SHIFT_16 + numIP[2] * SHIFT_8 + numIP[3];
}

// copied from bugblatter's 'name management' plugin for adminMod
/* Returns the lowest positive integer number that doesn't appear in g_lanID */
get_free_lan_id()
{
	new i;
	new r=1;

	for(i = 1; i < 32; i++)
	{
		if (g_lanID[i]==r)
		{
			i=0; /* Slightly hacky, but hey */
			r++;
		}
	}
	return r;
}

// copied from bugblatter's 'name management' plugin for adminMod
explode( output[][], input[], delimiter, textlen, maxMatches ) 
{
	new nIdx = 0;
	new nLen = (1 + copyc( output[nIdx], textlen, input, delimiter ));
	new len = strlen(input);

	while( nLen < len && nIdx < maxMatches )
		nLen += (1 + copyc( output[++nIdx], textlen, input[nLen], delimiter ));
        
	return nIdx + 1;
}