/*	Copyright � 2008, ConnorMcLeod

	func_door 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.

	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 func_door; if not, write to the
	Free Software Foundation, Inc., 59 Temple Place - Suite 330,
	Boston, MA 02111-1307, USA.
*/

#define CHATCOLOR
#define MAKE_DOORS_SILENT

#include <amxmodx>
#include <amxmisc>
#include <engine>
#include <fakemeta>
#include <hamsandwich>
#include <xs>

new const VERSION[] = "1.1.2";

#pragma semicolon 1

#define SetIdBits(%1,%2)		%1 |= 1<<(%2 & 31)
#define ClearIdBits(%1,%2)	%1 &= ~( 1<<(%2 & 31) )
#define GetIdBits(%1,%2)		%1 &  1<<(%2 & 31)

#define SetEntBits(%1,%2)	%1[%2>>5] |=  1<<(%2 & 31)
#define ClearEntBits(%1,%2)	%1[%2>>5] &= ~( 1 << (%2 & 31) )
#define GetEntBits(%1,%2)	%1[%2>>5] &   1<<(%2 & 31)

enum _:BlocksClasses {
	FUNC_DOOR,
	FUNC_WALL_TOGGLE,
	FUNC_BUTTON,
	TRIGGER_MULTIPLE
}

new const Float:VEC_DUCK_HULL_MIN[3]	= {-16.0, -16.0, -18.0 };
new const Float:VEC_DUCK_HULL_MAX[3]	= { 16.0,  16.0,  32.0 };
new const Float:VEC_DUCK_VIEW[3]		= {  0.0,   0.0,  12.0 };

new const Float:VEC_NULL[3]	= { 0.0, 0.0, 0.0 };

const PLAYERS_ARRAY_SIZE = 33;
const MAX_ENTSARRAYS_SIZE = 64; // x * 32 // 2048 (should be 1800 on default servers settings)

new g_bitPresentClass;

const KEYS = ((1<<0)|(1<<1)|(1<<9));

new g_iBlock[ PLAYERS_ARRAY_SIZE ];
new Float:g_flFirstTouch[ PLAYERS_ARRAY_SIZE ];

new Float:g_flJumpOrigin[ PLAYERS_ARRAY_SIZE ][3];
new Float:g_flJumpAngles[ PLAYERS_ARRAY_SIZE ][3];
new Float:g_flJumpGravity[ PLAYERS_ARRAY_SIZE ];

new g_bBlocks[MAX_ENTSARRAYS_SIZE], g_bBlocksByPlugin[MAX_ENTSARRAYS_SIZE];
new g_bOnGround, g_bTeleported, g_bAdmin;

new bool:g_bBlockEntityTouch;
new bool:g_bActive;
new bool:g_bSafeInform = true;

new g_iFhAddToFullPack;
new g_iAdminDoor[PLAYERS_ARRAY_SIZE];
new szConfigFile[64];

new Trie:g_iBlocksClass;

new g_iMaxPlayers, g_iMaxEnts;
#define IsPlayer(%1)	( 1 <= %1 <= g_iMaxPlayers )

public plugin_init() {
	register_plugin("MultiPlayer Bhop", VERSION, "ConnorMcLeod");
	new pCvar = register_cvar("mp_bhop_version", VERSION, FCVAR_SERVER|FCVAR_EXTDLL|FCVAR_SPONLY);
	set_pcvar_string(pCvar, VERSION);

	new const szPossibleBlockClass[][] = {"func_door", "func_wall_toggle", "func_button", "trigger_multiple"};
	g_iBlocksClass = TrieCreate();
	for(new i; i<sizeof(szPossibleBlockClass); i++)
		TrieSetCell(g_iBlocksClass, szPossibleBlockClass[i], i);

	register_concmd("kz_mpbhop", "ConCmd_MpBhop", ADMIN_CFG, "<0/1> set blocks so they can't move when players touch them");
	register_concmd("kz_mpbhop_entitytouch", "ConCmd_EntityTouch", ADMIN_CFG, "<0/1> set blocks so they can't move when other entities than players touch them");
	register_concmd("kz_safe_inform", "ClCmd_SafeInform", ADMIN_CFG, "<0/1> Inform recorders that their demo will be safe or not safe according to plugin state");

	register_clcmd("kz_mpbhopmenu", "ClCmd_BhopMenu", ADMIN_CFG);
	register_clcmd("kz_showblocks", "ClCmd_ShowBlocks", ADMIN_CFG);

	register_clcmd("fullupdate", "ClCmd_FullUpdate");

	register_menucmd(register_menuid("MpBhop Menu"), KEYS ,"MpBhopMenuAction");

	g_iMaxPlayers = get_maxplayers();
	g_iMaxEnts = global_get(glb_maxEntities);

	new iCount;

	iCount += Set_Doors();
	iCount += Set_Wall_Toggle();
	iCount += Set_Buttons();

	iCount += SetBlocksByFile();

	server_print("[MPBHOP] %d bhop blocks detected", iCount);

	SetTriggerMultiple();
}

public plugin_cfg() {
	new szConfigPath[96];
	get_localinfo("amxx_configsdir", szConfigPath, charsmax(szConfigPath));
	format(szConfigPath, charsmax(szConfigPath), "%s/mpbhop.cfg", szConfigPath);

	if(file_exists(szConfigPath)) {
		new buffer[2], n;
		read_file(szConfigPath, 0, buffer, charsmax(buffer), n);
		if(buffer[0] == ';')
			goto ForceWrite;
		server_cmd("exec %s", szConfigPath);
		server_exec();
	}
	else {
ForceWrite:
		new fp = fopen(szConfigPath, "wt");
		if(!fp)
			return;
		new szPluginFileName[96], szPluginName[64], szAuthor[32], szVersion[32], szStatus[2];
		new iPlugin = get_plugin(-1,
					szPluginFileName, charsmax(szPluginFileName),
					szPluginName, charsmax(szPluginName),
					szVersion, charsmax(szVersion),
					szAuthor, charsmax(szAuthor),
					szStatus, charsmax(szStatus) );

		// server_print("Plugin id is %d", iPlugin);
		fprintf(fp, "// ^"%s^" configuration file^n", szPluginName);
		fprintf(fp, "// Author : ^"%s^"^n", szAuthor);
		fprintf(fp, "// Version : ^"%s^"^n", szVersion);
		fprintf(fp, "// File : ^"%s^"^n", szPluginFileName);

		new iMax, i, szCommand[64], iCommandAccess, szCmdInfo[128], szFlags[32];
		iMax = get_concmdsnum(-1, -1);
		fprintf(fp, "^n// Console Commands :^n");
		for(i=0; i<iMax; i++) {
			if(get_concmd_plid(i, -1, -1) == iPlugin) {
				get_concmd(i,
					szCommand, charsmax(szCommand),
					iCommandAccess,
					szCmdInfo, charsmax(szCmdInfo),
					-1, -1);
				get_flags(iCommandAccess, szFlags, charsmax(szFlags));
				fprintf(fp, "// %s | Access:^"%s^" | ^"%s^"^n", szCommand, szFlags, szCmdInfo);
			}
		}

		iMax = get_plugins_cvarsnum();
		new iTempId, iPcvar, szCvarName[256], szCvarValue[128];
		fprintf(fp, "^n// Cvars :^n");
		for(new i; i<iMax; i++)
		{
			get_plugins_cvar(i, szCvarName, charsmax(szCvarName), _, iTempId, iPcvar);
			if( iTempId == iPlugin )
			{
				get_pcvar_string(iPcvar, szCvarValue, charsmax(szCvarValue));
				fprintf(fp, "// %s ^"%s^"^n", szCvarName, szCvarValue);
			}
		}

		fclose(fp);
	}
}

public ClCmd_FullUpdate( id )
{
	if( g_bSafeInform )
	{
		if( g_bActive )
		{
			client_print(id, print_console, "MpBhop is Activated, recording is NOT SAFE");
#if defined CHATCOLOR
			client_print_color(id, print_chat, "^1 * ^4[MpBhop] ^1MpBhop is ^4Activated^1, recording is ^3NOT SAFE");
#else
			client_print(id, print_chat, "MpBhop is Activated, recording is NOT SAFE");
#endif
		}
		else
		{
			client_print(id, print_console, "MpBhop is De-Activated, recording is SAFE");
#if defined CHATCOLOR
			client_print_color(id, print_chat, "^1 * ^4[MpBhop] ^1MpBhop is ^3De-Activated^1, recording is ^4SAFE");
#else
			client_print(id, print_chat, "MpBhop is De-Activated, recording is SAFE");
#endif
		}
	}
}

public ClCmd_SafeInform(id, lvl, cid)
{
	if( cmd_access(id, lvl, cid, 2) )
	{
		new szStatus[2];
		read_argv(1, szStatus, charsmax(szStatus));
		g_bSafeInform = !!str_to_num(szStatus);
	}
	return PLUGIN_HANDLED;
}

public client_putinserver(id)
{
	if( get_user_flags(id) & ADMIN_CFG )
	{
		SetIdBits(g_bAdmin, id);
	}
	else
	{
		ClearIdBits(g_bAdmin, id);
	}
	ClearIdBits(g_bOnGround, id);
	ClearIdBits(g_bTeleported, id);
}

public client_disconnected(id)
{
	ClearIdBits(g_bAdmin, id);
	ClearIdBits(g_bOnGround, id);
	ClearIdBits(g_bTeleported, id);
}

public ClCmd_ShowBlocks(id, level, cid)
{
	if( cmd_access(id, level, cid, 2) )
	{
		new szStatus[2];
		read_argv(1, szStatus, charsmax(szStatus));
		if( szStatus[0] == '1' )
		{
			if( !g_iFhAddToFullPack )
			{
				g_iFhAddToFullPack = register_forward(FM_AddToFullPack, "AddToFullPack", 1);
				client_print(id, print_console, "Recording with this feature Activated is NOT SAFE");
#if defined CHATCOLOR
				client_print_color(id, print_chat, "^1 * ^4[MpBhop] ^1Recording with this feature ^4Activated ^1is ^3NOT SAFE");
#else
				client_print(id, print_chat, "Recording with this feature Activated is NOT SAFE");
#endif
			}
		}
		else
		{
			if( g_iFhAddToFullPack )
			{
				unregister_forward(FM_AddToFullPack, g_iFhAddToFullPack, 1);
				g_iFhAddToFullPack = 0;
			}
		}
	}
	return PLUGIN_HANDLED;
}

public MpBhopMenuAction(id, iKey)
{
	new iEnt = g_iAdminDoor[id];

	switch( iKey )
	{
		case 0:
		{
			if( GetEntBits(g_bBlocks, iEnt) )
			{
				ClearEntBits(g_bBlocks, iEnt);
#if defined CHATCOLOR
				client_print_color(id, id, "^1 * ^4[MpBhop] ^1Block has been set ^4movable^1.");
#else
				client_print(id, print_chat, " * [MpBhop] Block has been set movable.");
#endif
			}
			else
			{
#if defined CHATCOLOR
				client_print_color(id, id, "^1 * ^4[MpBhop] ^1Block is already ^4movable^1.");
#else
				client_print(id, print_chat, " * [MpBhop] Block is already movable.");
#endif
			}
		}
		case 1:
		{
			if( GetEntBits(g_bBlocks, iEnt) )
			{
#if defined CHATCOLOR
				client_print_color(id, print_chat, "^1 * ^4[MpBhop] ^1Block is already ^3unmovable^1.");
#else
				client_print(id, print_chat, " * [MpBhop] Block is already unmovable.");
#endif
			}
			else
			{
				SetEntBits(g_bBlocks, iEnt);
#if defined CHATCOLOR
				client_print_color(id, print_chat, "^1 * ^4[MpBhop] ^1Block has been set ^3unmovable^1.");
#else
				client_print(id, print_chat, " * [MpBhop] Block has been set unmovable.");
#endif
				if( g_bActive )
				{
					SetTouch(true);
				}
			}
		}
	}
	return PLUGIN_HANDLED;
}

ShowMpBhopMenu(id, bIsBlocked)
{
	new szMenuBody[150];

	formatex(szMenuBody, charsmax(szMenuBody), "\rMpBhop Menu^n\dThis block is actually \
		\y%smovable \d:^n^n\r1\w. Mark this block as movable^n\r2\w. Mark this block as \
		unmovable^n^n\r0\w. Exit", bIsBlocked ? "un" : "");

	show_menu(id, KEYS, szMenuBody, _, "MpBhop Menu");
}

public ClCmd_BhopMenu(id, level, cid)
{
	if( cmd_access(id, level, cid, 1) )
	{
		new iEnt, crap, iClassType;
		get_user_aiming(id, iEnt, crap);
		if( iEnt && pev_valid(iEnt) )
		{
			new szClassName[32];
			pev(iEnt, pev_classname, szClassName, charsmax(szClassName));
			if( TrieGetCell(g_iBlocksClass, szClassName, iClassType) )
			{
				g_bitPresentClass |= 1<<iClassType;
				g_iAdminDoor[id] = iEnt;
				ShowMpBhopMenu(id, !!(GetEntBits(g_bBlocks, iEnt)));
			}
		}
	}
	return PLUGIN_HANDLED;
}

public AddToFullPack(es, e, iEnt, id, hostflags, player, pSet)
{
	if( !player && GetIdBits(g_bAdmin, id) && GetEntBits(g_bBlocks, iEnt) )
	{
		set_es(es, ES_RenderMode, kRenderTransColor);
		set_es(es, ES_RenderAmt, 150);
		set_es(es, ES_RenderColor, {120,75,170});
		set_es(es, ES_RenderFx, kRenderFxGlowShell);
	}
}

public CBasePlayer_PreThink(id)
{
	if( is_user_alive(id) )
	{
		if( GetIdBits(g_bTeleported, id) )
		{
			ClearIdBits(g_bTeleported, id);
			set_pev(id, pev_velocity, VEC_NULL);
			return;
		}

		static fFlags;
		fFlags = pev(id, pev_flags);
		if( fFlags & FL_ONGROUND )
		{
			static iEnt, Float:flVelocity[3], Float:flVecOrigin[3];
			iEnt = pev(id, pev_groundentity);
			if( !iEnt || !(GetEntBits(g_bBlocks, iEnt)) )
			{
				if( iEnt )
				{
					pev(iEnt, pev_velocity, flVelocity);
					if( flVelocity[0] || flVelocity[1] || flVelocity[2] )
					{
						ClearIdBits(g_bOnGround, id);
						return;
					}
				}

				if( fFlags & FL_DUCKING )
				{
					pev(id, pev_origin, flVecOrigin);
					flVecOrigin[2] += 18.0;
					if( !trace_hull(flVecOrigin, HULL_HUMAN, id, IGNORE_MONSTERS) )
					{
						flVecOrigin[2] -= 18.0;
						xs_vec_copy(flVecOrigin, g_flJumpOrigin[id]);
						SetIdBits(g_bOnGround, id);
					}
					else
					{
						ClearIdBits(g_bOnGround, id);
						return;
					}
				}
				else
				{
					pev(id, pev_origin, g_flJumpOrigin[id]);
					SetIdBits(g_bOnGround, id);
				}
			}
			else
			{
				ClearIdBits(g_bOnGround, id);
			}
		}
		else if( GetIdBits(g_bOnGround, id) )
		{
			ClearIdBits(g_bOnGround, id);
			pev(id, pev_v_angle, g_flJumpAngles[id]);
			pev(id, pev_gravity, g_flJumpGravity[id]);
		}
	}
}

public TriggerMultiple_Touch(iEnt, id)
{
	if( (IsPlayer(id) || g_bBlockEntityTouch) && GetEntBits(g_bBlocks, iEnt) )
	{
		return HAM_SUPERCEDE;
	}
	return HAM_IGNORED;
}

public Touch_Block(iBlock, id)
{
	if( !(GetEntBits(g_bBlocks, iBlock)) )
	{
		return HAM_IGNORED;
	}

	if( IsPlayer(id) )
	{
		if( !is_user_alive(id) )
		{
			return HAM_SUPERCEDE;
		}
	}
	else
	{
		return g_bBlockEntityTouch ? HAM_SUPERCEDE : HAM_IGNORED;
	}

	if( pev(id, pev_groundentity) != iBlock )
	{
		return HAM_SUPERCEDE;
	}

	if( g_iBlock[id] != iBlock )
	{
		g_iBlock[id] = iBlock;
		g_flFirstTouch[id] = get_gametime();
		return HAM_SUPERCEDE;
	}

	static Float:flTime;
	flTime = get_gametime();

	if( flTime - g_flFirstTouch[id] > 0.25 ) // 0.3 == exploit on cg_cbblebhop oO
	{
		if( flTime - g_flFirstTouch[id] > 0.7 )
		{
			g_flFirstTouch[id] = flTime;
			return HAM_SUPERCEDE;
		}

		Util_TeleportPlayerBack( id );
	}
	return HAM_SUPERCEDE;
}

Util_TeleportPlayerBack(id)
{
	SetIdBits(g_bTeleported, id); // apply null velocity on next PreThink()

	new flags = pev(id, pev_flags);
	if( flags & FL_BASEVELOCITY )
	{
		flags &= ~FL_BASEVELOCITY;
		set_pev(id, pev_basevelocity, VEC_NULL);
	}
	set_pev(id, pev_velocity, VEC_NULL);

	set_pev(id, pev_flags, flags | FL_DUCKING);

	engfunc(EngFunc_SetSize, id, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX);
	engfunc(EngFunc_SetOrigin, id, g_flJumpOrigin[id]);
	set_pev(id, pev_view_ofs, VEC_DUCK_VIEW);

	set_pev(id, pev_v_angle, VEC_NULL);
	set_pev(id, pev_angles, g_flJumpAngles[id]);
	set_pev(id, pev_punchangle, VEC_NULL);
	set_pev(id, pev_fixangle, 1);

	set_pev(id, pev_gravity, g_flJumpGravity[id]);

	set_pev(id, pev_fuser2, 0.0);
}

public ConCmd_MpBhop(id, lvl, cid)
{
	if( cmd_access(id, lvl, cid, 2) )
	{
		new szStatus[2];
		read_argv(1, szStatus, charsmax(szStatus));
		static HamHook:iHhPlayerPreThink;
		switch( szStatus[0] )
		{
			case '0':
			{
				if( !g_bActive )
				{
					return PLUGIN_HANDLED;
				}
				if( iHhPlayerPreThink )
				{
					DisableHamForward( iHhPlayerPreThink );
				}
				SetTouch( false );
				g_bActive = false;

				if( g_bSafeInform )
				{
					client_print(0, print_console, "MpBhop has been De-Activated, recording is now SAFE");
#if defined CHATCOLOR
					client_print_color(0, print_chat, "^1 * ^4[MpBhop] ^1MpBhop has been ^3De-Activated^1, recording is now ^4SAFE");
#else
					client_print(id, print_chat, " * [MpBhop] MpBhop has been De-Activated, recording is now SAFE");
#endif
				}
			}
			case '1':
			{
				if( g_bActive )
				{
					return PLUGIN_HANDLED;
				}
				if( !iHhPlayerPreThink )
				{
					RegisterHam(Ham_Player_PreThink, "player", "CBasePlayer_PreThink");
				}
				else
				{
					EnableHamForward( iHhPlayerPreThink );
				}
				SetTouch( true );
				g_bActive = true;
				if( g_bSafeInform )
				{
					client_print(0, print_console, "MpBhop has been Activated, recording is now NOT SAFE");
#if defined CHATCOLOR
					client_print_color(0, print_chat, "^1 * ^4[MpBhop] ^1MpBhop has been ^4Activated^1, recording is now ^3NOT SAFE");
#else
					client_print(id, print_chat, " * MpBhop has been Activated, recording is now NOT SAFE");
#endif
				}
			}
			default:
			{
				client_print(id, print_console, "Usage: kz_mpbhop <0/1>");
			}
		}
	}
	return PLUGIN_HANDLED;
}

public ConCmd_EntityTouch(id, lvl, cid)
{
	if( cmd_access(id, lvl, cid, 2) )
	{
		new szStatus[2];
		read_argv(1, szStatus, charsmax(szStatus));
		g_bBlockEntityTouch = !!str_to_num(szStatus);
	}
	return PLUGIN_HANDLED;
}

Set_Doors()
{
	new iDoor = FM_NULLENT, i;
	new Float:flMovedir[3], szNoise[32], Float:flSize[3], Float:flDmg, Float:flSpeed;
	new const szNull[] = "common/null.wav";
	while( (iDoor = find_ent_by_class( iDoor, "func_door")) )
	{
		// definitly not a bhop block
		pev(iDoor, pev_dmg, flDmg);
		if( flDmg )
		{
#if defined MAKE_DOORS_SILENT
			set_pev(iDoor, pev_noise1, szNull); // while here, set healing doors silent xD
			set_pev(iDoor, pev_noise2, szNull);
			set_pev(iDoor, pev_noise3, szNull);
#endif
			continue;
		}

		// this func_door goes UP, not a bhop block ?
		// or bhop block but let them move up (kz_megabhop for example)
		pev(iDoor, pev_movedir, flMovedir);
		if( flMovedir[2] > 0.0 )
		{
			continue;
		}

		// too small : real door ? could this one be skipped ?
		pev(iDoor, pev_size, flSize);
		if( ( flSize[0] < 24.0 && flSize[1] > 50.0 )
		||( flSize[1] < 24.0 && flSize[0] > 50.0 ) )
		{
			continue;
		}

		// real door ? not all doors make sound though...
		pev(iDoor, pev_noise1, szNoise, charsmax(szNoise));
		if( szNoise[0] && !equal(szNoise, szNull) )
		{
			continue;
		}
		pev(iDoor, pev_noise2, szNoise, charsmax(szNoise));
		if( szNoise[0] && !equal(szNoise, szNull) )
		{
			continue;
		}

		// not a bhop block ? too slow // this at least detects the big ent on kzsca_sewerbhop
		pev(iDoor, pev_speed, flSpeed);
		if( flSpeed < 80.0 )
		{
			continue;
		}

		// Pray for this to be a bhop block
		SetEntBits(g_bBlocksByPlugin, iDoor);
		SetEntBits(g_bBlocks, iDoor);
		g_bitPresentClass |= 1<<FUNC_DOOR;
		i++;
	}
	return i;
}

Set_Wall_Toggle()
{
	new iEnt = FM_NULLENT, i;
	while( (iEnt = find_ent_by_class(iEnt,"func_wall_toggle")) )
	{
		g_bitPresentClass |= 1<<FUNC_WALL_TOGGLE;
		SetEntBits(g_bBlocksByPlugin, iEnt);
		SetEntBits(g_bBlocks, iEnt);
		i++;
	}
	return i;
}

Set_Buttons()
{
	new const szStartStopButtons[][] = {
		"counter_start", "clockstartbutton", "firsttimerelay", "gogogo", "multi_start","counter_start_button", "startcounter", 
		"counter_off", "clockstop", "clockstopbutton", "multi_stop", "stop_counter", "stopcounter" };

	new Trie:tButtons = TrieCreate();

	for(new i; i<sizeof(szStartStopButtons); i++)
	{
		TrieSetCell(tButtons, szStartStopButtons[i], 1);
	}

	new iEnt = FM_NULLENT, i, szTarget[32], spawnflags;
	while( (iEnt = find_ent_by_class(iEnt,"func_button")) )
	{
		
		spawnflags = pev(iEnt, pev_spawnflags);
		if( spawnflags & (SF_BUTTON_DONTMOVE|SF_BUTTON_TOGGLE|SF_BUTTON_TOUCH_ONLY) == SF_BUTTON_TOUCH_ONLY )
		{
			pev(iEnt, pev_target, szTarget, charsmax(szTarget));
			if( !szTarget[0] || !TrieKeyExists(tButtons, szTarget))
			{
				pev(iEnt, pev_targetname, szTarget, charsmax(szTarget));
				if( !szTarget[0] || !TrieKeyExists(tButtons, szTarget))
				{
					g_bitPresentClass |= 1<<FUNC_BUTTON;
					SetEntBits(g_bBlocksByPlugin, iEnt);
					SetEntBits(g_bBlocks, iEnt);
					i++;
				}
			}
		}
#if defined MAKE_DOORS_SILENT
		if( spawnflags & SF_BUTTON_SPARK_IF_OFF )
		{
			set_pev(iEnt, pev_spawnflags, spawnflags & ~SF_BUTTON_SPARK_IF_OFF);
		}
#endif
	}
	TrieDestroy(tButtons);
	return i;
}

SetTouch(bool:bActive)
{
	static HamHook:iHhBlockTouch[BlocksClasses];
	if( bActive )
	{
		static const szClassesAndHandlers[BlocksClasses][][] = {
			{"func_door", "Touch_Block"},
			{"func_wall_toggle", "Touch_Block"},
			{"func_button", "Touch_Block"},
			{"trigger_multiple", "TriggerMultiple_Touch"}
		};
		for(new i; i<sizeof(iHhBlockTouch); i++)
		{
			if( g_bitPresentClass & (1<<i) )
			{
				if( iHhBlockTouch[i] )
				{
					EnableHamForward( iHhBlockTouch[i] );
				}
				else
				{
					iHhBlockTouch[i] = RegisterHam(Ham_Touch, szClassesAndHandlers[i][0], szClassesAndHandlers[i][1]);
				}
			}
		}
	}
	else
	{
		for(new i; i<sizeof(iHhBlockTouch); i++)
		{
			if( g_bitPresentClass & (1<<i) && iHhBlockTouch[i] )
			{
				DisableHamForward( iHhBlockTouch[i] );
			}
		}
	}
}

SetBlocksByFile()
{
	get_localinfo("amxx_datadir", szConfigFile, charsmax(szConfigFile));
	format(szConfigFile, charsmax(szConfigFile), "%s/mpbhop", szConfigFile);
	if( !dir_exists(szConfigFile) )
	{
		mkdir(szConfigFile);
	}

	new szMapName[32];
	get_mapname(szMapName, charsmax(szMapName));
	format(szConfigFile, charsmax(szConfigFile), "%s/%s.dat", szConfigFile, szMapName);

	new iFile = fopen(szConfigFile, "rt"), i;
	if( iFile )
	{
		new szDatas[48], szBrushOrigin[3][13], szType[2], Float:flBrushOrigin[3], i, iEnt;
		new szClassName[32], iClassType;
		while( !feof(iFile) )
		{
			fgets(iFile, szDatas, charsmax(szDatas));
			trim(szDatas);
			if(!szDatas[0] || szDatas[0] == ';' || szDatas[0] == '#' || (szDatas[0] == '/' && szDatas[1] == '/'))
			{
				continue;
			}

			parse(szDatas, szBrushOrigin[0], 12, szBrushOrigin[1], 12, szBrushOrigin[2], 12, szType, charsmax(szType));
			for(i=0; i<3; i++)
			{
				flBrushOrigin[i] = str_to_float( szBrushOrigin[i] );
			}

			iEnt = FindEntByBrushOrigin( flBrushOrigin );
			if( iEnt )
			{
				if( szType[0] == '1' )
				{
					pev(iEnt, pev_classname, szClassName, charsmax(szClassName));
					if( TrieGetCell(g_iBlocksClass, szClassName, iClassType) )
					{
						g_bitPresentClass |= 1<<iClassType;
					}
					if( ~GetEntBits(g_bBlocks, iEnt) )
					{
						i++;
					}
					SetEntBits(g_bBlocks, iEnt);
				}
				else
				{
					if( GetEntBits(g_bBlocks, iEnt) )
					{
						i--;
					}
					ClearEntBits(g_bBlocks, iEnt);
				}
			}
		}
		fclose(iFile);
	}
	return i;
}

FindEntByBrushOrigin(Float:flOrigin[3])
{
	new Float:flBrushOrigin[3];
	for( new iEnt=g_iMaxPlayers+1; iEnt<=g_iMaxEnts; iEnt++ )
	{
		if( pev_valid(iEnt) )
		{
			fm_get_brush_entity_origin(iEnt, flBrushOrigin);
			if( xs_vec_nearlyequal(flBrushOrigin, flOrigin) )
			{
				return iEnt;
			}
		}
	}
	return 0;
}

fm_get_brush_entity_origin(ent, Float:orig[3])
{
	new Float:Min[3], Float:Max[3];

	pev(ent, pev_origin, orig);
	pev(ent, pev_mins, Min);
	pev(ent, pev_maxs, Max);

	orig[0] += (Min[0] + Max[0]) * 0.5;
	orig[1] += (Min[1] + Max[1]) * 0.5;
	orig[2] += (Min[2] + Max[2]) * 0.5;

	return 1;
}

SetTriggerMultiple()
{
	new iEnt = FM_NULLENT, szTarget[32], iBlock;
	while( (iEnt = find_ent_by_class(iEnt,"trigger_multiple")) )
	{
		pev(iEnt, pev_target, szTarget, charsmax(szTarget));
		iBlock = find_ent_by_tname(FM_NULLENT, szTarget);
		if( iBlock && GetEntBits(g_bBlocks, iBlock) )
		{
			g_bitPresentClass |= 1<<TRIGGER_MULTIPLE;
			SetEntBits(g_bBlocksByPlugin, iEnt);
			SetEntBits(g_bBlocks, iEnt);
		}
	}
}

public plugin_end()
{
	TrieDestroy(g_iBlocksClass);
	delete_file(szConfigFile);

	new iFile;

	new Float:flBrushOrigin[3], bool:bUnMovable;
	for(new iEnt=g_iMaxPlayers+1; iEnt<=g_iMaxEnts; iEnt++)
	{
		if( pev_valid(iEnt) )
		{
			bUnMovable = !!( GetEntBits(g_bBlocks, iEnt) );
			if(	bUnMovable
			!=	!!( GetEntBits(g_bBlocksByPlugin, iEnt) )	)
			{
				if( !iFile )
				{
					iFile = fopen(szConfigFile, "wt");
				}
				fm_get_brush_entity_origin(iEnt, flBrushOrigin);
				fprintf(iFile, "%f %f %f %d^n",
				flBrushOrigin[0], flBrushOrigin[1], flBrushOrigin[2], bUnMovable);
			}
		}
	}
	if( iFile )
	{
		fclose( iFile );
	}
}
