/* * shavit's Timer - Map Zones * by: shavit, GAMMA CASE, rtldg, KiD Fearless, Kryptanyte, carnifex, rumour, BoomShotKapow, Nuko, Technoblazed, Kxnrl, Extan, sh4hrazad, OliviaMourning * * This file is part of shavit's Timer (https://github.com/shavitush/bhoptimer) * * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, 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 * this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #undef REQUIRE_PLUGIN #include #include #undef REQUIRE_EXTENSIONS #include #include #include #pragma semicolon 1 #pragma newdecls required #define DEBUG 0 #define FSOLID_NOT_SOLID 4 #define FSOLID_TRIGGER 8 #define EF_NODRAW 32 #define SOLID_BBOX 2 EngineVersion gEV_Type = Engine_Unknown; Database gH_SQL = null; int gI_Driver = Driver_unknown; bool gB_YouCanLoadZonesNow = false; char gS_Map[PLATFORM_MAX_PATH]; enum struct zone_settings_t { bool bVisible; int iRed; int iGreen; int iBlue; int iAlpha; float fWidth; bool bFlatZone; bool bUseVanillaSprite; bool bNoHalo; int iBeam; int iHalo; int iSpeed; char sBeam[PLATFORM_MAX_PATH]; } // 0 - nothing // 1 - wait for E tap to setup first coord // 2 - wait for E tap to setup second coord // 3 - confirm int gI_MapStep[MAXPLAYERS+1]; Handle gH_StupidTimer[MAXPLAYERS+1]; int gI_CurrentTraceEntity = 0; zone_cache_t gA_EditCache[MAXPLAYERS+1]; int gI_HookListPos[MAXPLAYERS+1]; int gI_ZoneID[MAXPLAYERS+1]; bool gB_WaitingForChatInput[MAXPLAYERS+1]; float gV_WallSnap[MAXPLAYERS+1][3]; bool gB_Button[MAXPLAYERS+1]; float gF_Modifier[MAXPLAYERS+1]; int gI_AdjustAxis[MAXPLAYERS+1]; int gI_GridSnap[MAXPLAYERS+1]; bool gB_SnapToWall[MAXPLAYERS+1]; bool gB_CursorTracing[MAXPLAYERS+1]; int gI_LatestTeleportTick[MAXPLAYERS+1]; // player zone status int gI_InsideZone[MAXPLAYERS+1][TRACKS_SIZE]; // bit flag bool gB_InsideZoneID[MAXPLAYERS+1][MAX_ZONES]; // zone cache zone_settings_t gA_ZoneSettings[ZONETYPES_SIZE][TRACKS_SIZE]; zone_cache_t gA_ZoneCache[MAX_ZONES]; // Vectors will not be inside this array. int gI_MapZones = 0; float gV_MapZones_Visual[MAX_ZONES][8][3]; float gV_ZoneCenter[MAX_ZONES][3]; int gI_HighestStage[TRACKS_SIZE]; float gF_CustomSpawn[TRACKS_SIZE][3]; int gI_EntityZone[2048] = {-1, ...}; int gI_LastStage[MAXPLAYERS+1]; char gS_BeamSprite[PLATFORM_MAX_PATH]; char gS_BeamSpriteIgnoreZ[PLATFORM_MAX_PATH]; int gI_BeamSpriteIgnoreZ; // admin menu TopMenu gH_AdminMenu = null; TopMenuObject gH_TimerCommands = INVALID_TOPMENUOBJECT; // misc cache bool gB_Late = false; ConVar sv_gravity = null; // cvars Convar gCV_SQLZones = null; Convar gCV_PrebuiltZones = null; Convar gCV_ClimbButtons = null; Convar gCV_Interval = null; Convar gCV_TeleportToStart = null; Convar gCV_TeleportToEnd = null; Convar gCV_AllowDrawAllZones = null; Convar gCV_UseCustomSprite = null; Convar gCV_Height = null; Convar gCV_Offset = null; Convar gCV_EnforceTracks = null; Convar gCV_BoxOffset = null; Convar gCV_ExtraSpawnHeight = null; Convar gCV_PrebuiltVisualOffset = null; Convar gCV_EnableStageRestart = null; Convar gCV_ForceTargetnameReset = null; Convar gCV_ResetTargetnameMain = null; Convar gCV_ResetTargetnameBonus = null; Convar gCV_ResetClassnameMain = null; Convar gCV_ResetClassnameBonus = null; // handles Handle gH_DrawVisible = null; Handle gH_DrawAllZones = null; bool gB_DrawAllZones[MAXPLAYERS+1]; Cookie gH_DrawAllZonesCookie = null; // table prefix char gS_MySQLPrefix[32]; // chat settings chatstrings_t gS_ChatStrings; // forwards Handle gH_Forwards_EnterZone = null; Handle gH_Forwards_LeaveZone = null; Handle gH_Forwards_LoadZonesHere = null; Handle gH_Forwards_StageMessage = null; // sdkcalls Handle gH_PhysicsRemoveTouchedList = null; Handle gH_PassesTriggerFilters = null; Handle gH_CommitSuicide = null; // sourcemod always finds a way to amaze me // dhooks DynamicHook gH_TeleportDhook = null; // kz support float gF_ClimbButtonCache[MAXPLAYERS+1][TRACKS_SIZE][2][3]; // 0 - location, 1 - angles // set start bool gB_HasSetStart[MAXPLAYERS+1][TRACKS_SIZE]; bool gB_StartAnglesOnly[MAXPLAYERS+1][TRACKS_SIZE]; float gF_StartPos[MAXPLAYERS+1][TRACKS_SIZE][3]; float gF_StartAng[MAXPLAYERS+1][TRACKS_SIZE][3]; // modules bool gB_Eventqueuefix = false; bool gB_ReplayRecorder = false; bool gB_AdminMenu = false; #define CZONE_VER 'c' // custom zone stuff Cookie gH_CustomZoneCookie = null; int gI_ZoneDisplayType[MAXPLAYERS+1][ZONETYPES_SIZE][TRACKS_SIZE]; int gI_ZoneColor[MAXPLAYERS+1][ZONETYPES_SIZE][TRACKS_SIZE]; int gI_ZoneWidth[MAXPLAYERS+1][ZONETYPES_SIZE][TRACKS_SIZE]; int gI_LastMenuPos[MAXPLAYERS+1]; public Plugin myinfo = { name = "[shavit] Map Zones", author = "shavit, GAMMA CASE, rtldg, KiD Fearless, Kryptanyte, carnifex, rumour, BoomShotKapow, Nuko, Technoblazed, Kxnrl, Extan, sh4hrazad, OliviaMourning", description = "Map zones for shavit's bhop timer.", version = SHAVIT_VERSION, url = "https://github.com/shavitush/bhoptimer" } public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { // zone natives CreateNative("Shavit_GetZoneData", Native_GetZoneData); CreateNative("Shavit_GetZoneFlags", Native_GetZoneFlags); CreateNative("Shavit_GetHighestStage", Native_GetHighestStage); CreateNative("Shavit_InsideZone", Native_InsideZone); CreateNative("Shavit_InsideZoneGetID", Native_InsideZoneGetID); CreateNative("Shavit_IsClientCreatingZone", Native_IsClientCreatingZone); CreateNative("Shavit_ZoneExists", Native_ZoneExists); CreateNative("Shavit_Zones_DeleteMap", Native_Zones_DeleteMap); CreateNative("Shavit_SetStart", Native_SetStart); CreateNative("Shavit_DeleteSetStart", Native_DeleteSetStart); CreateNative("Shavit_GetClientLastStage", Native_GetClientLastStage); CreateNative("Shavit_GetZoneTrack", Native_GetZoneTrack); CreateNative("Shavit_GetZoneType", Native_GetZoneType); CreateNative("Shavit_GetZoneID", Native_GetZoneID); CreateNative("Shavit_ReloadZones", Native_ReloadZones); CreateNative("Shavit_UnloadZones", Native_UnloadZones); CreateNative("Shavit_GetZoneCount", Native_GetZoneCount); CreateNative("Shavit_GetZone", Native_GetZone); CreateNative("Shavit_AddZone", Native_AddZone); CreateNative("Shavit_RemoveZone", Native_RemoveZone); // registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins RegPluginLibrary("shavit-zones"); gB_Late = late; return APLRes_Success; } public void OnPluginStart() { LoadTranslations("shavit-common.phrases"); LoadTranslations("shavit-zones.phrases"); // game specific gEV_Type = GetEngineVersion(); // menu RegAdminCmd("sm_addzone", Command_Zones, ADMFLAG_RCON, "Opens the mapzones menu."); RegAdminCmd("sm_zones", Command_Zones, ADMFLAG_RCON, "Opens the mapzones menu."); RegAdminCmd("sm_mapzones", Command_Zones, ADMFLAG_RCON, "Opens the mapzones menu. Alias of sm_zones."); RegAdminCmd("sm_delzone", Command_DeleteZone, ADMFLAG_RCON, "Delete a mapzone"); RegAdminCmd("sm_deletezone", Command_DeleteZone, ADMFLAG_RCON, "Delete a mapzone"); RegAdminCmd("sm_deleteallzones", Command_DeleteAllZones, ADMFLAG_RCON, "Delete all mapzones"); RegAdminCmd("sm_modifier", Command_Modifier, ADMFLAG_RCON, "Changes the axis modifier for the zone editor. Usage: sm_modifier "); RegAdminCmd("sm_addspawn", Command_AddSpawn, ADMFLAG_RCON, "Adds a custom spawn location"); RegAdminCmd("sm_delspawn", Command_DelSpawn, ADMFLAG_RCON, "Deletes a custom spawn location"); RegAdminCmd("sm_zoneedit", Command_ZoneEdit, ADMFLAG_RCON, "Modify an existing zone."); RegAdminCmd("sm_editzone", Command_ZoneEdit, ADMFLAG_RCON, "Modify an existing zone. Alias of sm_zoneedit."); RegAdminCmd("sm_modifyzone", Command_ZoneEdit, ADMFLAG_RCON, "Modify an existing zone. Alias of sm_zoneedit."); RegAdminCmd("sm_hookzone", Command_HookZone, ADMFLAG_RCON, "Hook an existing trigger, teleporter, or button."); RegAdminCmd("sm_tptozone", Command_TpToZone, ADMFLAG_RCON, "Teleport to a zone"); RegAdminCmd("sm_reloadzonesettings", Command_ReloadZoneSettings, ADMFLAG_ROOT, "Reloads the zone settings."); RegConsoleCmd("sm_beamer", Command_Beamer, "Draw cool beams"); RegConsoleCmd("sm_stages", Command_Stages, "Opens the stage menu. Usage: sm_stages [stage #]"); RegConsoleCmd("sm_stage", Command_Stages, "Opens the stage menu. Usage: sm_stage [stage #]"); RegConsoleCmd("sm_s", Command_Stages, "Opens the stage menu. Usage: sm_s [stage #]"); RegConsoleCmd("sm_rs", Command_StageRestart, "Teleports the player to the current stage. Only works on surf maps."); RegConsoleCmd("sm_stagerestart", Command_StageRestart, "Teleports the player to the current stage. Only works on surf maps."); RegConsoleCmd("sm_restartstage", Command_StageRestart, "Teleports the player to the current stage. Only works on surf maps."); RegConsoleCmd("sm_set", Command_SetStart, "Set current position as spawn location in start zone."); RegConsoleCmd("sm_setstart", Command_SetStart, "Set current position as spawn location in start zone."); RegConsoleCmd("sm_ss", Command_SetStart, "Set current position as spawn location in start zone."); RegConsoleCmd("sm_sp", Command_SetStart, "Set current position as spawn location in start zone."); RegConsoleCmd("sm_startpoint", Command_SetStart, "Set current position as spawn location in start zone."); RegConsoleCmd("sm_deletestart", Command_DeleteSetStart, "Deletes the custom set start position."); RegConsoleCmd("sm_deletesetstart", Command_DeleteSetStart, "Deletes the custom set start position."); RegConsoleCmd("sm_delss", Command_DeleteSetStart, "Deletes the custom set start position."); RegConsoleCmd("sm_delsp", Command_DeleteSetStart, "Deletes the custom set start position."); RegConsoleCmd("sm_drawallzones", Command_DrawAllZones, "Toggles drawing all zones."); RegConsoleCmd("sm_drawzones", Command_DrawAllZones, "Toggles drawing all zones."); gH_DrawAllZonesCookie = new Cookie("shavit_drawallzones", "Draw all zones cookie", CookieAccess_Protected); RegConsoleCmd("sm_czone", Command_CustomZones, "Customize start and end zone for each track"); RegConsoleCmd("sm_czones", Command_CustomZones, "Customize start and end zone for each track"); RegConsoleCmd("sm_customzones", Command_CustomZones, "Customize start and end zone for each track"); gH_CustomZoneCookie = new Cookie("shavit_customzones", "Cookie for storing custom zone stuff", CookieAccess_Private); for (int i = 0; i <= 9; i++) { char cmd[30]; FormatEx(cmd, sizeof(cmd), "sm_s%d%cGo to stage %d", i, 0, i); // 😈 RegConsoleCmd(cmd, Command_Stages, cmd[6]); } // events if(gEV_Type == Engine_TF2) { HookEvent("teamplay_round_start", Round_Start); } else { HookEvent("round_start", Round_Start); } HookEvent("player_spawn", Player_Spawn); // forwards gH_Forwards_EnterZone = CreateGlobalForward("Shavit_OnEnterZone", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); gH_Forwards_LeaveZone = CreateGlobalForward("Shavit_OnLeaveZone", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); gH_Forwards_LoadZonesHere = CreateGlobalForward("Shavit_LoadZonesHere", ET_Event); gH_Forwards_StageMessage = CreateGlobalForward("Shavit_OnStageMessage", ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell); // cvars and stuff gCV_SQLZones = new Convar("shavit_zones_usesql", "1", "Whether to automatically load zones from the database or not.\n0 - Load nothing. (You'll need a plugin to add zones with `Shavit_AddZone()`)\n1 - Load zones from database.", 0, true, 0.0, true, 1.0); gCV_PrebuiltZones = new Convar("shavit_zones_useprebuilt", "1", "Whether to automatically hook mod_zone_* zone entities.", 0, true, 0.0, true, 1.0); gCV_ClimbButtons = new Convar("shavit_zones_usebuttons", "1", "Whether to automatically hook climb_* buttons.", 0, true, 0.0, true, 1.0); gCV_Interval = new Convar("shavit_zones_interval", "1.0", "Interval between each time a mapzone is being drawn to the players.", 0, true, 0.25, true, 5.0); gCV_TeleportToStart = new Convar("shavit_zones_teleporttostart", "1", "Teleport players to the start zone on timer restart?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_TeleportToEnd = new Convar("shavit_zones_teleporttoend", "1", "Teleport players to the end zone on sm_end?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_AllowDrawAllZones = new Convar("shavit_zones_allowdrawallzones", "1", "Allow players to use !drawallzones to see all zones regardless of zone visibility settings.\n0 - nobody can use !drawallzones\n1 - admins (sm_zones access) can use !drawallzones\n2 - anyone can use !drawallzones", 0, true, 0.0, true, 2.0); gCV_UseCustomSprite = new Convar("shavit_zones_usecustomsprite", "1", "Use custom sprite for zone drawing?\nSee `configs/shavit-zones.cfg`.\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_Height = new Convar("shavit_zones_height", "128.0", "Height to use for the start zone.", 0, true, 0.0, false); gCV_Offset = new Convar("shavit_zones_offset", "1.0", "When calculating a zone's *VISUAL* box, by how many units, should we scale it to the center?\n0.0 - no downscaling. Values above 0 will scale it inward and negative numbers will scale it outwards.\nAdjust this value if the zones clip into walls."); gCV_EnforceTracks = new Convar("shavit_zones_enforcetracks", "1", "Enforce zone tracks upon entry?\n0 - allow every zone except for start/end to affect users on every zone.\n1 - require the user's track to match the zone's track.", 0, true, 0.0, true, 1.0); gCV_BoxOffset = new Convar("shavit_zones_box_offset", "1", "Offset zone trigger boxes to the center of a player's bounding box or the edges.\n0 - triggers when edges of the bounding boxes touch.\n1 - triggers when the center of a player is in a zone.", 0, true, 0.0, true, 1.0); gCV_ExtraSpawnHeight = new Convar("shavit_zones_extra_spawn_height", "0.0", "YOU DONT NEED TO TOUCH THIS USUALLY. FIX YOUR ACTUAL ZONES.\nUsed to fix some shit prebuilt zones that are in the ground like bhop_strafecontrol"); gCV_PrebuiltVisualOffset = new Convar("shavit_zones_prebuilt_visual_offset", "0", "YOU DONT NEED TO TOUCH THIS USUALLY.\nUsed to fix the VISUAL beam offset for prebuilt zones on a map.\nExample maps you'd want to use 16 on: bhop_tranquility and bhop_amaranthglow"); gCV_EnableStageRestart = new Convar("shavit_zones_enable_stage_restart", "1", "Whether clients can use !stagerestart/!restartstage/!rs to restart to the current stage zone. Currently only available for `surf_` maps.", 0, true, 0.0, true, 1.0); gCV_ForceTargetnameReset = new Convar("shavit_zones_forcetargetnamereset", "0", "Reset the player's targetname upon timer start?\nRecommended to leave disabled. Enable via per-map configs when necessary.\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_ResetTargetnameMain = new Convar("shavit_zones_resettargetname_main", "", "What targetname to use when resetting the player.\nWould be applied once player teleports to the start zone or on every start if shavit_zones_forcetargetnamereset cvar is set to 1.\nYou don't need to touch this"); gCV_ResetTargetnameBonus = new Convar("shavit_zones_resettargetname_bonus", "", "What targetname to use when resetting the player (on bonus tracks).\nWould be applied once player teleports to the start zone or on every start if shavit_zones_forcetargetnamereset cvar is set to 1.\nYou don't need to touch this"); gCV_ResetClassnameMain = new Convar("shavit_zones_resetclassname_main", "", "What classname to use when resetting the player.\nWould be applied once player teleports to the start zone or on every start if shavit_zones_forcetargetnamereset cvar is set to 1.\nYou don't need to touch this"); gCV_ResetClassnameBonus = new Convar("shavit_zones_resetclassname_bonus", "", "What classname to use when resetting the player (on bonus tracks).\nWould be applied once player teleports to the start zone or on every start if shavit_zones_forcetargetnamereset cvar is set to 1.\nYou don't need to touch this"); gCV_SQLZones.AddChangeHook(OnConVarChanged); gCV_Interval.AddChangeHook(OnConVarChanged); gCV_UseCustomSprite.AddChangeHook(OnConVarChanged); gCV_Offset.AddChangeHook(OnConVarChanged); gCV_PrebuiltVisualOffset.AddChangeHook(OnConVarChanged); gCV_BoxOffset.AddChangeHook(OnConVarChanged); Convar.AutoExecConfig(); LoadDHooks(); // misc cvars sv_gravity = FindConVar("sv_gravity"); for(int i = 0; i < ZONETYPES_SIZE; i++) { for(int j = 0; j < TRACKS_SIZE; j++) { gA_ZoneSettings[i][j].bVisible = true; gA_ZoneSettings[i][j].iRed = 255; gA_ZoneSettings[i][j].iGreen = 255; gA_ZoneSettings[i][j].iBlue = 255; gA_ZoneSettings[i][j].iAlpha = 255; gA_ZoneSettings[i][j].fWidth = 2.0; gA_ZoneSettings[i][j].bFlatZone = false; } } gB_ReplayRecorder = LibraryExists("shavit-replay-recorder"); gB_Eventqueuefix = LibraryExists("eventqueuefix"); gB_AdminMenu = LibraryExists("adminmenu"); if (gB_Late) { GetLowercaseMapName(gS_Map); // erm... Shavit_OnChatConfigLoaded(); Shavit_OnDatabaseLoaded(); if (gB_AdminMenu && (gH_AdminMenu = GetAdminTopMenu()) != null) { OnAdminMenuReady(gH_AdminMenu); } for(int i = 1; i <= MaxClients; i++) { if (IsValidClient(i)) { OnClientConnected(i); OnClientPutInServer(i); if (AreClientCookiesCached(i)) { OnClientCookiesCached(i); } } } } } void KillShavitZoneEnts(const char[] classname) { char targetname[64]; int ent = -1; while ((ent = FindEntityByClassname(ent, classname)) != -1) { GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname)); if (StrContains(targetname, "shavit_zones_") == 0) { AcceptEntityInput(ent, "Kill"); } } } public void OnPluginEnd() { KillShavitZoneEnts("trigger_multiple"); KillShavitZoneEnts("player_speedmod"); } void LoadDHooks() { Handle hGameData = LoadGameConfigFile("shavit.games"); if (hGameData == null) { SetFailState("Failed to load shavit gamedata"); } LoadPhysicsUntouch(hGameData); if (gEV_Type == Engine_CSGO) { StartPrepSDKCall(SDKCall_Entity); } else { StartPrepSDKCall(SDKCall_Static); } if (!PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, "PhysicsRemoveTouchedList")) { SetFailState("Failed to find \"PhysicsRemoveTouchedList\" signature!"); } if (gEV_Type != Engine_CSGO) { PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); } gH_PhysicsRemoveTouchedList = EndPrepSDKCall(); if (!gH_PhysicsRemoveTouchedList) { SetFailState("Failed to create sdkcall to \"PhysicsRemoveTouchedList\"!"); } StartPrepSDKCall(SDKCall_Entity); if (!PrepSDKCall_SetFromConf(hGameData, SDKConf_Virtual, "CBaseTrigger::PassesTriggerFilters")) { SetFailState("Failed to find \"CBaseTrigger::PassesTriggerFilters\" offset!"); } PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); if (!(gH_PassesTriggerFilters = EndPrepSDKCall())) { SetFailState("Failed to create sdkcall to \"CBaseTrigger::PassesTriggerFilters\"!"); } delete hGameData; hGameData = LoadGameConfigFile("sdktools.games"); if (hGameData == null) { SetFailState("Failed to load sdktools gamedata"); } int iOffset = GameConfGetOffset(hGameData, "Teleport"); if (iOffset == -1) { SetFailState("Couldn't get the offset for \"Teleport\"!"); } gH_TeleportDhook = new DynamicHook(iOffset, HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity); gH_TeleportDhook.AddParam(HookParamType_VectorPtr); gH_TeleportDhook.AddParam(HookParamType_VectorPtr); gH_TeleportDhook.AddParam(HookParamType_VectorPtr); if (GetEngineVersion() == Engine_CSGO) { gH_TeleportDhook.AddParam(HookParamType_Bool); } StartPrepSDKCall(SDKCall_Entity); PrepSDKCall_SetFromConf(hGameData, SDKConf_Virtual, "CommitSuicide"); PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_ByValue); // explode PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_ByValue); // force if (!(gH_CommitSuicide = EndPrepSDKCall())) { SetFailState("Failed to create sdkcall to \"CommitSuicide\""); } delete hGameData; } public void OnLibraryAdded(const char[] name) { if (strcmp(name, "adminmenu") == 0) { gB_AdminMenu = true; } else if (StrEqual(name, "shavit-replay-recorder")) { gB_ReplayRecorder = true; } else if (StrEqual(name, "eventqueuefix")) { gB_Eventqueuefix = true; } } public void OnLibraryRemoved(const char[] name) { if (strcmp(name, "adminmenu") == 0) { gB_AdminMenu = false; gH_AdminMenu = null; gH_TimerCommands = INVALID_TOPMENUOBJECT; } else if (StrEqual(name, "shavit-replay-recorder")) { gB_ReplayRecorder = false; } else if (StrEqual(name, "eventqueuefix")) { gB_Eventqueuefix = false; } } public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) { if(convar == gCV_Interval) { delete gH_DrawVisible; delete gH_DrawAllZones; gH_DrawVisible = CreateTimer(gCV_Interval.FloatValue, Timer_DrawZones, 0, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); gH_DrawAllZones = CreateTimer(gCV_Interval.FloatValue, Timer_DrawZones, 1, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); } else if (convar == gCV_Offset || convar == gCV_PrebuiltVisualOffset) { for (int i = 0; i < gI_MapZones; i++) { if ((convar == gCV_Offset && gA_ZoneCache[i].iForm == ZoneForm_Box) || (convar == gCV_PrebuiltVisualOffset && gA_ZoneCache[i].iForm == ZoneForm_trigger_multiple)) { gV_MapZones_Visual[i][0] = gA_ZoneCache[i].fCorner1; gV_MapZones_Visual[i][7] = gA_ZoneCache[i].fCorner2; CreateZonePoints(gV_MapZones_Visual[i], convar == gCV_PrebuiltVisualOffset); } } } else if(convar == gCV_UseCustomSprite && !StrEqual(oldValue, newValue)) { LoadZoneSettings(); } else if (convar == gCV_BoxOffset) { for (int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iForm == ZoneForm_Box && gA_ZoneCache[i].iEntity > 0) { SetZoneMinsMaxs(i); } } } else if (convar == gCV_SQLZones) { for (int i = gI_MapZones; i > 0; i++) { if (StrEqual(gA_ZoneCache[i-1].sSource, "sql")) Shavit_RemoveZone(i-1); } if (convar.BoolValue) RefreshZones(); } else if (convar == gCV_PrebuiltZones) { for (int i = gI_MapZones; i > 0; i++) { if (StrEqual(gA_ZoneCache[i-1].sSource, "autozone")) Shavit_RemoveZone(i-1); } if (convar.BoolValue) add_prebuilts_to_cache("trigger_multiple", false); } else if (convar == gCV_ClimbButtons) { for (int i = gI_MapZones; i > 0; i++) { if (StrEqual(gA_ZoneCache[i-1].sSource, "autobutton")) Shavit_RemoveZone(i-1); } if (convar.BoolValue) add_prebuilts_to_cache("func_button", true); if (convar.BoolValue) add_prebuilts_to_cache("func_rot_button", true); } } public void OnAdminMenuReady(Handle topmenu) { gH_AdminMenu = TopMenu.FromHandle(topmenu); if ((gH_TimerCommands = gH_AdminMenu.FindCategory("Timer Commands")) != INVALID_TOPMENUOBJECT) { gH_AdminMenu.AddItem("sm_zones", AdminMenu_Zones, gH_TimerCommands, "sm_zones", ADMFLAG_RCON); gH_AdminMenu.AddItem("sm_deletezone", AdminMenu_DeleteZone, gH_TimerCommands, "sm_deletezone", ADMFLAG_RCON); gH_AdminMenu.AddItem("sm_deleteallzones", AdminMenu_DeleteAllZones, gH_TimerCommands, "sm_deleteallzones", ADMFLAG_RCON); gH_AdminMenu.AddItem("sm_zoneedit", AdminMenu_ZoneEdit, gH_TimerCommands, "sm_zoneedit", ADMFLAG_RCON); gH_AdminMenu.AddItem("sm_tptozone", AdminMenu_TpToZone, gH_TimerCommands, "sm_tptozone", ADMFLAG_RCON); gH_AdminMenu.AddItem("sm_hookzone", AdminMenu_HookZone, gH_TimerCommands, "sm_hookzone", ADMFLAG_RCON); } } public void AdminMenu_Zones(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayOption) { FormatEx(buffer, maxlength, "%T", "AddMapZone", param); } else if(action == TopMenuAction_SelectOption) { Command_Zones(param, 0); } } public void AdminMenu_DeleteZone(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayOption) { FormatEx(buffer, maxlength, "%T", "DeleteMapZone", param); } else if(action == TopMenuAction_SelectOption) { Command_DeleteZone(param, 0); } } public void AdminMenu_DeleteAllZones(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayOption) { FormatEx(buffer, maxlength, "%T", "DeleteAllMapZone", param); } else if(action == TopMenuAction_SelectOption) { Command_DeleteAllZones(param, 0); } } public void AdminMenu_ZoneEdit(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayOption) { FormatEx(buffer, maxlength, "%T", "ZoneEdit", param); } else if(action == TopMenuAction_SelectOption) { Reset(param); OpenEditMenu(param); } } public void AdminMenu_TpToZone(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { FormatEx(buffer, maxlength, "%T", "TpToZone", param); } else if (action == TopMenuAction_SelectOption) { OpenTpToZoneMenu(param); } } public void AdminMenu_HookZone(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { FormatEx(buffer, maxlength, "%T", "HookZone", param); } else if (action == TopMenuAction_SelectOption) { OpenHookMenu_Form(param); } } public int Native_ZoneExists(Handle handler, int numParams) { return (GetZoneIndex(GetNativeCell(1), GetNativeCell(2)) != -1); } public int Native_GetZoneData(Handle handler, int numParams) { return gA_ZoneCache[GetNativeCell(1)].iData; } public int Native_GetZoneFlags(Handle handler, int numParams) { return gA_ZoneCache[GetNativeCell(1)].iFlags; } public int Native_InsideZone(Handle handler, int numParams) { return InsideZone(GetNativeCell(1), GetNativeCell(2), (numParams > 2) ? GetNativeCell(3) : -1); } public int Native_InsideZoneGetID(Handle handler, int numParams) { int client = GetNativeCell(1); int iType = GetNativeCell(2); int iTrack = GetNativeCell(3); if (iTrack >= 0 && !(gI_InsideZone[client][iTrack] & (1 << iType))) { return false; } for (int i = 0; i < gI_MapZones; i++) { if(gB_InsideZoneID[client][i] && gA_ZoneCache[i].iType == iType && (gA_ZoneCache[i].iTrack == iTrack || iTrack == -1)) { SetNativeCellRef(4, i); return true; } } return false; } public int Native_GetHighestStage(Handle handler, int numParas) { return gI_HighestStage[GetNativeCell(1)]; } public int Native_Zones_DeleteMap(Handle handler, int numParams) { char sMap[PLATFORM_MAX_PATH]; GetNativeString(1, sMap, sizeof(sMap)); LowercaseString(sMap); char sQuery[512]; FormatEx(sQuery, sizeof(sQuery), "DELETE FROM %smapzones WHERE map = '%s';", gS_MySQLPrefix, sMap); QueryLog(gH_SQL, SQL_DeleteMap_Callback, sQuery, StrEqual(gS_Map, sMap, false), DBPrio_High); return 1; } public void SQL_DeleteMap_Callback(Database db, DBResultSet results, const char[] error, any data) { if(results == null) { LogError("Timer (zones deletemap) SQL query failed. Reason: %s", error); return; } if(view_as(data)) { //DBConnectedSoDoStuff(); } } bool InsideZone(int client, int type, int track) { if(track != -1) { return (gI_InsideZone[client][track] & (1 << type)) != 0; } else { int res = 0; for(int i = 0; i < TRACKS_SIZE; i++) { res |= gI_InsideZone[client][i]; } return (res & (1 << type)) != 0; } } public int Native_IsClientCreatingZone(Handle handler, int numParams) { return (gI_MapStep[GetNativeCell(1)] != 0); } public int Native_SetStart(Handle handler, int numParams) { SetStart(GetNativeCell(1), GetNativeCell(2), view_as(GetNativeCell(3))); return 1; } public int Native_DeleteSetStart(Handle handler, int numParams) { DeleteSetStart(GetNativeCell(1), GetNativeCell(2)); return 1; } public int Native_GetClientLastStage(Handle plugin, int numParams) { return gI_LastStage[GetNativeCell(1)]; } public any Native_GetZoneTrack(Handle plugin, int numParams) { int zoneid = GetNativeCell(1); return gA_ZoneCache[zoneid].iTrack; } public any Native_GetZoneType(Handle plugin, int numParams) { int zoneid = GetNativeCell(1); return gA_ZoneCache[zoneid].iType; } public any Native_GetZoneID(Handle plugin, int numParams) { int entity = GetNativeCell(1); return gI_EntityZone[entity]; } public any Native_ReloadZones(Handle plugin, int numParams) { LoadZonesHere(); return 0; } public any Native_UnloadZones(Handle plugin, int numParams) { UnloadZones(); return 0; } public any Native_GetZoneCount(Handle plugin, int numParams) { return gI_MapZones; } public any Native_GetZone(Handle plugin, int numParams) { if (GetNativeCell(3) != sizeof(zone_cache_t)) { return ThrowNativeError(200, "zone_cache_t does not match latest(got %i expected %i). Please update your includes and recompile your plugins", GetNativeCell(3), sizeof(zone_cache_t)); } SetNativeArray(2, gA_ZoneCache[GetNativeCell(1)], sizeof(zone_cache_t)); return 0; } public any Native_AddZone(Handle plugin, int numParams) { if (gI_MapZones >= MAX_ZONES) { return -1; } if (GetNativeCell(2) != sizeof(zone_cache_t)) { return ThrowNativeError(200, "zone_cache_t does not match latest(got %i expected %i). Please update your includes and recompile your plugins", GetNativeCell(2), sizeof(zone_cache_t)); } zone_cache_t cache; GetNativeArray(1, cache, sizeof(cache)); cache.iEntity = -1; if (cache.iForm != ZoneForm_Box && (cache.iFlags & ZF_Origin)) { // previously origins were "%X %X %X" instead of "%.9f %.9f %.9f"... // so we just convert this right now... // "C56D0000 455D0000 C3600000" // to "-3792.000000000 3536.000000000 -224.000000000" if (-1 == StrContains(cache.sTarget, ".")) { Format(cache.sTarget, sizeof(cache.sTarget), "%.9f %.9f %.9f", StringToInt(cache.sTarget, 16), StringToInt(cache.sTarget[9], 16), StringToInt(cache.sTarget[18], 16) ); } } BoxPointsToMinsMaxs(cache.fCorner1, cache.fCorner2, cache.fCorner1, cache.fCorner2); gA_ZoneCache[gI_MapZones] = cache; gV_MapZones_Visual[gI_MapZones][0] = cache.fCorner1; gV_MapZones_Visual[gI_MapZones][7] = cache.fCorner2; CreateZonePoints(gV_MapZones_Visual[gI_MapZones], cache.iForm == ZoneForm_trigger_multiple); AddVectors(cache.fCorner1, cache.fCorner2, gV_ZoneCenter[gI_MapZones]); ScaleVector(gV_ZoneCenter[gI_MapZones], 0.5); if (cache.iType == Zone_Stage) { if (cache.iData > gI_HighestStage[cache.iTrack]) { gI_HighestStage[cache.iTrack] = cache.iData; } } return gI_MapZones++; } public any Native_RemoveZone(Handle plugin, int numParams) { int index = GetNativeCell(1); if (gI_MapZones <= 0 || index >= gI_MapZones) { return 0; } zone_cache_t cache; cache = gA_ZoneCache[index]; int ent = gA_ZoneCache[index].iEntity; ClearZoneEntity(index, true); if (ent > MaxClients && gA_ZoneCache[index].iForm == ZoneForm_Box) // created by shavit-zones { AcceptEntityInput(ent, "Kill"); } int top = --gI_MapZones; if (index < top) { gI_EntityZone[gA_ZoneCache[top].iEntity] = index; gA_ZoneCache[index] = gA_ZoneCache[top]; gV_ZoneCenter[index] = gV_ZoneCenter[top]; for (int i = 0; i < sizeof(gV_MapZones_Visual[]); i++) { gV_MapZones_Visual[index][i] = gV_MapZones_Visual[top][i]; } for (int i = 1; i <= MaxClients; i++) { gB_InsideZoneID[i][index] = gB_InsideZoneID[i][top]; } } else { bool empty_InsideZoneID[MAX_ZONES]; for (int i = 1; i <= MaxClients; i++) { gB_InsideZoneID[i] = empty_InsideZoneID; } } RecalcInsideZoneAll(); if (cache.iType == Zone_Stage && cache.iData == gI_HighestStage[cache.iTrack]) RecalcHighestStage(); // call EndTouchPost(zoneent, player) manually here? return 0; } bool JumpToZoneType(KeyValues kv, int type, int track) { static const char config_keys[ZONETYPES_SIZE][2][50] = { {"Start", ""}, {"End", ""}, {"Glitch_Respawn", "Glitch Respawn"}, {"Glitch_Stop", "Glitch Stop"}, {"Glitch_Slay", "Glitch Slay"}, {"Freestyle", ""}, {"Custom Speed Limit", "Nolimit"}, {"Teleport", ""}, {"SPAWN POINT", ""}, {"Easybhop", ""}, {"Slide", ""}, {"Airaccelerate", ""}, {"Stage", ""}, {"No Timer Gravity", ""}, {"Gravity", ""}, {"Speedmod", ""}, {"No Jump", ""}, {"Autobhop", ""}, }; char key[4][50]; if (track == Track_Main) { key[0] = config_keys[type][0]; key[1] = config_keys[type][1]; } else { FormatEx(key[0], sizeof(key[]), "Bonus %d %s", track, config_keys[type][0]); if (track == Track_Bonus) FormatEx(key[1], sizeof(key[]), "Bonus %s", config_keys[type][0]); if (config_keys[type][0][0]) { FormatEx(key[2], sizeof(key[]), "Bonus %d %s", track, config_keys[type][1]); if (track == Track_Bonus) FormatEx(key[3], sizeof(key[]), "Bonus %s", config_keys[type][1]); } } for (int i = 0; i < 4; i++) { if (key[i][0] && kv.JumpToKey(key[i])) { return true; } } return false; } bool LoadZonesConfig() { char sPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "configs/shavit-zones.cfg"); KeyValues kv = new KeyValues("shavit-zones"); if(!kv.ImportFromFile(sPath)) { delete kv; return false; } kv.JumpToKey("Sprites"); kv.GetString("beam", gS_BeamSprite, PLATFORM_MAX_PATH); kv.GetString("beam_ignorez", gS_BeamSpriteIgnoreZ, PLATFORM_MAX_PATH, gS_BeamSprite); char sDownloads[PLATFORM_MAX_PATH * 8]; kv.GetString("downloads", sDownloads, (PLATFORM_MAX_PATH * 8)); char sDownloadsExploded[PLATFORM_MAX_PATH][PLATFORM_MAX_PATH]; int iDownloads = ExplodeString(sDownloads, ";", sDownloadsExploded, PLATFORM_MAX_PATH, PLATFORM_MAX_PATH, false); for(int i = 0; i < iDownloads; i++) { if(strlen(sDownloadsExploded[i]) > 0) { TrimString(sDownloadsExploded[i]); AddFileToDownloadsTable(sDownloadsExploded[i]); } } kv.GoBack(); kv.JumpToKey("Colors"); for (int type = 0; type < ZONETYPES_SIZE; type++) { if (type == Zone_CustomSpawn) { continue; } for (int track = 0; track < TRACKS_SIZE; track++) { if (JumpToZoneType(kv, type, track)) { gA_ZoneSettings[type][track].bVisible = view_as(kv.GetNum("visible", 1)); gA_ZoneSettings[type][track].iRed = kv.GetNum("red", 255); gA_ZoneSettings[type][track].iGreen = kv.GetNum("green", 255); gA_ZoneSettings[type][track].iBlue = kv.GetNum("blue", 255); gA_ZoneSettings[type][track].iAlpha = kv.GetNum("alpha", 255); gA_ZoneSettings[type][track].fWidth = kv.GetFloat("width", 2.0); gA_ZoneSettings[type][track].bFlatZone = view_as(kv.GetNum("flat", false)); gA_ZoneSettings[type][track].bUseVanillaSprite = view_as(kv.GetNum("vanilla_sprite", false)); gA_ZoneSettings[type][track].bNoHalo = view_as(kv.GetNum("no_halo", false)); gA_ZoneSettings[type][track].iSpeed = kv.GetNum("speed", 0); kv.GetString("beam", gA_ZoneSettings[type][track].sBeam, sizeof(zone_settings_t::sBeam), ""); kv.GoBack(); } else if (track > Track_Bonus) { // Copy bonus 1 settings to any other bonuses that are missing this zone... gA_ZoneSettings[type][track] = gA_ZoneSettings[type][Track_Bonus]; } } } delete kv; return true; } void LoadZoneSettings() { if(!LoadZonesConfig()) { SetFailState("Cannot open \"configs/shavit-zones.cfg\". Make sure this file exists and that the server has read permissions to it."); } int defaultBeam; int defaultHalo; int customBeam; if(IsSource2013(gEV_Type)) { defaultBeam = PrecacheModel("sprites/laser.vmt", true); defaultHalo = PrecacheModel("sprites/halo01.vmt", true); } else { defaultBeam = PrecacheModel("sprites/laserbeam.vmt", true); defaultHalo = PrecacheModel("sprites/glow01.vmt", true); } if(gCV_UseCustomSprite.BoolValue) { customBeam = PrecacheModel(gS_BeamSprite, true); } else { customBeam = defaultBeam; } gI_BeamSpriteIgnoreZ = PrecacheModel(gS_BeamSpriteIgnoreZ, true); for (int i = 0; i < ZONETYPES_SIZE; i++) { for (int j = 0; j < TRACKS_SIZE; j++) { if (gA_ZoneSettings[i][j].bUseVanillaSprite) { gA_ZoneSettings[i][j].iBeam = defaultBeam; } else { gA_ZoneSettings[i][j].iBeam = (gA_ZoneSettings[i][j].sBeam[0] != 0) ? PrecacheModel(gA_ZoneSettings[i][j].sBeam, true) : customBeam; } gA_ZoneSettings[i][j].iHalo = (gA_ZoneSettings[i][j].bNoHalo) ? 0 : defaultHalo; } } } public void OnMapStart() { GetLowercaseMapName(gS_Map); LoadZoneSettings(); //UnloadZones(); if (gEV_Type == Engine_TF2) { PrecacheModel("models/error.mdl"); } else { PrecacheModel("models/props/cs_office/vending_machine.mdl"); } } public void OnConfigsExecuted() { if (gH_DrawAllZones == null) { gH_DrawVisible = CreateTimer(gCV_Interval.FloatValue, Timer_DrawZones, 0, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); gH_DrawAllZones = CreateTimer(gCV_Interval.FloatValue, Timer_DrawZones, 1, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); } RequestFrame(LoadZonesHere); } void LoadZonesHere() { gB_YouCanLoadZonesNow = true; UnloadZones(); Call_StartForward(gH_Forwards_LoadZonesHere); Call_Finish(); } public void Shavit_LoadZonesHere() { if (gCV_SQLZones.BoolValue && gH_SQL) { RefreshZones(); } if (gCV_PrebuiltZones.BoolValue) { add_prebuilts_to_cache("trigger_multiple", false); } if (gCV_ClimbButtons.BoolValue) { add_prebuilts_to_cache("func_button", true); add_prebuilts_to_cache("func_rot_button", true); } } void add_prebuilts_to_cache(const char[] classname, bool button) { char targetname[64]; int ent = -1; while (-1 != (ent = FindEntityByClassname(ent, classname))) { GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname)); zone_cache_t cache; if (!Shavit_ParseZoneTargetname(targetname, button, cache.iType, cache.iTrack, cache.iData, gS_Map)) { continue; } int hammerid = GetEntProp(ent, Prop_Data, "m_iHammerID"); if (hammerid && IntToString(hammerid, cache.sTarget, sizeof(cache.sTarget))) { cache.iFlags |= ZF_Hammerid; } else { cache.sTarget = targetname; } PrintToServer(">>>> shavit-zones: Hooking '%s' '%s' (%d)", classname, targetname, hammerid); cache.iDatabaseID = -1; cache.iForm = button ? ZoneForm_func_button : ZoneForm_trigger_multiple; cache.sSource = button ? "autobutton" : "autozone"; if (button) { Shavit_MarkKZMap(cache.iTrack); } else { float origin[3]; GetEntPropVector(ent, Prop_Send, "m_vecOrigin", origin); GetEntPropVector(ent, Prop_Send, "m_vecMins", cache.fCorner1); GetEntPropVector(ent, Prop_Send, "m_vecMaxs", cache.fCorner2); //origin[2] -= (maxs[2] - 2.0); // so you don't get stuck in the ground origin[2] += 1.0; // so you don't get stuck in the ground AddVectors(origin, cache.fCorner1, cache.fCorner1); AddVectors(origin, cache.fCorner2, cache.fCorner2); } Shavit_AddZone(cache); } } public void OnGameFrame() { bool search_trigger_multiple; bool search_trigger_teleport; bool search_func_button; for (int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iEntity > 0) { continue; } switch (gA_ZoneCache[i].iForm) { case ZoneForm_Box: { if (!CreateZoneTrigger(i)) { return; // uhhhhhhhh } } case ZoneForm_trigger_multiple: { if (gA_ZoneCache[i].sTarget[0]) search_trigger_multiple = true; } case ZoneForm_trigger_teleport: { if (gA_ZoneCache[i].sTarget[0]) search_trigger_teleport = true; } case ZoneForm_func_button: { if (gA_ZoneCache[i].sTarget[0]) search_func_button = true; } } } if (search_trigger_multiple) { FindEntitiesToHook("trigger_multiple", ZoneForm_trigger_multiple); } if (search_trigger_teleport) { FindEntitiesToHook("trigger_teleport", ZoneForm_trigger_teleport); } if (search_func_button) { FindEntitiesToHook("func_button", ZoneForm_func_button); FindEntitiesToHook("func_rot_button", ZoneForm_func_button); } } void EntOriginString(int ent, char[] sOrigin, bool short) { float fOrigin[3]; GetEntPropVector(ent, Prop_Send, "m_vecOrigin", fOrigin); FormatEx(sOrigin, 64, short ? "%.0f %.0f %.0f" : "%.9f %.9f %.9f", EXPAND_VECTOR(fOrigin)); } void FindEntitiesToHook(const char[] classname, int form) { char targetname[64]; int ent = -1; while ((ent = FindEntityByClassname(ent, classname)) != -1) { if (gI_EntityZone[ent] > MaxClients) { continue; } GetEntPropString(ent, Prop_Data, form == ZoneForm_trigger_teleport ? "m_target" : "m_iName", targetname, sizeof(targetname)); if (form == ZoneForm_trigger_multiple && StrContains(targetname, "shavit_zones_") == 0) { continue; } char hammerid[12]; IntToString(GetEntProp(ent, Prop_Data, "m_iHammerID"), hammerid, sizeof(hammerid)); // xd string comparisons char sOrigin[64]; EntOriginString(ent, sOrigin, false); for (int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iEntity > 0 || gA_ZoneCache[i].iForm != form || !StrEqual(gA_ZoneCache[i].sTarget, (gA_ZoneCache[i].iFlags & ZF_Hammerid) ? hammerid : ((gA_ZoneCache[i].iFlags & ZF_Origin) ? sOrigin : targetname)) ) { continue; } if (form == ZoneForm_func_button) { HookButton(i, ent); } else { HookZoneTrigger(i, ent); } } } } public void OnMapEnd() { gB_YouCanLoadZonesNow = false; delete gH_DrawVisible; delete gH_DrawAllZones; UnloadZones(); } public void OnClientPutInServer(int client) { gI_LatestTeleportTick[client] = 0; if (!IsFakeClient(client) && gH_TeleportDhook != null) { gH_TeleportDhook.HookEntity(Hook_Pre, client, DHooks_OnTeleport); } } public void OnEntityDestroyed(int entity) { if (entity > MaxClients && entity < 2048 && gI_EntityZone[entity] > -1) { ClearZoneEntity(gI_EntityZone[entity], false); } } public void Shavit_OnChatConfigLoaded() { Shavit_GetChatStringsStruct(gS_ChatStrings); } void ClearZoneEntity(int index, bool unhook) { for (int i = 1; i <= MaxClients; i++) { gB_InsideZoneID[i][index] = false; } int entity = gA_ZoneCache[index].iEntity; gA_ZoneCache[index].iEntity = -1; if (entity > MaxClients) { gI_EntityZone[entity] = -1; if (unhook && IsValidEntity(entity)) { UnhookZone(gA_ZoneCache[index]); } } } void UnhookZone(zone_cache_t cache) { int entity = cache.iEntity; if (cache.iForm == ZoneForm_func_button) { SDKUnhook(entity, SDKHook_UsePost, UsePost_HookedButton); } else if (cache.iForm == ZoneForm_Box) { SDKUnhook(entity, SDKHook_StartTouchPost, StartTouchPost); SDKUnhook(entity, SDKHook_EndTouchPost, EndTouchPost); SDKUnhook(entity, SDKHook_TouchPost, TouchPost); if (cache.iType == Zone_Speedmod) { SDKUnhook(entity, SDKHook_StartTouch, SameTrack_StartTouch_er); } } } bool CreateZoneTrigger(int zone) { bool speedmod = (gA_ZoneCache[zone].iType == Zone_Speedmod); char classname[32]; classname = speedmod ? "player_speedmod" : "trigger_multiple"; int entity = CreateEntityByName(classname); if (entity == -1) { LogError("\"%s\" creation failed, map %s.", classname, gS_Map); return false; } if (!speedmod) { DispatchKeyValue(entity, "wait", "0"); // useless??? set to 0.0001 or something? ::Spawn() m_flWait 0 turns into 0.2 which then isn't even used anyway because eventqueuefix sets nextthinktick now??? DispatchKeyValue(entity, "spawnflags", "4097"); // 1|4096 = allow clients|disallow bots } if (!DispatchSpawn(entity)) { AcceptEntityInput(entity, "Kill"); LogError("\"%s\" spawning failed, map %s.", classname, gS_Map); return false; } gA_ZoneCache[zone].iEntity = entity; ActivateEntity(entity); SetEntityModel(entity, (gEV_Type == Engine_TF2) ? "models/error.mdl" : "models/props/cs_office/vending_machine.mdl"); SetEntProp(entity, Prop_Send, "m_fEffects", EF_NODRAW); SetEntProp(entity, Prop_Send, "m_nSolidType", SOLID_BBOX); if (speedmod) { SetEntProp(entity, Prop_Send, "m_usSolidFlags", FSOLID_TRIGGER|FSOLID_NOT_SOLID); } if (gA_ZoneCache[zone].iFlags & ZF_Solid) { SetEntProp(entity, Prop_Send, "m_usSolidFlags", GetEntProp(entity, Prop_Send, "m_usSolidFlags") & ~(FSOLID_TRIGGER|FSOLID_NOT_SOLID)); EntityCollisionRulesChanged(entity); } TeleportEntity(entity, gV_ZoneCenter[zone], NULL_VECTOR, NULL_VECTOR); SetZoneMinsMaxs(zone); HookZoneTrigger(zone, entity); char sTargetname[64]; FormatEx(sTargetname, sizeof(sTargetname), "shavit_zones_%d_%d", gA_ZoneCache[zone].iTrack, gA_ZoneCache[zone].iType); if (gA_ZoneCache[zone].iType == Zone_Stage) { Format(sTargetname, sizeof(sTargetname), "%s_stage%d", sTargetname, gA_ZoneCache[zone].iData); } DispatchKeyValue(entity, "targetname", sTargetname); return true; } void HookButton(int zone, int entity) { Shavit_MarkKZMap(gA_ZoneCache[zone].iTrack); SDKHook(entity, SDKHook_UsePost, UsePost_HookedButton); gI_EntityZone[entity] = zone; gA_ZoneCache[zone].iEntity = entity; } void HookZoneTrigger(int zone, int entity) { SDKHook(entity, SDKHook_StartTouchPost, StartTouchPost); SDKHook(entity, SDKHook_EndTouchPost, EndTouchPost); SDKHook(entity, SDKHook_TouchPost, TouchPost); if (gA_ZoneCache[zone].iType == Zone_Speedmod) { SDKHook(entity, SDKHook_StartTouch, SameTrack_StartTouch_er); } gI_EntityZone[entity] = zone; gA_ZoneCache[zone].iEntity = entity; } void UnloadZones() { for (int i = 0; i < gI_MapZones; i++) { int ent = gA_ZoneCache[i].iEntity; ClearZoneEntity(i, true); if (ent && gA_ZoneCache[i].iForm == ZoneForm_Box) // created by shavit-zones { AcceptEntityInput(ent, "Kill"); } zone_cache_t empty_cache; gA_ZoneCache[i] = empty_cache; } gI_MapZones = 0; int empty_tracks[TRACKS_SIZE]; bool empty_InsideZoneID[MAX_ZONES]; for (int i = 1; i <= MaxClients; i++) { gI_InsideZone[i] = empty_tracks; gB_InsideZoneID[i] = empty_InsideZoneID; } gI_HighestStage = empty_tracks; for(int i = 0; i < TRACKS_SIZE; i++) { gF_CustomSpawn[i] = ZERO_VECTOR; } } void RecalcInsideZoneAll() { for (int i = 1; i <= MaxClients; i++) { RecalcInsideZone(i); } } void RecalcInsideZone(int client) { int empty_array[TRACKS_SIZE]; gI_InsideZone[client] = empty_array; for (int i = 0; i < gI_MapZones; i++) { if (gB_InsideZoneID[client][i]) { int track = gA_ZoneCache[i].iTrack; int type = gA_ZoneCache[i].iType; gI_InsideZone[client][track] |= (1 << type); } } } void RecalcHighestStage() { int empty_tracks[TRACKS_SIZE]; gI_HighestStage = empty_tracks; for (int i = 0; i < gI_MapZones; i++) { int type = gA_ZoneCache[i].iType; if (type != Zone_Stage) continue; int track = gA_ZoneCache[i].iTrack; int stagenum = gA_ZoneCache[i].iData; if (stagenum > gI_HighestStage[track]) gI_HighestStage[track] = stagenum; } } void RefreshZones() { char sQuery[512]; FormatEx(sQuery, 512, "SELECT type, corner1_x, corner1_y, corner1_z, corner2_x, corner2_y, corner2_z, destination_x, destination_y, destination_z, track, %s, flags, data, form, target FROM %smapzones WHERE map = '%s';", (gI_Driver != Driver_sqlite)? "id":"rowid", gS_MySQLPrefix, gS_Map); QueryLog(gH_SQL, SQL_RefreshZones_Callback, sQuery, 0, DBPrio_High); } public void SQL_RefreshZones_Callback(Database db, DBResultSet results, const char[] error, any data) { if(results == null) { LogError("Timer (zone refresh) SQL query failed. Reason: %s", error); return; } while(results.FetchRow()) { int type = results.FetchInt(0); int track = results.FetchInt(10); float destination[3]; destination[0] = results.FetchFloat(7); destination[1] = results.FetchFloat(8); destination[2] = results.FetchFloat(9); if (type == Zone_CustomSpawn) { gF_CustomSpawn[track] = destination; continue; } zone_cache_t cache; cache.iEntity = -1; cache.iType = type; cache.fCorner1[0] = results.FetchFloat(1); cache.fCorner1[1] = results.FetchFloat(2); cache.fCorner1[2] = results.FetchFloat(3); cache.fCorner2[0] = results.FetchFloat(4); cache.fCorner2[1] = results.FetchFloat(5); cache.fCorner2[2] = results.FetchFloat(6); cache.fDestination = destination; cache.iTrack = track; cache.iDatabaseID = results.FetchInt(11); cache.iFlags = results.FetchInt(12); cache.iData = results.FetchInt(13); cache.iForm = results.FetchInt(14); results.FetchString(15, cache.sTarget, sizeof(cache.sTarget)); cache.sSource = "sql"; if (cache.iForm == ZoneForm_Box) { // } else if (cache.iForm == ZoneForm_trigger_multiple) { if (!cache.sTarget[0]) { // ~~Migrate previous `prebuilt`-column-having zones~~ nevermind... TODO continue; } } Shavit_AddZone(cache); } #if 0 if (!gB_InsertedPrebuiltZones) { gB_InsertedPrebuiltZones = true; char sQuery[1024]; Transaction trans; for (int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].bPrebuilt) { if (trans == null) { trans = new Transaction(); } InsertPrebuiltZone(i, false, sQuery, sizeof(sQuery)); AddQueryLog(hTransaction, sQuery); } } if (trans != null) { gH_SQL.Execute(trans); } } #endif } public void OnClientConnected(int client) { int empty_InsideZone[TRACKS_SIZE]; for (int i = 0; i < ZONETYPES_SIZE; i++) { gI_InsideZone[client] = empty_InsideZone; for(int j = 0; j < TRACKS_SIZE; j++) { gI_ZoneDisplayType[client][i][j] = ZoneDisplay_Default; gI_ZoneColor[client][i][j] = ZoneColor_Default; gI_ZoneWidth[client][i][j] = ZoneWidth_Default; } } bool empty_InsideZoneID[MAX_ZONES]; gB_InsideZoneID[client] = empty_InsideZoneID; for (int i = 0; i < TRACKS_SIZE; i++) { gF_ClimbButtonCache[client][i][0] = ZERO_VECTOR; gF_ClimbButtonCache[client][i][1] = ZERO_VECTOR; } bool empty_HasSetStart[TRACKS_SIZE]; gB_HasSetStart[client] = empty_HasSetStart; Reset(client); gF_Modifier[client] = 16.0; gI_AdjustAxis[client] = 0; gI_GridSnap[client] = 16; gB_SnapToWall[client] = false; gB_CursorTracing[client] = true; gB_DrawAllZones[client] = false; } public void OnClientAuthorized(int client) { if (gH_SQL && !IsFakeClient(client)) { GetStartPosition(client); } } public void OnClientCookiesCached(int client) { if (IsFakeClient(client)) { return; } char setting[8]; gH_DrawAllZonesCookie.Get(client, setting, sizeof(setting)); gB_DrawAllZones[client] = view_as(StringToInt(setting)); char czone[100]; // #define MAX_VALUE_LENGTH 100 gH_CustomZoneCookie.Get(client, czone, sizeof(czone)); char ver = czone[0]; if (ver == 'a' || ver == 'b') // "version number" { // a = [1 + 2*2*TRACKS_SIZE + 1]; // version + ((start + end) * 2 chars * tracks) + NUL terminator // b = [1 + ZONETYPES_SIZE*2*2 + 1] // version + (ZONETYPES_SIZE * 2 chars * (main+bonus)) + NUL terminator int p = 1; char c; while ((c = czone[p++]) != 0) { int track = c & 0xf; #if CZONE_VER != 'a' if (track > Track_Bonus) { ++p; continue; } #endif int type = (c >> 4) & 1; gI_ZoneDisplayType[client][type][track] = (c >> 5) & 3; c = czone[p++]; gI_ZoneColor[client][type][track] = c & 0xf; gI_ZoneWidth[client][type][track] = (c >> 4) & 7; } } else if (ver == 'c') // back to the original :pensive: { // c = [1 + ZONETYPES_SIZE*2*3 + 1] // version = (ZONETYPES_SIZE * (main+bonus) * 3 chars) + NUL terminator // char[98] as of right now.... int p = 1; // TODO: ZONETYPES_SIZE is too big now so we'll have to come back to do something about this... // Just make another cookie :pepega: or maybe store the settings in the DB rather than cookie. //for (int type = Zone_Start; type < ZONETYPES_SIZE; type++) for (int type = Zone_Start; type <= Zone_Speedmod; type++) { for (int track = Track_Main; track <= Track_Bonus; track++) { gI_ZoneDisplayType[client][type][track] = czone[p++] - '0'; gI_ZoneColor[client][type][track] = czone[p++] - '0'; gI_ZoneWidth[client][type][track] = czone[p++] - '0'; } } } } void GetStartPosition(int client) { int steamID = GetSteamAccountID(client); if(steamID == 0 || client == 0) { return; } char query[512]; FormatEx(query, 512, "SELECT track, pos_x, pos_y, pos_z, ang_x, ang_y, ang_z, angles_only FROM %sstartpositions WHERE auth = %d AND map = '%s'", gS_MySQLPrefix, steamID, gS_Map); QueryLog(gH_SQL, SQL_GetStartPosition_Callback, query, GetClientSerial(client)); } public void SQL_GetStartPosition_Callback(Database db, DBResultSet results, const char[] error, any data) { if(results == null) { LogError("Timer (get start position) SQL query failed. Reason: %s", error); return; } int client = GetClientFromSerial(data); if(client == 0) { return; } while(results.FetchRow()) { int track = results.FetchInt(0); gF_StartPos[client][track][0] = results.FetchFloat(1); gF_StartPos[client][track][1] = results.FetchFloat(2); gF_StartPos[client][track][2] = results.FetchFloat(3); gF_StartAng[client][track][0] = results.FetchFloat(4); gF_StartAng[client][track][1] = results.FetchFloat(5); gF_StartAng[client][track][2] = results.FetchFloat(6); gB_StartAnglesOnly[client][track] = results.FetchInt(7) > 0; gB_HasSetStart[client][track] = true; } } public Action Command_SetStart(int client, int args) { if(!IsValidClient(client, true)) { Shavit_PrintToChat(client, "%T", "SetStartCommandAlive", client, gS_ChatStrings.sVariable2, gS_ChatStrings.sText); return Plugin_Handled; } int track = Shavit_GetClientTrack(client); Shavit_PrintToChat(client, "%T", "SetStart", client, gS_ChatStrings.sVariable2, gS_ChatStrings.sText); SetStart(client, track, GetEntPropEnt(client, Prop_Send, "m_hGroundEntity") == -1); return Plugin_Handled; } void SetStart(int client, int track, bool anglesonly) { gB_HasSetStart[client][track] = true; gB_StartAnglesOnly[client][track] = anglesonly; if (anglesonly) { gF_StartPos[client][track] = ZERO_VECTOR; } else { GetClientAbsOrigin(client, gF_StartPos[client][track]); } GetClientEyeAngles(client, gF_StartAng[client][track]); char query[1024]; FormatEx(query, sizeof(query), "REPLACE INTO %sstartpositions (auth, track, map, pos_x, pos_y, pos_z, ang_x, ang_y, ang_z, angles_only) VALUES (%d, %d, '%s', %.03f, %.03f, %.03f, %.03f, %.03f, %.03f, %d);", gS_MySQLPrefix, GetSteamAccountID(client), track, gS_Map, gF_StartPos[client][track][0], gF_StartPos[client][track][1], gF_StartPos[client][track][2], gF_StartAng[client][track][0], gF_StartAng[client][track][1], gF_StartAng[client][track][2], anglesonly); QueryLog(gH_SQL, SQL_InsertStartPosition_Callback, query); } public void SQL_InsertStartPosition_Callback(Database db, DBResultSet results, const char[] error, DataPack data) { if(!db || !results || error[0]) { LogError("Timer (zones) InsertStartPosition_Callback SQL query failed! (%s)", error); return; } } public Action Command_DeleteSetStart(int client, int args) { if(!IsValidClient(client)) { return Plugin_Handled; } Menu menu = new Menu(MenuHandler_DeleteSetStart); menu.SetTitle("%T\n ", "DeleteSetStartMenuTitle", client); for (int i = 0; i < TRACKS_SIZE; i++) { if (gB_HasSetStart[client][i]) { char info[8], sTrack[32]; IntToString(i, info, sizeof(info)); GetTrackName(client, i, sTrack, sizeof(sTrack)); menu.AddItem(info, sTrack); } } if (!menu.ItemCount) { delete menu; return Plugin_Handled; } menu.ExitButton = true; menu.Display(client, MENU_TIME_FOREVER); return Plugin_Handled; } public int MenuHandler_DeleteSetStart(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char info[8]; menu.GetItem(param2, info, sizeof(info)); int track = StringToInt(info); Shavit_PrintToChat(param1, "%T", "DeleteSetStart", param1, gS_ChatStrings.sWarning, gS_ChatStrings.sText); DeleteSetStart(param1, track); } else if(action == MenuAction_End) { delete menu; } return 0; } void DeleteSetStart(int client, int track) { gB_HasSetStart[client][track] = false; gF_StartPos[client][track] = ZERO_VECTOR; gF_StartAng[client][track] = ZERO_VECTOR; char query[512]; FormatEx(query, 512, "DELETE FROM %sstartpositions WHERE auth = %d AND track = %d AND map = '%s';", gS_MySQLPrefix, GetSteamAccountID(client), track, gS_Map); QueryLog(gH_SQL, SQL_DeleteSetStart_Callback, query); } public void SQL_DeleteSetStart_Callback(Database db, DBResultSet results, const char[] error, DataPack data) { if(!db || !results || error[0]) { LogError("SQL_DeleteSetStart_Callback - Query failed! (%s)", error); return; } } public Action Command_Modifier(int client, int args) { if(!IsValidClient(client)) { return Plugin_Handled; } if(args == 0) { Shavit_PrintToChat(client, "%T", "ModifierCommandNoArgs", client); return Plugin_Handled; } char sArg1[16]; GetCmdArg(1, sArg1, 16); float fArg1 = StringToFloat(sArg1); if(fArg1 <= 0.0) { Shavit_PrintToChat(client, "%T", "ModifierTooLow", client); return Plugin_Handled; } gF_Modifier[client] = fArg1; Shavit_PrintToChat(client, "%T %s%.01f%s.", "ModifierSet", client, gS_ChatStrings.sVariable, fArg1, gS_ChatStrings.sText); return Plugin_Handled; } bool CanDrawAllZones(int client) { if (!gCV_AllowDrawAllZones.BoolValue) { return false; } return gCV_AllowDrawAllZones.IntValue == 2 || CheckCommandAccess(client, "sm_zones", ADMFLAG_RCON); } public Action Command_DrawAllZones(int client, int args) { if (CanDrawAllZones(client)) { gB_DrawAllZones[client] = !gB_DrawAllZones[client]; gH_DrawAllZonesCookie.Set(client, gB_DrawAllZones[client] ? "1" : "0"); } return Plugin_Handled; } public Action Command_AddSpawn(int client, int args) { if(!IsValidClient(client)) { return Plugin_Handled; } #if 0 if (!gCV_SQLZones.BoolValue) { Shavit_PrintToChat(client, "%T", "ZonesNotSQL", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); return Plugin_Handled; } #endif return DisplayCustomSpawnMenu(client); } Action DisplayCustomSpawnMenu(int client) { Menu menu = new Menu(MenuHandler_AddCustomSpawn); menu.SetTitle("%T\n ", "ZoneCustomSpawnMenuTitle", client); for(int i = 0; i < TRACKS_SIZE; i++) { char sInfo[8]; IntToString(i, sInfo, 8); char sTrack[32]; GetTrackName(client, i, sTrack, 32); menu.AddItem(sInfo, sTrack); } menu.ExitButton = true; menu.Display(client, 300); return Plugin_Handled; } public int MenuHandler_AddCustomSpawn(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { if(!IsPlayerAlive(param1)) { Shavit_PrintToChat(param1, "%T", "ZoneDead", param1); return 0; } char sInfo[8]; menu.GetItem(param2, sInfo, 8); int iTrack = StringToInt(sInfo); if(!EmptyVector(gF_CustomSpawn[iTrack])) { char sTrack[32]; GetTrackName(param1, iTrack, sTrack, 32); Shavit_PrintToChat(param1, "%T", "ZoneCustomSpawnExists", param1, gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText); return 0; } zone_cache_t cache; cache.iType = Zone_CustomSpawn; cache.iTrack = iTrack; cache.iDatabaseID = -1; GetClientAbsOrigin(param1, cache.fDestination); gF_CustomSpawn[iTrack] = cache.fDestination; for (int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iType == Zone_CustomSpawn && gA_ZoneCache[i].iTrack == iTrack && StrEqual(gA_ZoneCache[i].sSource, "sql")) { cache.iDatabaseID = gA_ZoneCache[i].iDatabaseID; break; } } gA_EditCache[param1] = cache; InsertZone(param1); } else if(action == MenuAction_End) { delete menu; } return 0; } public Action Command_DelSpawn(int client, int args) { if(!IsValidClient(client)) { return Plugin_Handled; } return DisplayCustomSpawnDeleteMenu(client); } Action DisplayCustomSpawnDeleteMenu(int client) { Menu menu = new Menu(MenuHandler_DeleteCustomSpawn); menu.SetTitle("%T\n ", "ZoneCustomSpawnMenuDeleteTitle", client); for(int i = 0; i < TRACKS_SIZE; i++) { char sInfo[8]; IntToString(i, sInfo, 8); char sTrack[32]; GetTrackName(client, i, sTrack, 32); menu.AddItem(sInfo, sTrack, (EmptyVector(gF_CustomSpawn[i]))? ITEMDRAW_DISABLED:ITEMDRAW_DEFAULT); } menu.ExitButton = true; menu.Display(client, 300); return Plugin_Handled; } public int MenuHandler_DeleteCustomSpawn(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char sInfo[8]; menu.GetItem(param2, sInfo, 8); int iTrack = StringToInt(sInfo); if(EmptyVector(gF_CustomSpawn[iTrack])) { char sTrack[32]; GetTrackName(param1, iTrack, sTrack, 32); Shavit_PrintToChat(param1, "%T", "ZoneCustomSpawnMissing", param1, gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText); return 0; } gF_CustomSpawn[iTrack] = ZERO_VECTOR; Shavit_LogMessage("%L - deleted custom spawn from map `%s`.", param1, gS_Map); char sQuery[512]; FormatEx(sQuery, sizeof(sQuery), "DELETE FROM %smapzones WHERE type = %d AND map = '%s' AND track = %d;", gS_MySQLPrefix, Zone_CustomSpawn, gS_Map, iTrack); QueryLog(gH_SQL, SQL_DeleteCustom_Spawn_Callback, sQuery, GetClientSerial(param1)); } else if(action == MenuAction_End) { delete menu; } return 0; } public void SQL_DeleteCustom_Spawn_Callback(Database db, DBResultSet results, const char[] error, any data) { if(results == null) { LogError("Timer (custom spawn delete) SQL query failed. Reason: %s", error); return; } int client = GetClientFromSerial(data); if(client == 0) { return; } Shavit_PrintToChat(client, "%T", "ZoneCustomSpawnDelete", client); } public Action Command_TpToZone(int client, int args) { if (!IsValidClient(client)) { return Plugin_Handled; } return OpenTpToZoneMenu(client); } public Action Command_ZoneEdit(int client, int args) { if(!IsValidClient(client)) { return Plugin_Handled; } #if 0 if (!gCV_SQLZones.BoolValue) { Shavit_PrintToChat(client, "%T", "ZonesNotSQL", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); return Plugin_Handled; } #endif Reset(client); return OpenEditMenu(client); } public Action Command_HookZone(int client, int args) { if (!IsValidClient(client)) { return Plugin_Handled; } #if 0 if (!gCV_SQLZones.BoolValue) { Shavit_PrintToChat(client, "%T", "ZonesNotSQL", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); return Plugin_Handled; } #endif OpenHookMenu_Form(client); return Plugin_Handled; } public Action Command_ReloadZoneSettings(int client, int args) { LoadZoneSettings(); ReplyToCommand(client, "Reloaded zone settings."); return Plugin_Handled; } // Was originally used in beamer to replicate this gmod lua code: /* My code from tracegun.lua that I based this off of: local ang = tr.Normal:Angle() // calculate in SP with: ( traceRes.HitPos - traceRes.StartPos ):Normalize() ang:RotateAroundAxis(tr.HitNormal, 180) local dir = ang:Forward()*-1 tr = util.TraceLine({start=tr.HitPos, endpos=tr.HitPos+(dir*100000), filter=players}) */ stock void RotateAroundAxis(float v[3], const float in_k[3], float theta) { // https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula#Statement // vrot = (v * cos(theta)) + ((k x v) * sin(theta)) + (k * (k . v) * (1 - cos(theta))) float k[3]; k[0] = DegToRad(in_k[1]); k[1] = DegToRad(in_k[2]); k[2] = DegToRad(in_k[0]); // right-hand rule related ordering? NormalizeVector(k, k); theta = DegToRad(theta); float theta_cos = Cosine(theta); float theta_sin = Sine(theta); float one_minus_theta_cos = 1.0 - theta_cos; float kv_dot = GetVectorDotProduct(k, v); float kv_cross[3]; GetVectorCrossProduct(k, v, kv_cross); for (int i = 0; i < 3; i++) { v[i] = (v[i] * theta_cos) + (kv_cross[i] * theta_sin) + (k[i] * kv_dot * one_minus_theta_cos); } } stock void ReflectAngles(float direction[3], const float normal[3]) { float fwd[3], reflected[3]; GetAngleVectors(direction, fwd, NULL_VECTOR, NULL_VECTOR); float dot = GetVectorDotProduct(fwd, normal); for (int i = 0; i < 3; i++) { reflected[i] = fwd[i] - 2.0 * dot * normal[i]; } NormalizeVector(reflected, reflected); GetVectorAngles(reflected, direction); } public Action Command_Beamer(int client, int args) { static float rate_limit[MAXPLAYERS+1]; float now = GetEngineTime(); if (rate_limit[client] > now) return Plugin_Handled; rate_limit[client] = now + 0.2; float startpos[3], endpos[3], direction[3]; GetClientEyePosition(client, startpos); startpos[2] -= 3.0; GetClientEyeAngles(client, direction); float delay = 0.0; for (int C = 20; C >= 0; --C) { TR_TraceRayFilter(startpos, direction, MASK_ALL, RayType_Infinite, TRFilter_NoPlayers, client); TR_GetEndPosition(endpos); TE_SetupBeamPoints( startpos, endpos, gI_BeamSpriteIgnoreZ, gA_ZoneSettings[Zone_Start][Track_Main].iHalo, 0, // StartFrame 0, // FrameRate 10.0, // Life 1.0, // Width 1.0, // EndWidth 0, // FadeLength 0.0, // Amplitude {255, 255, 0, 192}, // Colour 20 // Speed ); TE_SendToClient(client, delay); delay += 0.11; if (!C) break; startpos = endpos; float hitnormal[3]; TR_GetPlaneNormal(INVALID_HANDLE, hitnormal); ReflectAngles(direction, hitnormal); } return Plugin_Handled; } public Action Command_Stages(int client, int args) { if(!IsValidClient(client)) { return Plugin_Handled; } if(!IsPlayerAlive(client)) { Shavit_PrintToChat(client, "%T", "StageCommandAlive", client, gS_ChatStrings.sVariable2, gS_ChatStrings.sText); return Plugin_Handled; } int iStage = -1; char sCommand[16]; GetCmdArg(0, sCommand, 16); if ('0' <= sCommand[4] <= '9') { iStage = sCommand[4] - '0'; } else if (args > 0) { char arg1[8]; GetCmdArg(1, arg1, 8); iStage = StringToInt(arg1); } if (iStage > -1) { for(int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iType == Zone_Stage && gA_ZoneCache[i].iData == iStage) { Shavit_StopTimer(client); if (!EmptyVector(gA_ZoneCache[i].fDestination)) { TeleportEntity(client, gA_ZoneCache[i].fDestination, NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } else { TeleportEntity(client, gV_ZoneCenter[i], NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } } } } else { Menu menu = new Menu(MenuHandler_SelectStage); menu.SetTitle("%T", "ZoneMenuStage", client); char sDisplay[64]; for(int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iType == Zone_Stage) { char sTrack[32]; GetTrackName(client, gA_ZoneCache[i].iTrack, sTrack, 32); FormatEx(sDisplay, 64, "#%d - %T (%s)", (i + 1), "ZoneSetStage", client, gA_ZoneCache[i].iData, sTrack); char sInfo[8]; IntToString(i, sInfo, 8); menu.AddItem(sInfo, sDisplay); } } menu.ExitButton = true; menu.Display(client, MENU_TIME_FOREVER); } return Plugin_Handled; } public Action Command_StageRestart(int client, int args) { // This command should only work on surf maps for now // There are quite a few bhop maps that have checkpoint triggers and this command would ruin those maps // Ideally there would be a zone-based solution to this problem if(!IsValidClient(client) || strncmp(gS_Map, "surf_", 5)) { return Plugin_Handled; } if (!gCV_EnableStageRestart.BoolValue) { Shavit_PrintToChat(client, "!stagerestart is disabled!"); // untranslated strings in 2024... return Plugin_Handled; } if(!IsPlayerAlive(client)) { Shavit_PrintToChat(client, "%T", "StageCommandAlive", client); return Plugin_Handled; } int last = gI_LastStage[client]; int track = Shavit_GetClientTrack(client); // crude way to prevent cheesing if (InsideZone(client, Zone_Stage, track) || InsideZone(client, Zone_Start, -1)) { return Plugin_Handled; } if (last <= 0 || Shavit_GetTimerStatus(client) == Timer_Stopped || InsideZone(client, Zone_End, track)) { Shavit_RestartTimer(client, track); } else { for(int i = 0; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iType == Zone_Stage && gA_ZoneCache[i].iData == last && gA_ZoneCache[i].iTrack == track) { if (!EmptyVector(gA_ZoneCache[i].fDestination)) { TeleportEntity(client, gA_ZoneCache[i].fDestination, NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } else { TeleportEntity(client, gV_ZoneCenter[i], NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } } } } return Plugin_Handled; } public int MenuHandler_SelectStage(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char sInfo[8]; menu.GetItem(param2, sInfo, 8); int iIndex = StringToInt(sInfo); Shavit_StopTimer(param1); if (!EmptyVector(gA_ZoneCache[iIndex].fDestination)) { TeleportEntity(param1, gA_ZoneCache[iIndex].fDestination, NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } else { TeleportEntity(param1, gV_ZoneCenter[iIndex], NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } } else if(action == MenuAction_End) { delete menu; } return 0; } public Action Command_Zones(int client, int args) { if (!gH_SQL) { Shavit_PrintToChat(client, "Database not loaded. Check your error logs."); return Plugin_Handled; } if(!IsValidClient(client)) { return Plugin_Handled; } if(!IsPlayerAlive(client)) { Shavit_PrintToChat(client, "%T", "ZonesCommand", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); return Plugin_Handled; } #if 0 if (!gCV_SQLZones.BoolValue) { Shavit_PrintToChat(client, "%T", "ZonesNotSQL", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); return Plugin_Handled; } #endif Reset(client); Menu menu = new Menu(MenuHandler_SelectZoneTrack); menu.SetTitle("%T", "ZoneMenuTrack", client); for(int i = 0; i < TRACKS_SIZE; i++) { char sInfo[8]; IntToString(i, sInfo, 8); char sDisplay[16]; GetTrackName(client, i, sDisplay, 16); menu.AddItem(sInfo, sDisplay); } menu.ExitBackButton = true; menu.Display(client, 300); return Plugin_Handled; } public int MenuHandler_SelectZoneTrack(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char sInfo[8]; menu.GetItem(param2, sInfo, 8); gA_EditCache[param1].iTrack = StringToInt(sInfo); char sTrack[16]; GetTrackName(param1, gA_EditCache[param1].iTrack, sTrack, 16); Menu submenu = new Menu(MenuHandler_SelectZoneType); submenu.SetTitle("%T\n ", "ZoneMenuTitle", param1, sTrack); char sZoneName[32]; for(int i = 0; i < ZONETYPES_SIZE; i++) { if(i == Zone_CustomSpawn) { continue; } GetZoneName(param1, i, sZoneName, sizeof(sZoneName)); IntToString(i, sInfo, 8); submenu.AddItem(sInfo, sZoneName); } submenu.ExitButton = true; submenu.Display(param1, 300); } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { gH_AdminMenu.DisplayCategory(gH_TimerCommands, param1); } else if(action == MenuAction_End) { delete menu; } return 0; } Action OpenTpToZoneMenu(int client, int pagepos=0) { Menu menu = new Menu(MenuHandler_TpToEdit); menu.SetTitle("%T\n ", "TpToZone", client); int newPageInterval = (gEV_Type == Engine_CSGO) ? 6 : 7; char sDisplay[128]; FormatEx(sDisplay, sizeof(sDisplay), "%T", "ZoneEditRefresh", client); menu.AddItem("-2", sDisplay); for (int i = 0; i < gI_MapZones; i++) { if ((menu.ItemCount % newPageInterval) == 0) { FormatEx(sDisplay, sizeof(sDisplay), "%T", "ZoneEditRefresh", client); menu.AddItem("-2", sDisplay); } char sInfo[8]; IntToString(i, sInfo, 8); char sTarget[64]; switch (gA_ZoneCache[i].iForm) { case ZoneForm_func_button, ZoneForm_trigger_multiple, ZoneForm_trigger_teleport: { FormatEx(sTarget, sizeof(sTarget), " (%s)", gA_ZoneCache[i].sTarget); } } char sTrack[32]; GetTrackName(client, gA_ZoneCache[i].iTrack, sTrack, 32); char sZoneName[32]; GetZoneName(client, gA_ZoneCache[i].iType, sZoneName, sizeof(sZoneName)); if (gA_ZoneCache[i].iType == Zone_CustomSpeedLimit || gA_ZoneCache[i].iType == Zone_Stage || gA_ZoneCache[i].iType == Zone_Airaccelerate) { FormatEx(sDisplay, sizeof(sDisplay), "#%d - %s %d (%s)%s", (i + 1), sZoneName, gA_ZoneCache[i].iData, sTrack, sTarget); } else if (gA_ZoneCache[i].iType == Zone_Gravity || gA_ZoneCache[i].iType == Zone_Speedmod) { FormatEx(sDisplay, 64, "#%d - %s %.2f (%s)", (i + 1), sZoneName, gA_ZoneCache[i].iData, sTrack); } else { FormatEx(sDisplay, sizeof(sDisplay), "#%d - %s (%s)%s", (i + 1), sZoneName, sTrack, sTarget); } if (gB_InsideZoneID[client][i]) { Format(sDisplay, sizeof(sDisplay), "%s %T", sDisplay, "ZoneInside", client); } menu.AddItem(sInfo, sDisplay, ITEMDRAW_DEFAULT); } menu.ExitBackButton = true; menu.DisplayAt(client, pagepos, MENU_TIME_FOREVER); return Plugin_Handled; } public int MenuHandler_TpToEdit(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { char info[8]; menu.GetItem(param2, info, 8); int id = StringToInt(info); switch (id) { case -2: { } case -1: { Shavit_PrintToChat(param1, "%T", "ZonesMenuNoneFound", param1); } default: { Shavit_StopTimer(param1); float fCenter[3]; fCenter[0] = gV_ZoneCenter[id][0]; fCenter[1] = gV_ZoneCenter[id][1]; fCenter[2] = gA_ZoneCache[id].fCorner1[2]; TeleportEntity(param1, fCenter, NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } } OpenTpToZoneMenu(param1, GetMenuSelectionPosition()); } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { gH_AdminMenu.DisplayCategory(gH_TimerCommands, param1); } else if (action == MenuAction_End) { delete menu; } return 0; } public int MenuHandler_HookZone_Editor(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { char info[32]; menu.GetItem(param2, info, sizeof(info)); if (StrEqual(info, "tpto")) { Shavit_StopTimer(param1); float center[3]; center[0] = (gA_EditCache[param1].fCorner1[0] + gA_EditCache[param1].fCorner2[0]) * 0.5; center[1] = (gA_EditCache[param1].fCorner1[1] + gA_EditCache[param1].fCorner2[1]) * 0.5; center[2] = gA_EditCache[param1].fCorner1[2] + 1.0; TeleportEntity(param1, center, NULL_VECTOR, ZERO_VECTOR); } else if (StrEqual(info, "track")) { if ((gA_EditCache[param1].iTrack += 1) > Track_Bonus_Last) gA_EditCache[param1].iTrack = 0; } else if (StrEqual(info, "ztype")) { static int allowed_types[] = { // ZoneForm_Box (unused) 0 // ZoneForm_trigger_multiple , (1 << Zone_Start) | (1 << Zone_End) | (1 << Zone_Respawn) | (1 << Zone_Stop) | (1 << Zone_Slay) | (1 << Zone_Freestyle) | (1 << Zone_CustomSpeedLimit) | (1 << Zone_Teleport) | (1 << Zone_Easybhop) | (1 << Zone_Slide) | (1 << Zone_Airaccelerate) | (1 << Zone_Stage) | (1 << Zone_NoTimerGravity) | (1 << Zone_Gravity) | (1 << Zone_Speedmod) | (1 << Zone_NoJump) | (1 << Zone_Autobhop) // ZoneForm_trigger_teleport , (1 << Zone_End) | (1 << Zone_Respawn) | (1 << Zone_Stop) | (1 << Zone_Slay) | (1 << Zone_Freestyle) | (1 << Zone_CustomSpeedLimit) | (1 << Zone_Stage) | (1 << Zone_NoTimerGravity) // ZoneForm_func_button , (1 << Zone_Start) | (1 << Zone_End) | (1 << Zone_Stop) | (1 << Zone_Slay) | (1 << Zone_Stage) }; int form = gA_EditCache[param1].iForm; for (int i = 0; i < 100; i++) // no infinite loops = good :) { if (++gA_EditCache[param1].iType >= ZONETYPES_SIZE) gA_EditCache[param1].iType = 0; if (allowed_types[form] & (1 << gA_EditCache[param1].iType)) break; } } else if (StrEqual(info, "htype")) { if (gA_EditCache[param1].iFlags == -1) gA_EditCache[param1].iFlags = 0; else if (gA_EditCache[param1].iFlags & ZF_Origin) gA_EditCache[param1].iFlags &= ~ZF_Origin; else if (gA_EditCache[param1].iFlags & ZF_Hammerid) gA_EditCache[param1].iFlags ^= ZF_Hammerid|ZF_Origin; else gA_EditCache[param1].iFlags |= ZF_Hammerid; } else if (StrEqual(info, "hook")) { if (gA_EditCache[param1].iFlags & ZF_Hammerid) IntToString(GetEntProp(gA_EditCache[param1].iEntity, Prop_Data, "m_iHammerID"), gA_EditCache[param1].sTarget, sizeof(gA_EditCache[].sTarget)); else if (gA_EditCache[param1].iFlags & ZF_Origin) EntOriginString(gA_EditCache[param1].iEntity, gA_EditCache[param1].sTarget, false); else GetEntPropString(gA_EditCache[param1].iEntity, Prop_Data, gA_EditCache[param1].iForm == ZoneForm_trigger_teleport ? "m_target" : "m_iName", gA_EditCache[param1].sTarget, sizeof(gA_EditCache[].sTarget)); CreateEditMenu(param1, true); return 0; } OpenHookMenu_Editor(param1); } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { OpenHookMenu_List(param1, gA_EditCache[param1].iForm, gI_HookListPos[param1]); } else if (action == MenuAction_End) { delete menu; } return 0; } void OpenHookMenu_Editor(int client) { int ent = gA_EditCache[client].iEntity; int form = gA_EditCache[client].iForm; int track = gA_EditCache[client].iTrack; int hooktype = gA_EditCache[client].iFlags; int zonetype = gA_EditCache[client].iType; char classname[32], targetname[64], hammerid[16], sOrigin[64]; GetEntityClassname(ent, classname, sizeof(classname)); GetEntPropString(ent, Prop_Data, form == ZoneForm_trigger_teleport ? "m_target" : "m_iName", targetname, sizeof(targetname)); IntToString(GetEntProp(ent, Prop_Data, "m_iHammerID"), hammerid, sizeof(hammerid)); EntOriginString(ent, sOrigin, true); Menu menu = new Menu(MenuHandler_HookZone_Editor); menu.SetTitle("%s\nhammerid = %s\n%s = '%s'\norigin = %s\n ", classname, hammerid, form == ZoneForm_trigger_teleport ? "target" : "targetname", targetname, sOrigin); char display[128], buf[32]; FormatEx(display, sizeof(display), "%T\n ", "ZoneHook_Tpto", client); menu.AddItem("tpto", display);//, form == ZoneForm_trigger_teleport ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); GetTrackName(client, track, buf, sizeof(buf), true); FormatEx(display, sizeof(display), "%T", "ZoneEditTrack", client, buf); menu.AddItem("track", display); GetZoneName(client, zonetype, buf, sizeof(buf)); FormatEx(display, sizeof(display), "%T", "ZoneHook_Zonetype", client, buf); menu.AddItem("ztype", display); FormatEx(display, sizeof(display), "%T\n ", "ZoneHook_Hooktype", client, (hooktype == -1) ? "UNKNOWN" : (hooktype & ZF_Hammerid) ? "hammerid" : ((hooktype & ZF_Origin) ? "origin" : (form == ZoneForm_trigger_teleport ? "target" : "targetname")), (hooktype == -1) ? "": (hooktype & ZF_Hammerid) ? hammerid : ((hooktype & ZF_Origin) ? sOrigin : targetname) ); menu.AddItem("htype", display); FormatEx(display, sizeof(display), "%T", "ZoneHook_Confirm", client); menu.AddItem( "hook", display, (zonetype != -1 && hooktype != -1 && track != -1) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED ); menu.ExitBackButton = true; menu.Display(client, MENU_TIME_FOREVER); } void HookZone_SetupEditor(int client, int ent) { float origin[3]; GetEntPropVector(ent, Prop_Send, "m_vecOrigin", origin); GetEntPropVector(ent, Prop_Send, "m_vecMins", gA_EditCache[client].fCorner1); GetEntPropVector(ent, Prop_Send, "m_vecMaxs", gA_EditCache[client].fCorner2); origin[2] += 1.0; // so you don't get stuck in the ground AddVectors(origin, gA_EditCache[client].fCorner1, gA_EditCache[client].fCorner1); AddVectors(origin, gA_EditCache[client].fCorner2, gA_EditCache[client].fCorner2); gI_MapStep[client] = 3; gA_EditCache[client].iEntity = ent; gA_EditCache[client].iType = -1; gA_EditCache[client].iTrack = -1; gA_EditCache[client].iFlags = -1; OpenHookMenu_Editor(client); //if (gA_EditCache[client].iForm == ZoneForm_trigger_multiple) gH_StupidTimer[client] = CreateTimer(0.1, Timer_Draw, GetClientSerial(client), TIMER_REPEAT); } public int MenuHandle_HookZone_List(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { char info[20]; menu.GetItem(param2, info, sizeof(info)); int ent = EntRefToEntIndex(StringToInt(info)); if (ent <= MaxClients) { Shavit_PrintToChat(param1, "Invalid entity index???"); OpenHookMenu_List(param1, gA_EditCache[param1].iForm, 0); return 0; } gI_HookListPos[param1] = GetMenuSelectionPosition(); HookZone_SetupEditor(param1, ent); } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { OpenHookMenu_Form(param1); } else if (action == MenuAction_End) { delete menu; } return 0; } enum struct ent_list_thing { float dist; int ent; } void OpenHookMenu_List(int client, int form, int pos = 0) { Reset(client); gA_EditCache[client].iForm = form; gA_EditCache[client].sSource = "sql"; float player_origin[3]; GetClientAbsOrigin(client, player_origin); char classname[32]; classname = (form == ZoneForm_trigger_multiple) ? "trigger_multiple" : ((form == ZoneForm_trigger_teleport) ? "trigger_teleport" : "func_button"); char targetname[64], info[20], display[128]; int ent = -1; ArrayList list = new ArrayList(sizeof(ent_list_thing)); while ((ent = FindEntityByClassname(ent, classname)) != -1) { if (gI_EntityZone[ent] > -1) continue; float ent_origin[3]; GetEntPropVector(ent, Prop_Send, "m_vecOrigin", ent_origin); ent_list_thing thing; thing.dist = GetVectorDistance(player_origin, ent_origin); thing.ent = ent; list.PushArray(thing); } // copy & paste for func_rot_button because it's shrimple and I can't think of how to do it cleanly otherwise right now if (form == ZoneForm_func_button) { while ((ent = FindEntityByClassname(ent, "func_rot_button")) != -1) { if (gI_EntityZone[ent] > -1) continue; float ent_origin[3]; GetEntPropVector(ent, Prop_Send, "m_vecOrigin", ent_origin); ent_list_thing thing; thing.dist = GetVectorDistance(player_origin, ent_origin); thing.ent = ent; list.PushArray(thing); } } if (!list.Length) { Shavit_PrintToChat(client, "No unhooked entities found"); delete list; OpenHookMenu_Form(client); return; } list.Sort(Sort_Ascending, Sort_Float); Menu menu = new Menu(MenuHandle_HookZone_List); menu.SetTitle("%T\n ", "HookZone2", client, classname); for (int i = 0; i < list.Length; i++) { ent_list_thing thing; list.GetArray(i, thing); GetEntPropString(thing.ent, Prop_Data, form == ZoneForm_trigger_teleport ? "m_target" : "m_iName", targetname, sizeof(targetname)); if (form == ZoneForm_trigger_multiple && StrContains(targetname, "shavit_zones_") == 0) { continue; } FormatEx(display, sizeof(display), "%s | %d dist=%.1fm", targetname, GetEntProp(thing.ent, Prop_Data, "m_iHammerID"), thing.dist*0.01905); FormatEx(info, sizeof(info), "%d", EntIndexToEntRef(thing.ent)); menu.AddItem(info, display); } delete list; if (!menu.ItemCount) { Shavit_PrintToChat(client, "No unhooked entities found"); delete menu; OpenHookMenu_Form(client); return; } menu.ExitBackButton = true; menu.DisplayAt(client, pos, MENU_TIME_FOREVER); } bool TeleportFilter(int entity) { char classname[20]; GetEntityClassname(entity, classname, sizeof(classname)); if (StrEqual(classname, "trigger_teleport") || StrEqual(classname, "trigger_multiple") || StrEqual(classname, "func_button") || StrEqual(classname, "func_rot_button")) { //TR_ClipCurrentRayToEntity(MASK_ALL, entity); gI_CurrentTraceEntity = entity; return false; } return true; } public int MenuHandle_HookZone_Form(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { char info[20]; menu.GetItem(param2, info, sizeof(info)); int form = StringToInt(info); if (form != -1) { OpenHookMenu_List(param1, form, 0); return 0; } // entity under crosshair float origin[3], endpos[3]; GetClientEyePosition(param1, origin); GetClientEyeAngles(param1, endpos); GetAngleVectors(endpos, endpos, NULL_VECTOR, NULL_VECTOR); ScaleVector(endpos, 30000.0); AddVectors(origin, endpos, endpos); gI_CurrentTraceEntity = 0; // had some troubles in mpbhops_but_working with TR_EnumerateEntitiesHull. So I did this. And copied it to bhoptimer TR_EnumerateEntitiesHull(origin, endpos, view_as({-8.0, -8.0, 0.0}), view_as({8.0, 8.0, 0.0}), PARTITION_TRIGGER_EDICTS, TeleportFilter, 0); int ent = gI_CurrentTraceEntity; if (ent <= MaxClients || ent >= 2048) { Shavit_PrintToChat(param1, "Couldn't find entity under crosshair"); OpenHookMenu_Form(param1); return 0; } char classname[32]; GetEntityClassname(ent, classname, sizeof(classname)); if (StrEqual(classname, "func_button") || StrEqual(classname, "func_rot_button")) { form = ZoneForm_func_button; } else if (StrEqual(classname, "trigger_multiple")) { form = ZoneForm_trigger_multiple; } else if (StrEqual(classname, "trigger_teleport")) { form = ZoneForm_trigger_teleport; } else { Shavit_PrintToChat(param1, "Entity class %s (%d) not supported", classname, ent); OpenHookMenu_Form(param1); return 0; } if (gI_EntityZone[ent] > -1) { Shavit_PrintToChat(param1, "Entity %s (%d) is already hooked", classname, ent); OpenHookMenu_Form(param1); return 0; } Reset(param1); gI_HookListPos[param1] = 0; gA_EditCache[param1].iForm = form; gA_EditCache[param1].sSource = "sql"; HookZone_SetupEditor(param1, ent); } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { gH_AdminMenu.DisplayCategory(gH_TimerCommands, param1); } else if (action == MenuAction_End) { delete menu; } return 0; } void OpenHookMenu_Form(int client) { Reset(client); Menu menu = new Menu(MenuHandle_HookZone_Form); menu.SetTitle("%T\n ", "HookZone", client); char display[128]; FormatEx(display, sizeof(display), "%T\n ", "ZoneHook_Crosshair", client); menu.AddItem("-1", display); // hardcoded ZoneForm_ values menu.AddItem("3", "func_button"); menu.AddItem("1", "trigger_multiple"); menu.AddItem("2", "trigger_teleport"); menu.ExitBackButton = true; menu.Display(client, MENU_TIME_FOREVER); } Action OpenEditMenu(int client, int pos = 0) { Menu menu = new Menu(MenuHandler_ZoneEdit); menu.SetTitle("%T\n ", "ZoneEditTitle", client); int newPageInterval = (gEV_Type == Engine_CSGO) ? 6 : 7; char sDisplay[128]; FormatEx(sDisplay, sizeof(sDisplay), "%T", "ZoneEditRefresh", client); menu.AddItem("-2", sDisplay); for(int i = 0; i < gI_MapZones; i++) { if ((menu.ItemCount % newPageInterval) == 0) { FormatEx(sDisplay, sizeof(sDisplay), "%T", "ZoneEditRefresh", client); menu.AddItem("-2", sDisplay); } char sInfo[8]; IntToString(i, sInfo, 8); char sTarget[64]; switch (gA_ZoneCache[i].iForm) { case ZoneForm_func_button, ZoneForm_trigger_multiple, ZoneForm_trigger_teleport: { FormatEx(sTarget, sizeof(sTarget), " (%s)", gA_ZoneCache[i].sTarget); } } char sTrack[32]; GetTrackName(client, gA_ZoneCache[i].iTrack, sTrack, 32); char sZoneName[32]; GetZoneName(client, gA_ZoneCache[i].iType, sZoneName, sizeof(sZoneName)); if (gA_ZoneCache[i].iType == Zone_CustomSpeedLimit || gA_ZoneCache[i].iType == Zone_Stage || gA_ZoneCache[i].iType == Zone_Airaccelerate) { FormatEx(sDisplay, sizeof(sDisplay), "#%d - %s %d (%s)%s", (i + 1), sZoneName, gA_ZoneCache[i].iData, sTrack, sTarget); } else if (gA_ZoneCache[i].iType == Zone_Gravity || gA_ZoneCache[i].iType == Zone_Speedmod) { FormatEx(sDisplay, 64, "#%d - %s %.2f (%s)", (i + 1), sZoneName, gA_ZoneCache[i].iData, sTrack); } else { FormatEx(sDisplay, sizeof(sDisplay), "#%d - %s (%s)%s", (i + 1), sZoneName, sTrack, sTarget); } if(gB_InsideZoneID[client][i]) { Format(sDisplay, sizeof(sDisplay), "%s %T", sDisplay, "ZoneInside", client); } menu.AddItem(sInfo, sDisplay, ITEMDRAW_DEFAULT); } menu.ExitBackButton = true; menu.DisplayAt(client, pos, MENU_TIME_FOREVER); return Plugin_Handled; } public int MenuHandler_ZoneEdit(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char info[8]; menu.GetItem(param2, info, 8); int id = StringToInt(info); switch(id) { case -2: { OpenEditMenu(param1, GetMenuSelectionPosition()); } case -1: { Shavit_PrintToChat(param1, "%T", "ZonesMenuNoneFound", param1); } default: { // a hack to place the player in the last step of zone editing gI_MapStep[param1] = 3; gA_EditCache[param1] = gA_ZoneCache[id]; gI_ZoneID[param1] = id; gI_LastMenuPos[param1] = GetMenuSelectionPosition(); // draw the zone edit CreateTimer(0.1, Timer_Draw, GetClientSerial(param1), TIMER_REPEAT); CreateEditMenu(param1); } } } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { gH_AdminMenu.DisplayCategory(gH_TimerCommands, param1); } else if(action == MenuAction_End) { delete menu; } return 0; } public Action Command_CustomZones(int client, int args) { if (!client) return Plugin_Handled; OpenCustomZoneMenu(client); return Plugin_Handled; } void OpenCustomZoneMenu(int client, int pos=0) { Menu menu = new Menu(MenuHandler_CustomZones); menu.SetTitle("%T", "CustomZone_MainMenuTitle", client); // Only start zone and end zone are customizable imo, why do you even want to customize the zones that arent often used/seen??? #if CZONE_VER == 'b' for (int i = 0; i <= Track_Bonus; i++) { for (int j = 0; j < ZONETYPES_SIZE; j++) #else for (int i = 0; i < TRACKS_SIZE; i++) { for (int j = 0; j <= Zone_End; j++) #endif { if (j != Zone_CustomSpawn)// && gA_ZoneSettings[j][i].bVisible) { char info[8]; FormatEx(info, sizeof(info), "%i;%i", i, j); char trackName[32], zoneName[32], display[64]; GetTrackName(client, i, trackName, sizeof(trackName), !(CZONE_VER == 'b')); GetZoneName(client, j, zoneName, sizeof(zoneName)); FormatEx(display, sizeof(display), "%s - %s", trackName, zoneName); menu.AddItem(info, display); } } } menu.DisplayAt(client, pos, MENU_TIME_FOREVER); } public int MenuHandler_CustomZones(Menu menu, MenuAction action, int client, int position) { if(action == MenuAction_Select) { char info[8]; menu.GetItem(position, info, sizeof(info)); char exploded[2][4]; ExplodeString(info, ";", exploded, 2, 4); int track = StringToInt(exploded[0]); int zoneType = StringToInt(exploded[1]); gI_LastMenuPos[client] = GetMenuSelectionPosition(); OpenSubCustomZoneMenu(client, track, zoneType); } else if(action == MenuAction_End) { delete menu; } return 0; } void OpenSubCustomZoneMenu(int client, int track, int zoneType) { Menu menu = new Menu(MenuHandler_SubCustomZones); char trackName[32], zoneName[32]; GetTrackName(client, track, trackName, sizeof(trackName)); GetZoneName(client, zoneType, zoneName, sizeof(zoneName)); menu.SetTitle("%T", "CustomZone_SubMenuTitle", client, trackName, zoneName); char info[16], display[64]; static char displayName[ZoneDisplay_Size][] = { "CustomZone_Default", "CustomZone_DisplayType_Flat", "CustomZone_DisplayType_Box", "CustomZone_DisplayType_None", }; static char colorName[ZoneColor_Size][] = { "CustomZone_Default", "CustomZone_Color_White", "CustomZone_Color_Red", "CustomZone_Color_Orange", "CustomZone_Color_Yellow", "CustomZone_Color_Green", "CustomZone_Color_Cyan", "CustomZone_Color_Blue", "CustomZone_Color_Purple", "CustomZone_Color_Pink" }; static char widthName[ZoneWidth_Size][] = { "CustomZone_Default", "CustomZone_Width_UltraThin", "CustomZone_Width_Thin", "CustomZone_Width_Normal", "CustomZone_Width_Thick" }; FormatEx(info, sizeof(info), "%i;%i;0", track, zoneType); FormatEx(display, sizeof(display), "%T: %T", "CustomZone_DisplayType", client, displayName[gI_ZoneDisplayType[client][zoneType][track]], client); menu.AddItem(info, display); FormatEx(info, sizeof(info), "%i;%i;1", track, zoneType); FormatEx(display, sizeof(display), "%T: %T", "CustomZone_Color", client, colorName[gI_ZoneColor[client][zoneType][track]], client); menu.AddItem(info, display); FormatEx(info, sizeof(info), "%i;%i;2", track, zoneType); FormatEx(display, sizeof(display), "%T: %T", "CustomZone_Width", client, widthName[gI_ZoneWidth[client][zoneType][track]], client); menu.AddItem(info, display); menu.ExitBackButton = true; menu.Display(client, MENU_TIME_FOREVER); } void HandleCustomZoneCookie(int client) { char buf[100]; // #define MAX_VALUE_LENGTH 100 int p = 0; #if CZONE_VER >= 'b' for (int type = Zone_Start; type < ZONETYPES_SIZE; type++) { for (int track = Track_Main; track <= Track_Bonus; track++) #else for (int type = Zone_Start; type <= Zone_End; type++) { for (int track = Track_Main; track < TRACKS_SIZE; track++) #endif { #if CZONE_VER == 'c' if (!p) buf[p++] = CZONE_VER; buf[p++] = '0' + gI_ZoneDisplayType[client][type][track]; buf[p++] = '0' + gI_ZoneColor[client][type][track]; buf[p++] = '0' + gI_ZoneWidth[client][type][track]; #else if (gI_ZoneDisplayType[client][type][track] || gI_ZoneColor[client][type][track] || gI_ZoneWidth[client][type][track]) { if (!p) buf[p++] = CZONE_VER; // highest bit (0x80) set so we don't get a zero byte terminating the cookie early buf[p++] = 0x80 | (gI_ZoneDisplayType[client][type][track] << 5) | (type << 4) | track; buf[p++] = 0x80 | (gI_ZoneWidth[client][type][track] << 4) | gI_ZoneColor[client][type][track]; } #endif } } gH_CustomZoneCookie.Set(client, buf); } public int MenuHandler_SubCustomZones(Menu menu, MenuAction action, int client, int param2) { if (action == MenuAction_Select) { char info[16]; menu.GetItem(param2, info, sizeof(info)); char exploded[3][4]; ExplodeString(info, ";", exploded, 3, 4); int track = StringToInt(exploded[0]); int zoneType = StringToInt(exploded[1]); int option = StringToInt(exploded[2]); if (option == 0) // Display type { gI_ZoneDisplayType[client][zoneType][track]++; if (gI_ZoneDisplayType[client][zoneType][track] >= ZoneDisplay_Size) gI_ZoneDisplayType[client][zoneType][track] = ZoneDisplay_Default; HandleCustomZoneCookie(client); } else if (option == 1) // Color { gI_ZoneColor[client][zoneType][track]++; if (gI_ZoneColor[client][zoneType][track] >= ZoneColor_Size) gI_ZoneColor[client][zoneType][track] = ZoneColor_Default; HandleCustomZoneCookie(client); } else if (option == 2) // Width { gI_ZoneWidth[client][zoneType][track]++; if (gI_ZoneWidth[client][zoneType][track] >= ZoneWidth_Size) gI_ZoneWidth[client][zoneType][track] = ZoneWidth_Default; HandleCustomZoneCookie(client); } OpenSubCustomZoneMenu(client, track, zoneType); } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { OpenCustomZoneMenu(client, gI_LastMenuPos[client]); } else if (action == MenuAction_End) { delete menu; } return 0; } public Action Command_DeleteZone(int client, int args) { if (!gH_SQL) { Shavit_PrintToChat(client, "Database not loaded. Check your error logs."); return Plugin_Handled; } if(!IsValidClient(client)) { return Plugin_Handled; } #if 0 if (!gCV_SQLZones.BoolValue) { Shavit_PrintToChat(client, "%T", "ZonesNotSQL", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); return Plugin_Handled; } #endif return OpenDeleteMenu(client); } Action OpenDeleteMenu(int client, int pos = 0) { Menu menu = new Menu(MenuHandler_DeleteZone); menu.SetTitle("%T\n ", "ZoneMenuDeleteTitle", client); int newPageInterval = (gEV_Type == Engine_CSGO) ? 6 : 7; char sDisplay[128]; FormatEx(sDisplay, sizeof(sDisplay), "%T", "ZoneEditRefresh", client); menu.AddItem("-2", sDisplay); for(int i = 0; i < gI_MapZones; i++) { if (true)//(gA_ZoneCache[i].bInitialized) { if ((menu.ItemCount % newPageInterval) == 0) { FormatEx(sDisplay, sizeof(sDisplay), "%T", "ZoneEditRefresh", client); menu.AddItem("-2", sDisplay); } char sTarget[64]; switch (gA_ZoneCache[i].iForm) { case ZoneForm_func_button, ZoneForm_trigger_multiple, ZoneForm_trigger_teleport: { FormatEx(sTarget, sizeof(sTarget), " (%s)", gA_ZoneCache[i].sTarget); } } char sTrack[32]; GetTrackName(client, gA_ZoneCache[i].iTrack, sTrack, 32); char sZoneName[32]; GetZoneName(client, gA_ZoneCache[i].iType, sZoneName, sizeof(sZoneName)); if(gA_ZoneCache[i].iType == Zone_CustomSpeedLimit || gA_ZoneCache[i].iType == Zone_Stage || gA_ZoneCache[i].iType == Zone_Airaccelerate) { FormatEx(sDisplay, sizeof(sDisplay), "#%d - %s %d (%s)%s", (i + 1), sZoneName, gA_ZoneCache[i].iData, sTrack, sTarget); } else if (gA_ZoneCache[i].iType == Zone_Gravity || gA_ZoneCache[i].iType == Zone_Speedmod) { FormatEx(sDisplay, 64, "#%d - %s %.2f (%s)", (i + 1), sZoneName, gA_ZoneCache[i].iData, sTrack); } else { FormatEx(sDisplay, sizeof(sDisplay), "#%d - %s (%s)%s", (i + 1), sZoneName, sTrack, sTarget); } char sInfo[8]; IntToString(i, sInfo, 8); if(gB_InsideZoneID[client][i]) { Format(sDisplay, sizeof(sDisplay), "%s %T", sDisplay, "ZoneInside", client); } menu.AddItem(sInfo, sDisplay, ITEMDRAW_DEFAULT); } } menu.ExitBackButton = true; menu.DisplayAt(client, pos, MENU_TIME_FOREVER); return Plugin_Handled; } public int MenuHandler_DeleteZone(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char info[8]; menu.GetItem(param2, info, 8); int id = StringToInt(info); switch(id) { case -2: { OpenDeleteMenu(param1, GetMenuSelectionPosition()); } case -1: { Shavit_PrintToChat(param1, "%T", "ZonesMenuNoneFound", param1); } default: { char sZoneName[32]; GetZoneName(LANG_SERVER, gA_ZoneCache[id].iType, sZoneName, sizeof(sZoneName)); Shavit_LogMessage("%L - deleted %s (id %d) from map `%s`.", param1, sZoneName, gA_ZoneCache[id].iDatabaseID, gS_Map); char sQuery[256]; FormatEx(sQuery, 256, "DELETE FROM %smapzones WHERE %s = %d;", gS_MySQLPrefix, (gI_Driver != Driver_sqlite) ? "id":"rowid", gA_ZoneCache[id].iDatabaseID); DataPack hDatapack = new DataPack(); hDatapack.WriteCell(GetClientSerial(param1)); hDatapack.WriteCell(gA_ZoneCache[id].iType); QueryLog(gH_SQL, SQL_DeleteZone_Callback, sQuery, hDatapack); Shavit_RemoveZone(id); } } } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { gH_AdminMenu.DisplayCategory(gH_TimerCommands, param1); } else if(action == MenuAction_End) { delete menu; } return 0; } public void SQL_DeleteZone_Callback(Database db, DBResultSet results, const char[] error, DataPack data) { data.Reset(); int client = GetClientFromSerial(data.ReadCell()); int type = data.ReadCell(); delete data; if(results == null) { LogError("Timer (single zone delete) SQL query failed. Reason: %s", error); return; } if(client == 0) { return; } char sZoneName[32]; GetZoneName(client, type, sZoneName, sizeof(sZoneName)); Shavit_PrintToChat(client, "%T", "ZoneDeleteSuccessful", client, gS_ChatStrings.sVariable, sZoneName, gS_ChatStrings.sText); } public Action Command_DeleteAllZones(int client, int args) { if (!gH_SQL) { Shavit_PrintToChat(client, "Database not loaded. Check your error logs."); return Plugin_Handled; } if(!IsValidClient(client)) { return Plugin_Handled; } Menu menu = new Menu(MenuHandler_DeleteAllZones); menu.SetTitle("%T", "ZoneMenuDeleteALLTitle", client); char sMenuItem[64]; for(int i = 1; i <= GetRandomInt(1, 4); i++) { FormatEx(sMenuItem, 64, "%T", "ZoneMenuNo", client); menu.AddItem("-1", sMenuItem); } FormatEx(sMenuItem, 64, "%T", "ZoneMenuYes", client); menu.AddItem("yes", sMenuItem); for(int i = 1; i <= GetRandomInt(1, 3); i++) { FormatEx(sMenuItem, 64, "%T", "ZoneMenuNo", client); menu.AddItem("-1", sMenuItem); } menu.ExitBackButton = true; menu.Display(client, 300); return Plugin_Handled; } public int MenuHandler_DeleteAllZones(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char info[8]; menu.GetItem(param2, info, 8); int iInfo = StringToInt(info); if(iInfo == -1) { return 0; } Shavit_LogMessage("%L - deleted all zones from map `%s`.", param1, gS_Map); char sQuery[512]; FormatEx(sQuery, sizeof(sQuery), "DELETE FROM %smapzones WHERE map = '%s';", gS_MySQLPrefix, gS_Map); QueryLog(gH_SQL, SQL_DeleteAllZones_Callback, sQuery, GetClientSerial(param1)); } else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { gH_AdminMenu.DisplayCategory(gH_TimerCommands, param1); } else if(action == MenuAction_End) { delete menu; } return 0; } public void SQL_DeleteAllZones_Callback(Database db, DBResultSet results, const char[] error, any data) { if(results == null) { LogError("Timer (single zone delete) SQL query failed. Reason: %s", error); return; } UnloadZones(); int client = GetClientFromSerial(data); if(client == 0) { return; } Shavit_PrintToChat(client, "%T", "ZoneDeleteAllSuccessful", client); } public int MenuHandler_SelectZoneType(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char info[8]; menu.GetItem(param2, info, 8); gA_EditCache[param1].iType = StringToInt(info); if (gA_EditCache[param1].iType == Zone_Gravity || gA_EditCache[param1].iType == Zone_Speedmod) { gA_EditCache[param1].iData = view_as(1.0); } ShowPanel(param1, 1); } else if(action == MenuAction_End) { delete menu; } return 0; } void Reset(int client) { zone_cache_t cache; cache.iDatabaseID = -1; gA_EditCache[client] = cache; gI_MapStep[client] = 0; gI_HookListPos[client] = -1; delete gH_StupidTimer[client]; gB_WaitingForChatInput[client] = false; gI_ZoneID[client] = -1; gV_WallSnap[client] = ZERO_VECTOR; } void ShowPanel(int client, int step) { gI_MapStep[client] = step; if(step == 1) { CreateTimer(0.1, Timer_Draw, GetClientSerial(client), TIMER_REPEAT); } Panel pPanel = new Panel(); char sPanelText[128]; char sFirst[64]; char sSecond[64]; FormatEx(sFirst, 64, "%T", "ZoneFirst", client); FormatEx(sSecond, 64, "%T", "ZoneSecond", client); if(gEV_Type == Engine_TF2) { FormatEx(sPanelText, 128, "%T", "ZonePlaceTextTF2", client, (step == 1)? sFirst:sSecond); } else { FormatEx(sPanelText, 128, "%T", "ZonePlaceText", client, (step == 1)? sFirst:sSecond); } pPanel.DrawItem(sPanelText, ITEMDRAW_RAWLINE); char sPanelItem[64]; FormatEx(sPanelItem, 64, "%T", "AbortZoneCreation", client); pPanel.DrawItem(sPanelItem); char sDisplay[64]; FormatEx(sDisplay, 64, "%T", "GridSnapPlus", client, gI_GridSnap[client]); pPanel.DrawItem(sDisplay); FormatEx(sDisplay, 64, "%T", "GridSnapMinus", client); pPanel.DrawItem(sDisplay); FormatEx(sDisplay, 64, "%T", "WallSnap", client, (gB_SnapToWall[client])? "ZoneSetYes":"ZoneSetNo", client); pPanel.DrawItem(sDisplay); FormatEx(sDisplay, 64, "%T", "CursorZone", client, (gB_CursorTracing[client])? "ZoneSetYes":"ZoneSetNo", client); pPanel.DrawItem(sDisplay); pPanel.Send(client, ZoneCreation_Handler, 600); delete pPanel; } public int ZoneCreation_Handler(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { switch(param2) { case 1: { Reset(param1); return 0; } case 2: { gI_GridSnap[param1] *= 2; if(gI_GridSnap[param1] > 64) { gI_GridSnap[param1] = 1; } } case 3: { gI_GridSnap[param1] /= 2; if(gI_GridSnap[param1] < 1) { gI_GridSnap[param1] = 64; } } case 4: { gB_SnapToWall[param1] = !gB_SnapToWall[param1]; if(gB_SnapToWall[param1]) { gB_CursorTracing[param1] = false; if(gI_GridSnap[param1] < 32) { gI_GridSnap[param1] = 32; } } } case 5: { gB_CursorTracing[param1] = !gB_CursorTracing[param1]; if(gB_CursorTracing[param1]) { gB_SnapToWall[param1] = false; } } } ShowPanel(param1, gI_MapStep[param1]); } else if(action == MenuAction_End) { delete menu; } return 0; } float[] SnapToGrid(float pos[3], int grid, bool third) { float origin[3]; origin = pos; origin[0] = float(RoundToNearest(pos[0] / grid) * grid); origin[1] = float(RoundToNearest(pos[1] / grid) * grid); if(third) { origin[2] = float(RoundToNearest(pos[2] / grid) * grid); } return origin; } bool SnapToWall(float pos[3], int client, float final[3]) { bool hit = false; float end[3]; float temp[3]; float prefinal[3]; prefinal = pos; for(int i = 0; i < 4; i++) { end = pos; int axis = (i / 2); end[axis] += (((i % 2) == 1)? -gI_GridSnap[client]:gI_GridSnap[client]); TR_TraceRayFilter(pos, end, MASK_PLAYERSOLID, RayType_EndPoint, TraceFilter_NoClients, client); if(TR_DidHit()) { TR_GetEndPosition(temp); prefinal[axis] = temp[axis]; hit = true; } } if(hit && GetVectorDistance(prefinal, pos) <= gI_GridSnap[client]) { final = SnapToGrid(prefinal, gI_GridSnap[client], false); return true; } return false; } public bool TraceFilter_NoClients(int entity, int contentsMask, any data) { return (entity != data && !IsValidClient(data)); } float[] GetAimPosition(int client) { float pos[3]; GetClientEyePosition(client, pos); float angles[3]; GetClientEyeAngles(client, angles); TR_TraceRayFilter(pos, angles, MASK_PLAYERSOLID, RayType_Infinite, TraceFilter_NoClients, client); if(TR_DidHit()) { float end[3]; TR_GetEndPosition(end); return SnapToGrid(end, gI_GridSnap[client], true); } return pos; } public bool TraceFilter_World(int entity, int contentsMask) { return (entity == 0); } // Sometimes our points aren't mins/maxs... sometimes old DB points... which is not good... void BoxPointsToMinsMaxs(float point1[3], float point2[3], float boxmin[3], float boxmax[3]) { for (int i = 0; i < 3; i++) { float a = point1[i]; float b = point2[i]; if (a < b) { boxmin[i] = a; boxmax[i] = b; } else { boxmin[i] = b; boxmax[i] = a; } } } bool BoxesIntersect(float amin[3], float amax[3], float bmin[3], float bmax[3]) { return (amin[0] <= bmax[0] && amax[0] >= bmin[0]) && (amin[1] <= bmax[1] && amax[1] >= bmin[1]) && (amin[2] <= bmax[2] && amax[2] >= bmin[2]); } bool PointInBox(float point[3], float bmin[3], float bmax[3]) { return (bmin[0] <= point[0] <= bmax[0]) && (bmin[1] <= point[1] <= bmax[1]) && (bmin[2] <= point[2] <= bmax[2]); } bool InStartOrEndZone(float point1[3], float point2[3], int track, int type) { if (type != Zone_Start && type != Zone_End) { return false; } float amin[3], amax[3]; bool box = !IsNullVector(point2); if (box) { BoxPointsToMinsMaxs(point1, point2, amin, amax); } for (int i = 0; i < gI_MapZones; i++) { if ((gA_ZoneCache[i].iTrack == track && gA_ZoneCache[i].iType == type) || (gA_ZoneCache[i].iType != Zone_End && gA_ZoneCache[i].iType != Zone_Start)) { continue; } float bmin[3], bmax[3]; BoxPointsToMinsMaxs(gV_MapZones_Visual[i][0], gV_MapZones_Visual[i][7], bmin, bmax); if (box) { if (BoxesIntersect(amin, amax, bmin, bmax)) { return true; } } else { if (PointInBox(point1, bmin, bmax)) { return true; } } } return false; } public Action Shavit_OnUserCmdPre(int client, int &buttons, int &impulse, float vel[3], float angles[3], TimerStatus status, int track, int style) { if(gI_MapStep[client] > 0 && gI_MapStep[client] != 3) { int button = (gEV_Type == Engine_TF2)? IN_ATTACK2:IN_USE; if((buttons & button) > 0) { if(!gB_Button[client]) { float vPlayerOrigin[3]; GetClientAbsOrigin(client, vPlayerOrigin); float origin[3]; if(gB_CursorTracing[client]) { origin = GetAimPosition(client); } else if(!(gB_SnapToWall[client] && SnapToWall(vPlayerOrigin, client, origin))) { origin = SnapToGrid(vPlayerOrigin, gI_GridSnap[client], false); } else { gV_WallSnap[client] = origin; } origin[2] = vPlayerOrigin[2]; if(gI_MapStep[client] == 1) { origin[2] += 1.0; if (!InStartOrEndZone(origin, NULL_VECTOR, gA_EditCache[client].iTrack, gA_EditCache[client].iType)) { gA_EditCache[client].fCorner1 = origin; ShowPanel(client, 2); } } else if(gI_MapStep[client] == 2) { origin[2] += gCV_Height.FloatValue; if (origin[0] != gA_EditCache[client].fCorner1[0] && origin[1] != gA_EditCache[client].fCorner1[1] && !InStartOrEndZone(gA_EditCache[client].fCorner1, origin, gA_EditCache[client].iTrack, gA_EditCache[client].iType)) { gA_EditCache[client].fCorner2 = origin; gI_MapStep[client]++; CreateEditMenu(client, true); } } } } gB_Button[client] = (buttons & button) > 0; } if(InsideZone(client, Zone_Slide, (gCV_EnforceTracks.BoolValue)? track:-1) && GetEntPropEnt(client, Prop_Send, "m_hGroundEntity") == -1) { // trace down, see if there's 8 distance or less to ground float fPosition[3]; GetClientAbsOrigin(client, fPosition); TR_TraceRayFilter(fPosition, view_as({90.0, 0.0, 0.0}), MASK_PLAYERSOLID, RayType_Infinite, TRFilter_NoPlayers, client); float fGroundPosition[3]; if(TR_DidHit() && TR_GetEndPosition(fGroundPosition) && GetVectorDistance(fPosition, fGroundPosition) <= 8.0) { float fSpeed[3]; GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", fSpeed); fSpeed[2] = 8.0 * GetEntityGravity(client) * GetEntPropFloat(client, Prop_Data, "m_flLaggedMovementValue") * (sv_gravity.FloatValue / 800); TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, fSpeed); } } return Plugin_Continue; } public bool TRFilter_NoPlayers(int entity, int mask, any data) { //return (entity != view_as(data) || (entity < 1 || entity > MaxClients)); return !(1 <= entity <= MaxClients); } public int CreateZoneConfirm_Handler(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char sInfo[16]; menu.GetItem(param2, sInfo, 16); if(StrEqual(sInfo, "yes")) { if (gI_ZoneID[param1] != -1) { Shavit_RemoveZone(gI_ZoneID[param1]); // TODO: gI_ZoneID can be wiped mid menu or something... } InsertZone(param1); return 0; } else if(StrEqual(sInfo, "no")) { Reset(param1); return 0; } else if(StrEqual(sInfo, "adjust")) { CreateAdjustMenu(param1, 0); return 0; } else if(StrEqual(sInfo, "tpzone")) { UpdateTeleportZone(param1); } else if(StrEqual(sInfo, "datafromchat")) { gA_EditCache[param1].iData = 0; gB_WaitingForChatInput[param1] = true; Shavit_PrintToChat(param1, "%T", "ZoneEnterDataChat", param1); return 0; } else if(StrEqual(sInfo, "forcerender")) { gA_EditCache[param1].iFlags ^= ZF_ForceRender; } CreateEditMenu(param1); } else if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { if (gI_ZoneID[param1] == -1) { OpenHookMenu_Editor(param1); } else { OpenEditMenu(param1, gI_LastMenuPos[param1]); } } else { Reset(param1); } } else if(action == MenuAction_End) { delete menu; } return 0; } public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { if(gB_WaitingForChatInput[client] && gI_MapStep[client] == 3) { if (gA_EditCache[client].iType == Zone_Gravity || gA_EditCache[client].iType == Zone_Speedmod) { gA_EditCache[client].iData = view_as(StringToFloat(sArgs)); } else { gA_EditCache[client].iData = StringToInt(sArgs); } CreateEditMenu(client); return Plugin_Handled; } return Plugin_Continue; } void UpdateTeleportZone(int client) { float vTeleport[3]; GetClientAbsOrigin(client, vTeleport); vTeleport[2] += 2.0; if (gA_EditCache[client].iType == Zone_Stage) { gA_EditCache[client].fDestination = vTeleport; Shavit_PrintToChat(client, "%T", "ZoneTeleportUpdated", client); } else { bool bInside = true; for(int i = 0; i < 3; i++) { if (gA_EditCache[client].fCorner1[i] >= vTeleport[i] == gA_EditCache[client].fCorner2[i] >= vTeleport[i]) { bInside = false; } } if(bInside) { Shavit_PrintToChat(client, "%T", "ZoneTeleportInsideZone", client); } else { gA_EditCache[client].fDestination = vTeleport; Shavit_PrintToChat(client, "%T", "ZoneTeleportUpdated", client); } } } void CreateEditMenu(int client, bool autostage=false) { bool hookmenu = gI_HookListPos[client] != -1; char sTrack[32], sType[32]; GetTrackName(client, gA_EditCache[client].iTrack, sTrack, 32); GetZoneName(client, gA_EditCache[client].iType, sType, sizeof(sType)); Menu menu = new Menu(CreateZoneConfirm_Handler); if (hookmenu) { menu.SetTitle("%T\n%T\n%T\n%T\n ", "ZoneEditConfirm", client, "ZoneEditTrack", client, sTrack, "ZoneHook_Zonetype", client, sType, "ZoneHook_Hooktype", client, (gA_EditCache[client].iFlags & ZF_Hammerid) ? "hammerid" : ((gA_EditCache[client].iFlags & ZF_Origin) ? "origin" : (gA_EditCache[client].iForm == ZoneForm_trigger_teleport ? "target" : "targetname")), gA_EditCache[client].sTarget); } else { menu.SetTitle("%T\n%T\n%T\n ", "ZoneEditConfirm", client, "ZoneEditTrack", client, sTrack, "ZoneHook_Zonetype", client, sType); } char sMenuItem[64]; if (gA_EditCache[client].iType == Zone_Teleport) { if (EmptyVector(gA_EditCache[client].fDestination)) { FormatEx(sMenuItem, 64, "%T", "ZoneSetTP", client); menu.AddItem("-1", sMenuItem, ITEMDRAW_DISABLED); } else { FormatEx(sMenuItem, 64, "%T", "ZoneSetYes", client); menu.AddItem("yes", sMenuItem); } FormatEx(sMenuItem, 64, "%T", "ZoneSetTPZone", client); menu.AddItem("tpzone", sMenuItem); } else if (gA_EditCache[client].iType == Zone_Stage) { FormatEx(sMenuItem, 64, "%T", "ZoneSetYes", client); menu.AddItem("yes", sMenuItem); FormatEx(sMenuItem, 64, "%T", "ZoneSetTPZone", client); menu.AddItem("tpzone", sMenuItem); } else { FormatEx(sMenuItem, 64, "%T", "ZoneSetYes", client); menu.AddItem("yes", sMenuItem); } FormatEx(sMenuItem, 64, "%T", "ZoneSetNo", client); menu.AddItem("no", sMenuItem); FormatEx(sMenuItem, 64, "%T", "ZoneSetAdjust", client); menu.AddItem("adjust", sMenuItem, (hookmenu || gA_EditCache[client].iForm != ZoneForm_Box) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); FormatEx(sMenuItem, 64, "%T", "ZoneForceRender", client, ((gA_EditCache[client].iFlags & ZF_ForceRender) > 0)? "+":"-"); menu.AddItem("forcerender", sMenuItem); if (gA_EditCache[client].iType == Zone_Stage) { if (autostage) { gA_EditCache[client].iData = gI_HighestStage[gA_EditCache[client].iTrack] + 1; } FormatEx(sMenuItem, 64, "%T", "ZoneSetStage", client, gA_EditCache[client].iData); menu.AddItem("datafromchat", sMenuItem); } else if (gA_EditCache[client].iType == Zone_Airaccelerate) { FormatEx(sMenuItem, 64, "%T", "ZoneSetAiraccelerate", client, gA_EditCache[client].iData); menu.AddItem("datafromchat", sMenuItem); } else if (gA_EditCache[client].iType == Zone_CustomSpeedLimit) { if (gA_EditCache[client].iData == 0) { FormatEx(sMenuItem, 64, "%T", "ZoneSetSpeedLimitUnlimited", client, gA_EditCache[client].iData); } else { FormatEx(sMenuItem, 64, "%T", "ZoneSetSpeedLimit", client, gA_EditCache[client].iData); } menu.AddItem("datafromchat", sMenuItem); } else if (gA_EditCache[client].iType == Zone_Gravity) { float g = view_as(gA_EditCache[client].iData); FormatEx(sMenuItem, sizeof(sMenuItem), "%T", "ZoneSetGravity", client, g); menu.AddItem("datafromchat", sMenuItem); } else if (gA_EditCache[client].iType == Zone_Speedmod) { float speed = view_as(gA_EditCache[client].iData); FormatEx(sMenuItem, sizeof(sMenuItem), "%T", "ZoneSetSpeedmod", client, speed); menu.AddItem("datafromchat", sMenuItem); } if (hookmenu || gI_ZoneID[client] != -1) menu.ExitBackButton = true; else menu.ExitButton = true; menu.Display(client, MENU_TIME_FOREVER); } void CreateAdjustMenu(int client, int page) { Menu hMenu = new Menu(ZoneAdjuster_Handler); char sMenuItem[64]; hMenu.SetTitle("%T\n ", "ZoneAdjustPosition", client); char sAxis[4]; strcopy(sAxis, 4, "XYZ"); char sDisplay[32]; char sInfo[16]; for(int iPoint = 1; iPoint <= 2; iPoint++) { for (int iState = 1; iState <= 2; iState++) { FormatEx(sDisplay, 32, "%T %c%.01f%s", "ZonePoint", client, iPoint, sAxis[gI_AdjustAxis[client]], (iState == 1)? '+':'-', gF_Modifier[client], (iState==2)?"\n ":""); FormatEx(sInfo, 16, "%d;%d;%d", iPoint, gI_AdjustAxis[client], iState); hMenu.AddItem(sInfo, sDisplay); } } FormatEx(sMenuItem, 64, "%T\n ", "ZoneAxis", client); hMenu.AddItem("axis", sMenuItem); FormatEx(sMenuItem, 64, "%T", "ZoneAdjustDone", client); hMenu.AddItem("done", sMenuItem); hMenu.ExitButton = true; hMenu.DisplayAt(client, page, MENU_TIME_FOREVER); } public int ZoneAdjuster_Handler(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) { char sInfo[16]; menu.GetItem(param2, sInfo, 16); if(StrEqual(sInfo, "done")) { CreateEditMenu(param1); } else if (StrEqual(sInfo, "axis")) { gI_AdjustAxis[param1] = (gI_AdjustAxis[param1] + 1) % 3; CreateAdjustMenu(param1, GetMenuSelectionPosition()); } else { char sAxis[4]; strcopy(sAxis, 4, "XYZ"); char sExploded[3][8]; ExplodeString(sInfo, ";", sExploded, 3, 8); int iPoint = StringToInt(sExploded[0]); int iAxis = StringToInt(sExploded[1]); bool bIncrease = view_as(StringToInt(sExploded[2]) == 1); float mod = ((bIncrease)? gF_Modifier[param1]:-gF_Modifier[param1]); if (iPoint == 1) gA_EditCache[param1].fCorner1[iAxis] += mod; else gA_EditCache[param1].fCorner2[iAxis] += mod; Shavit_StopChatSound(); Shavit_PrintToChat(param1, "%T", (bIncrease)? "ZoneSizeIncrease":"ZoneSizeDecrease", param1, gS_ChatStrings.sVariable2, sAxis[iAxis], gS_ChatStrings.sText, iPoint, gS_ChatStrings.sVariable, gF_Modifier[param1], gS_ChatStrings.sText); CreateAdjustMenu(param1, GetMenuSelectionPosition()); } } else if (action == MenuAction_Cancel) { if (gI_ZoneID[param1] != -1) { // reenable original zone //gA_ZoneCache[gI_ZoneID[param1]].bInitialized = true; } Reset(param1); } else if(action == MenuAction_End) { delete menu; } return 0; } void InsertZone(int client) { zone_cache_t c; c = gA_EditCache[client]; c.iEntity = -1; c.sSource = "sql"; char sQuery[1024]; char sTrack[64], sZoneName[32]; GetTrackName(LANG_SERVER, c.iTrack, sTrack, sizeof(sTrack)); GetZoneName(LANG_SERVER, c.iType, sZoneName, sizeof(sZoneName)); BoxPointsToMinsMaxs(c.fCorner1, c.fCorner2, c.fCorner1, c.fCorner2); Reset(client); if (!gCV_SQLZones.BoolValue) { c.sSource = "folder?"; c.iDatabaseID = GetTime(); } Shavit_AddZone(c); if (!gCV_SQLZones.BoolValue) return; if (c.iDatabaseID == -1) // insert { Shavit_LogMessage( "%L - added %s %s to map `%s`. \ p1(%f, %f, %f), p2(%f, %f, %f), dest(%f, %f, %f), \ flags=%d, data=%d, form=%d, target='%s'", client, sTrack, sZoneName, gS_Map, EXPAND_VECTOR(c.fCorner1), EXPAND_VECTOR(c.fCorner2), EXPAND_VECTOR(c.fDestination), c.iFlags, c.iData, c.iForm, c.sTarget ); FormatEx(sQuery, sizeof(sQuery), "INSERT INTO %smapzones (map, type, \ corner1_x, corner1_y, corner1_z, \ corner2_x, corner2_y, corner2_z, \ destination_x, destination_y, destination_z, \ track, flags, data, form, target) VALUES \ ('%s', %d, \ %f, %f, %f, \ %f, %f, %f, \ %f, %f, %f, \ %d, %d, %d, \ %d, '%s');", gS_MySQLPrefix, gS_Map, c.iType, EXPAND_VECTOR(c.fCorner1), EXPAND_VECTOR(c.fCorner2), EXPAND_VECTOR(c.fDestination), c.iTrack, c.iFlags, c.iData, c.iForm, c.sTarget ); } else // update { Shavit_LogMessage( "%L - updated %s %s (%d) in map `%s`. \ p1(%f, %f, %f), p2(%f, %f, %f), dest(%f, %f, %f), \ flags=%d, data=%d, form=%d, target='%s'", client, sTrack, sZoneName, c.iDatabaseID, gS_Map, EXPAND_VECTOR(c.fCorner1), EXPAND_VECTOR(c.fCorner2), EXPAND_VECTOR(c.fDestination), c.iFlags, c.iData, c.iForm, c.sTarget ); FormatEx(sQuery, sizeof(sQuery), "UPDATE %smapzones SET \ corner1_x = '%f', corner1_y = '%f', corner1_z = '%f' \ , corner2_x = '%f', corner2_y = '%f', corner2_z = '%f' \ , destination_x = '%f', destination_y = '%f', destination_z = '%f' \ , flags = %d, data = %d \ , form = %d, target = '%s' \ WHERE %s = %d;", gS_MySQLPrefix, EXPAND_VECTOR(c.fCorner1), EXPAND_VECTOR(c.fCorner2), EXPAND_VECTOR(c.fDestination), c.iFlags, c.iData, c.iForm, c.sTarget, (gI_Driver != Driver_sqlite) ? "id" : "rowid", c.iDatabaseID ); } DataPack pack = new DataPack(); pack.WriteCellArray(c, sizeof(c)); QueryLog(gH_SQL, SQL_InsertZone_Callback, sQuery, pack); } bool MyArrayEquals(any[] a, any[] b, int size) { for (int i = 0; i < size; i++) if (a[i] != b[i]) return false; return true; } public void SQL_InsertZone_Callback(Database db, DBResultSet results, const char[] error, DataPack pack) { zone_cache_t cache; pack.Reset(); pack.ReadCellArray(cache, sizeof(cache)); delete pack; if (results == null) { LogError("Timer (zone insert) SQL query failed. Reason: %s", error); return; } for (int i = 0; i < gI_MapZones; i++) { cache.iEntity = gA_ZoneCache[i].iEntity; if (MyArrayEquals(gA_ZoneCache[i], cache, sizeof(zone_cache_t))) { gA_ZoneCache[i].iDatabaseID = results.InsertId; break; } } } public Action Timer_DrawZones(Handle Timer, any drawAll) { if (gI_MapZones == 0) { return Plugin_Continue; } if (drawAll && !gCV_AllowDrawAllZones.BoolValue) { return Plugin_Continue; } static int iCycle[2]; static int iMaxZonesPerFrame = 5; if (iCycle[drawAll] >= gI_MapZones) { iCycle[drawAll] = 0; } int iDrawn = 0; for (int i = iCycle[drawAll]; i < gI_MapZones; i++, iCycle[drawAll]++) { int form = gA_ZoneCache[i].iForm; int type = gA_ZoneCache[i].iType; int track = gA_ZoneCache[i].iTrack; if (drawAll || gA_ZoneSettings[type][track].bVisible || (gA_ZoneCache[i].iFlags & ZF_ForceRender) > 0) { if ((form == ZoneForm_trigger_teleport || form == ZoneForm_func_button) && !(drawAll || (gA_ZoneCache[i].iFlags & ZF_ForceRender) > 0)) { continue; } int colors[4]; GetZoneColors(colors, type, track); DrawZone( gV_MapZones_Visual[i], colors, RoundToCeil(float(gI_MapZones) / iMaxZonesPerFrame + 2.0) * gCV_Interval.FloatValue, gA_ZoneSettings[type][track].fWidth, gA_ZoneSettings[type][track].bFlatZone, gV_ZoneCenter[i], gA_ZoneSettings[type][track].iBeam, gA_ZoneSettings[type][track].iHalo, track, type, gA_ZoneSettings[type][track].iSpeed, !!drawAll, 0 ); if (++iDrawn % iMaxZonesPerFrame == 0) { return Plugin_Continue; } } } iCycle[drawAll] = 0; return Plugin_Continue; } void GetZoneColors(int colors[4], int type, int track, int customalpha = 0) { colors[0] = gA_ZoneSettings[type][track].iRed; colors[1] = gA_ZoneSettings[type][track].iGreen; colors[2] = gA_ZoneSettings[type][track].iBlue; colors[3] = (customalpha > 0)? customalpha:gA_ZoneSettings[type][track].iAlpha; } public Action Timer_Draw(Handle Timer, any data) { int client = GetClientFromSerial(data); if(client == 0 || gI_MapStep[client] == 0) { Reset(client); for (int i = 1; i < MAXPLAYERS+1; i++) { if (gH_StupidTimer[i] == Timer) { gH_StupidTimer[i] = null; break; } } return Plugin_Stop; } float vPlayerOrigin[3]; GetClientAbsOrigin(client, vPlayerOrigin); float origin[3]; if(gB_CursorTracing[client]) { origin = GetAimPosition(client); } else if(!(gB_SnapToWall[client] && SnapToWall(vPlayerOrigin, client, origin))) { origin = SnapToGrid(vPlayerOrigin, gI_GridSnap[client], false); } else { gV_WallSnap[client] = origin; } if (gI_MapStep[client] == 1 || gA_EditCache[client].fCorner2[0] == 0.0) { origin[2] = (vPlayerOrigin[2] + gCV_Height.FloatValue); } else { origin = gA_EditCache[client].fCorner2; } int type = gA_EditCache[client].iType; type = type < 0 ? 0 : type; int track = gA_EditCache[client].iTrack; track = track < 0 ? 0 : track; if (!EmptyVector(gA_EditCache[client].fCorner1) || !EmptyVector(gA_EditCache[client].fCorner2)) { float points[8][3]; points[0] = gA_EditCache[client].fCorner1; points[7] = origin; CreateZonePoints(points, false); // This is here to make the zone setup grid snapping be 1:1 to how it looks when done with the setup. origin = points[7]; int colors[4]; GetZoneColors(colors, type, track, 125); DrawZone(points, colors, 0.1, gA_ZoneSettings[type][track].fWidth, false, origin, gI_BeamSpriteIgnoreZ, gA_ZoneSettings[type][track].iHalo, track, type, gA_ZoneSettings[type][track].iSpeed, false, 0, gI_AdjustAxis[client]); if (gA_EditCache[client].iType == Zone_Teleport && !EmptyVector(gA_EditCache[client].fDestination)) { TE_SetupEnergySplash(gA_EditCache[client].fDestination, ZERO_VECTOR, false); TE_SendToAll(0.0); } } if(gI_MapStep[client] != 3 && !EmptyVector(origin)) { origin[2] -= gCV_Height.FloatValue; TE_SetupBeamPoints(vPlayerOrigin, origin, gI_BeamSpriteIgnoreZ, gA_ZoneSettings[type][track].iHalo, 0, 0, 0.1, 1.0, 1.0, 0, 0.0, {255, 255, 255, 75}, 0); TE_SendToAll(0.0); // visualize grid snap float snap1[3]; float snap2[3]; for(int i = 0; i < 3; i++) { snap1 = origin; snap1[i] -= gI_GridSnap[client]; snap2 = origin; snap2[i] += gI_GridSnap[client]; TE_SetupBeamPoints(snap1, snap2, gI_BeamSpriteIgnoreZ, gA_ZoneSettings[type][track].iHalo, 0, 0, 0.1, 1.0, 1.0, 0, 0.0, {255, 255, 255, 75}, 0); TE_SendToAll(0.0); } } return Plugin_Continue; } void DrawZone(float points[8][3], int color[4], float life, float width, bool flat, float center[3], int beam, int halo, int track, int type, int speed, bool drawallzones, int single_client, int editaxis=-1) { static int pairs[][] = { { 0, 2 }, { 2, 6 }, { 6, 4 }, { 4, 0 }, { 0, 1 }, { 3, 1 }, { 3, 2 }, { 3, 7 }, { 5, 1 }, { 5, 4 }, { 6, 7 }, { 7, 5 } }; static int clrs[][4] = { { 255, 255, 255, 255 }, // White { 255, 0, 0, 255 }, // Red { 255, 128, 0, 255 }, // Orange { 255, 255 ,0, 255 }, // Yellow { 0, 255, 0, 255}, // Green { 0, 255, 255, 255 }, // Cyan { 0, 0, 255, 255 }, // Blue { 128, 0, 128, 255 }, // Purple { 255, 192, 203, 255 }, // Pink }; static float some_width[4] = { 0.1, 0.5, 2.0, 8.0 }; #if CZONE_VER == 'b' track = (track >= Track_Bonus) ? Track_Bonus : Track_Main; #endif int clients[MAXPLAYERS+1]; int count = 0; if (single_client) { clients[count++] = single_client; } else { for(int i = 1; i <= MaxClients; i++) { if(IsClientInGame(i) && !IsFakeClient(i) && (!drawallzones || (gB_DrawAllZones[i] && CanDrawAllZones(i)))) { float eyes[3]; GetClientEyePosition(i, eyes); if(gI_ZoneDisplayType[i][type][track] != ZoneDisplay_None && (GetVectorDistance(eyes, center) <= 2048.0 || (TR_TraceRayFilter(eyes, center, MASK_PLAYERSOLID, RayType_EndPoint, TraceFilter_World) && !TR_DidHit()))) { clients[count++] = i; } } } } if (editaxis != -1) { // The is generated with https://gist.github.com/rtldg/94fa32b7abb064e0e99dfbf0c73c1cda // The beam pairs array at the top of this function isn't useful for the order we want // for drawing the editaxis beams so that gist was used to help figure out which // beam indices go where, and then to make a fun little magic string out of it... char magic[] = "\x01\x132\x02EWvF\x04\x15&77&2v\x15\x04\x10T\x13W\x02F7\x151u&\x04 d#g\x01E"; for (int j = 0; j < 12; j++) { float actual_width = (j >= 8) ? 0.5 : 1.0; char x = magic[editaxis*12+j]; TE_SetupBeamPoints(points[x >> 4], points[x & 7], beam, halo, 0, 0, life, actual_width, actual_width, 0, 0.0, clrs[((j >= 8) ? ZoneColor_White : ZoneColor_Green) - 1], speed); TE_Send(clients, count, 0.0); } return; } for (int i = 0; i < count; i++) { int point_size = (gI_ZoneDisplayType[clients[i]][type][track] == ZoneDisplay_Flat || gI_ZoneDisplayType[clients[i]][type][track] == ZoneDisplay_Default && flat) ? 4 : 12; int actual_color[4]; actual_color = (gI_ZoneColor[clients[i]][type][track] == ZoneColor_Default) ? color : clrs[gI_ZoneColor[clients[i]][type][track] - 1]; float actual_width = (gI_ZoneWidth[clients[i]][type][track] == ZoneWidth_Default) ? width : some_width[gI_ZoneWidth[clients[i]][type][track] - 1]; for(int j = 0; j < point_size; j++) { TE_SetupBeamPoints(points[pairs[j][0]], points[pairs[j][1]], beam, halo, 0, 0, life, actual_width, actual_width, 0, 0.0, actual_color, speed); TE_SendToClient(clients[i], 0.0); } } } // original by blacky // creates 3d box from 2 points void CreateZonePoints(float point[8][3], bool prebuilt) { float offset = -(prebuilt ? gCV_PrebuiltVisualOffset.FloatValue : 0.0) + gCV_Offset.FloatValue; // calculate all zone edges for(int i = 1; i < 7; i++) { for(int j = 0; j < 3; j++) { point[i][j] = point[((i >> (2 - j)) & 1) * 7][j]; } } // apply beam offset if(offset != 0.0) { float center[2]; center[0] = ((point[0][0] + point[7][0]) / 2); center[1] = ((point[0][1] + point[7][1]) / 2); for(int i = 0; i < 8; i++) { for(int j = 0; j < 2; j++) { if(point[i][j] < center[j]) { point[i][j] += offset; } else if(point[i][j] > center[j]) { point[i][j] -= offset; } } } } } public void Shavit_OnDatabaseLoaded() { GetTimerSQLPrefix(gS_MySQLPrefix, 32); gH_SQL = Shavit_GetDatabase(gI_Driver); if (gB_YouCanLoadZonesNow && gCV_SQLZones.BoolValue) { RefreshZones(); } for(int i = 1; i <= MaxClients; i++) { if (IsValidClient(i) && !IsFakeClient(i)) { GetStartPosition(i); } } } void ResetClientTargetNameAndClassName(int client, int track) { char targetname[64]; char classname[64]; if (track == Track_Main) { gCV_ResetTargetnameMain.GetString(targetname, sizeof(targetname)); gCV_ResetClassnameMain.GetString(classname, sizeof(classname)); } else { gCV_ResetTargetnameBonus.GetString(targetname, sizeof(targetname)); gCV_ResetClassnameBonus.GetString(classname, sizeof(classname)); } DispatchKeyValue(client, "targetname", targetname); if (!classname[0]) { classname = "player"; } SetEntPropString(client, Prop_Data, "m_iClassname", classname); } public Action Shavit_OnStart(int client, int track) { if(gCV_ForceTargetnameReset.BoolValue) { ResetClientTargetNameAndClassName(client, track); } return Plugin_Continue; } public void Shavit_OnRestart(int client, int track) { gI_LastStage[client] = 0; if (!IsPlayerAlive(client)) { return; } int iIndex = GetZoneIndex(Zone_Start, track); if(gCV_TeleportToStart.BoolValue) { bool bCustomStart = gB_HasSetStart[client][track] && !gB_StartAnglesOnly[client][track]; bool use_CustomStart_over_CustomSpawn = (iIndex != -1) && bCustomStart; // custom spawns if (!use_CustomStart_over_CustomSpawn && !EmptyVector(gF_CustomSpawn[track])) { float pos[3]; pos = gF_CustomSpawn[track]; pos[2] += 1.0; TeleportEntity(client, pos, NULL_VECTOR, ZERO_VECTOR); } // standard zoning else if (bCustomStart || iIndex != -1) { float fCenter[3]; if (bCustomStart) { fCenter = gF_StartPos[client][track]; } else { fCenter[0] = gV_ZoneCenter[iIndex][0]; fCenter[1] = gV_ZoneCenter[iIndex][1]; fCenter[2] = gA_ZoneCache[iIndex].fCorner1[2] + gCV_ExtraSpawnHeight.FloatValue; } fCenter[2] += 1.0; TeleportEntity(client, fCenter, gB_HasSetStart[client][track] ? gF_StartAng[client][track] : NULL_VECTOR, view_as({0.0, 0.0, 0.0})); if (gB_ReplayRecorder && gB_HasSetStart[client][track]) { Shavit_HijackAngles(client, gF_StartAng[client][track][0], gF_StartAng[client][track][1], -1, true); } if (!gB_HasSetStart[client][track] || gB_StartAnglesOnly[client][track]) { ResetClientTargetNameAndClassName(client, track); // normally StartTimer will happen on zone-touch BUT we have this here for zones that are in the air bool skipGroundCheck = true; Shavit_StartTimer(client, track, skipGroundCheck); } } // kz buttons else if (Shavit_IsKZMap(track)) { if (EmptyVector(gF_ClimbButtonCache[client][track][0]) || EmptyVector(gF_ClimbButtonCache[client][track][1])) { return; } TeleportEntity(client, gF_ClimbButtonCache[client][track][0], gF_ClimbButtonCache[client][track][1], view_as({0.0, 0.0, 0.0})); return; } } if (iIndex != -1) { int colors[4]; GetZoneColors(colors, Zone_Start, track); DrawZone( gV_MapZones_Visual[iIndex], colors, gCV_Interval.FloatValue, gA_ZoneSettings[Zone_Start][track].fWidth, gA_ZoneSettings[Zone_Start][track].bFlatZone, gV_ZoneCenter[iIndex], gA_ZoneSettings[Zone_Start][track].iBeam, gA_ZoneSettings[Zone_Start][track].iHalo, track, Zone_Start, gA_ZoneSettings[Zone_Start][track].iSpeed, false, client ); } } public void Shavit_OnEnd(int client, int track) { int iIndex = GetZoneIndex(Zone_End, track); if(gCV_TeleportToEnd.BoolValue) { if(iIndex != -1) { float fCenter[3]; fCenter[0] = gV_ZoneCenter[iIndex][0]; fCenter[1] = gV_ZoneCenter[iIndex][1]; fCenter[2] = gA_ZoneCache[iIndex].fCorner1[2] + 1.0; // no stuck in floor please TeleportEntity(client, fCenter, NULL_VECTOR, view_as({0.0, 0.0, 0.0})); } } if (iIndex != -1) { int colors[4]; GetZoneColors(colors, Zone_End, track); DrawZone( gV_MapZones_Visual[iIndex], colors, gCV_Interval.FloatValue, gA_ZoneSettings[Zone_End][track].fWidth, gA_ZoneSettings[Zone_End][track].bFlatZone, gV_ZoneCenter[iIndex], gA_ZoneSettings[Zone_End][track].iBeam, gA_ZoneSettings[Zone_End][track].iHalo, track, Zone_End, gA_ZoneSettings[Zone_End][track].iSpeed, false, client ); } } bool EmptyVector(float vec[3]) { return (IsNullVector(vec) || (vec[0] == 0.0 && vec[1] == 0.0 && vec[2] == 0.0)); } // returns -1 if there's no zone int GetZoneIndex(int type, int track, int start = 0) { for(int i = start; i < gI_MapZones; i++) { if (gA_ZoneCache[i].iForm == ZoneForm_func_button) continue; if (gA_ZoneCache[i].iType == type && (gA_ZoneCache[i].iTrack == track || track == -1)) { return i; } } return -1; } public void Player_Spawn(Event event, const char[] name, bool dontBroadcast) { Reset(GetClientOfUserId(event.GetInt("userid"))); } public void Round_Start(Event event, const char[] name, bool dontBroadcast) { int empty_InsideZone[TRACKS_SIZE]; bool empty_InsideZoneID[MAX_ZONES]; for (int i = 1; i <= MaxClients; i++) { gI_InsideZone[i] = empty_InsideZone; gB_InsideZoneID[i] = empty_InsideZoneID; } } float Abs(float input) { if(input < 0.0) { return -input; } return input; } void SetZoneMinsMaxs(int zone) { float offsets[2]; // 0 = x/y width. 1 = z height. if (gCV_BoxOffset.FloatValue != 0.0) { if (gEV_Type == Engine_CSS) { offsets[0] = 32.0 / 2.0; offsets[1] = 62.0 / 2.0; } else if (gEV_Type == Engine_CSGO) { offsets[0] = 32.0 / 2.0; offsets[1] = 72.0 / 2.0; } else if (gEV_Type == Engine_TF2) { offsets[0] = 48.0 / 2.0; offsets[1] = 82.0 / 2.0; } } float mins[3], maxs[3]; for (int i = 0; i < 3; i++) { float offset = offsets[i/2]; #if 1 maxs[i] = Abs(gA_ZoneCache[zone].fCorner1[i] - gA_ZoneCache[zone].fCorner2[i]) / 2.0; if (maxs[i] > offset) maxs[i] -= offset; #else // maybe this would be good? maxs[i] = Abs(gA_ZoneCache[zone].fCorner1[i] - gA_ZoneCache[zone].fCorner2[i]) / 2.0 - offset; if (maxs[i] < 1.0) maxs[i] = 1.0; #endif mins[i] = -maxs[i]; } SetEntPropVector(gA_ZoneCache[zone].iEntity, Prop_Send, "m_vecMins", mins); SetEntPropVector(gA_ZoneCache[zone].iEntity, Prop_Send, "m_vecMaxs", maxs); } public MRESReturn DHooks_OnTeleport(int pThis, DHookParam hParams) { if (!IsValidEntity(pThis) || !IsClientInGame(pThis)) { return MRES_Ignored; } if (!hParams.IsNull(1)) { gI_LatestTeleportTick[pThis] = GetGameTickCount(); } return MRES_Ignored; } void PhysicsRemoveTouchedList(int client) { SDKCall(gH_PhysicsRemoveTouchedList, client); } void ACTUALLY_ForcePlayerSuicide(int client) { SDKCall(gH_CommitSuicide, client, false, true); } public void StartTouchPost(int entity, int other) { if (other < 1 || other > MaxClients || IsFakeClient(other)) return; int zone = gI_EntityZone[entity]; if (zone == -1) return; int type = gA_ZoneCache[zone].iType; int track = gA_ZoneCache[zone].iTrack; // todo: do this after the inside-zone is set? if (gCV_EnforceTracks.BoolValue && type > Zone_End && track != Shavit_GetClientTrack(other)) return; if (gA_ZoneCache[zone].iForm == ZoneForm_trigger_multiple || gA_ZoneCache[zone].iForm == ZoneForm_trigger_teleport) { if (!SDKCall(gH_PassesTriggerFilters, entity, other)) { return; } } TimerStatus status = Shavit_GetTimerStatus(other); switch (type) { case Zone_Respawn: { CS_RespawnPlayer(other); } case Zone_Teleport: { TeleportEntity(other, gA_ZoneCache[zone].fDestination, NULL_VECTOR, NULL_VECTOR); } case Zone_Slay: { if (status != Timer_Stopped) { Shavit_StopTimer(other); ACTUALLY_ForcePlayerSuicide(other); Shavit_PrintToChat(other, "%T", "ZoneSlayEnter", other, gS_ChatStrings.sWarning, gS_ChatStrings.sVariable2, gS_ChatStrings.sWarning); } } case Zone_Stop: { if(status != Timer_Stopped) { Shavit_StopTimer(other); Shavit_PrintToChat(other, "%T", "ZoneStopEnter", other, gS_ChatStrings.sWarning, gS_ChatStrings.sVariable2, gS_ChatStrings.sWarning); } } case Zone_End: { if (status == Timer_Running && Shavit_GetClientTrack(other) == track) { Shavit_FinishMap(other, track); } } case Zone_Stage: { int num = gA_ZoneCache[zone].iData; int iStyle = Shavit_GetBhopStyle(other); bool bTASSegments = Shavit_GetStyleSettingBool(iStyle, "tas") || Shavit_GetStyleSettingBool(iStyle, "segments"); if (status == Timer_Running && Shavit_GetClientTrack(other) == track && (num > gI_LastStage[other] || bTASSegments || Shavit_IsPracticeMode(other))) { gI_LastStage[other] = num; char sTime[32]; FormatSeconds(Shavit_GetClientTime(other), sTime, 32, true); char sMessage[255]; FormatEx(sMessage, 255, "%T", "ZoneStageEnter", other, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, num, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTime, gS_ChatStrings.sText); Action aResult = Plugin_Continue; Call_StartForward(gH_Forwards_StageMessage); Call_PushCell(other); Call_PushCell(num); Call_PushStringEx(sMessage, 255, SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); Call_PushCell(255); Call_Finish(aResult); if(aResult < Plugin_Handled) { Shavit_PrintToChat(other, "%s", sMessage); } } } case Zone_Speedmod: { char s[16]; FloatToString(view_as(gA_ZoneCache[zone].iData), s, sizeof(s)); SetVariantString(s); AcceptEntityInput(entity, "ModifySpeed", other, entity, 0); } } gI_InsideZone[other][track] |= (1 << type); gB_InsideZoneID[other][zone] = true; Call_StartForward(gH_Forwards_EnterZone); Call_PushCell(other); Call_PushCell(type); Call_PushCell(track); Call_PushCell(zone); Call_PushCell(entity); Call_PushCell(gA_ZoneCache[zone].iData); Call_Finish(); } public Action SameTrack_StartTouch_er(int entity, int other) { if (other < 1 || other > MaxClients || IsFakeClient(other)) return Plugin_Stop; int zone = gI_EntityZone[entity]; if (zone == -1 || gA_ZoneCache[zone].iTrack != Shavit_GetClientTrack(other)) return Plugin_Stop; return Plugin_Continue; } public void EndTouchPost(int entity, int other) { if (other < 1 || other > MaxClients || IsFakeClient(other)) return; int entityzone = gI_EntityZone[entity]; if (entityzone == -1) return; int type = gA_ZoneCache[entityzone].iType; int track = gA_ZoneCache[entityzone].iTrack; if (type < 0 || track < 0) // odd { return; } gB_InsideZoneID[other][entityzone] = false; RecalcInsideZone(other); if (type == Zone_Speedmod) { SetVariantString("1.0"); AcceptEntityInput(entity, "ModifySpeed", other, entity, 0); } Call_StartForward(gH_Forwards_LeaveZone); Call_PushCell(other); Call_PushCell(type); Call_PushCell(track); Call_PushCell(entityzone); Call_PushCell(entity); Call_PushCell(gA_ZoneCache[entityzone].iData); Call_Finish(); } public void TouchPost(int entity, int other) { if (other < 1 || other > MaxClients || IsFakeClient(other)) return; int zone = gI_EntityZone[entity]; if (zone == -1) return; int type = gA_ZoneCache[zone].iType; int track = gA_ZoneCache[zone].iTrack; if (gCV_EnforceTracks.BoolValue && type > Zone_End && track != Shavit_GetClientTrack(other)) return; if (gA_ZoneCache[zone].iForm == ZoneForm_trigger_multiple || gA_ZoneCache[zone].iForm == ZoneForm_trigger_teleport) { if (!SDKCall(gH_PassesTriggerFilters, entity, other)) { return; } } // do precise stuff here, this will be called *A LOT* switch (type) { case Zone_Start: { if (gB_Eventqueuefix) { static int tick_served[MAXPLAYERS + 1]; int curr_tick = GetGameTickCount(); // GAMMACASE: This prevents further abuses related to external events being ran after you teleport from the trigger, with events setup, outside the start zone into the start zone. // This accounts for the io events that might be set inside the start zone trigger in OnStartTouch and wont reset them! // Logic behind this code is that all events in this chain are not instantly fired, so checking if there were teleport from the outside of a start zone in last couple of ticks // and doing PhysicsRemoveTouchedList() now to trigger all OnEndTouch that should happen at the same tick but later and removing them allows further events from OnStartTouch be separated // and be fired after which is the expected and desired effect. // This also kills all ongoing events that were active on the client prior to the teleportation to start and also resets targetname and classname // before the OnStartTouch from triggers in start zone are run, thus preventing the maps to be abusable if they don't have any reset triggers in place if (gI_LatestTeleportTick[other] <= curr_tick <= gI_LatestTeleportTick[other] + 4) { if (curr_tick != tick_served[other]) { ResetClientTargetNameAndClassName(other, track); PhysicsRemoveTouchedList(other); ClearClientEvents(other); tick_served[other] = curr_tick; } return; } else if (curr_tick != tick_served[other]) { tick_served[other] = 0; } } #if 0 if (GetEntPropEnt(other, Prop_Send, "m_hGroundEntity") == -1 && !Shavit_GetStyleSettingBool(Shavit_GetBhopStyle(other), "startinair")) { return; } #endif // start timer instantly for main track, but require bonuses to have the current timer stopped // so you don't accidentally step on those while running if (Shavit_GetTimerStatus(other) == Timer_Stopped || Shavit_GetClientTrack(other) != Track_Main) { Shavit_StartTimer(other, track); } else if (track == Track_Main) { Shavit_StartTimer(other, Track_Main); } } case Zone_Respawn: { CS_RespawnPlayer(other); } case Zone_Teleport: { TeleportEntity(other, gA_ZoneCache[zone].fDestination, NULL_VECTOR, NULL_VECTOR); } case Zone_Slay: { TimerStatus status = Shavit_GetTimerStatus(other); if (status != Timer_Stopped) { Shavit_StopTimer(other); ACTUALLY_ForcePlayerSuicide(other); Shavit_PrintToChat(other, "%T", "ZoneSlayEnter", other, gS_ChatStrings.sWarning, gS_ChatStrings.sVariable2, gS_ChatStrings.sWarning); } } case Zone_Stop: { if(Shavit_GetTimerStatus(other) != Timer_Stopped) { Shavit_StopTimer(other); Shavit_PrintToChat(other, "%T", "ZoneStopEnter", other, gS_ChatStrings.sWarning, gS_ChatStrings.sVariable2, gS_ChatStrings.sWarning); } } } } public void UsePost_HookedButton(int entity, int activator, int caller, UseType type, float value) { if (activator < 1 || activator > MaxClients || IsFakeClient(activator)) { return; } int zone = gI_EntityZone[entity]; if (zone > -1) { ButtonLogic(activator, gA_ZoneCache[zone].iType, gA_ZoneCache[zone].iTrack); } } void ButtonLogic(int activator, int type, int track) { if (type == Zone_Start) { if (GetEntPropEnt(activator, Prop_Send, "m_hGroundEntity") == -1) { return; } GetClientAbsOrigin(activator, gF_ClimbButtonCache[activator][track][0]); GetClientEyeAngles(activator, gF_ClimbButtonCache[activator][track][1]); Shavit_StartTimer(activator, track); } else if (type == Zone_End && !Shavit_IsPaused(activator) && Shavit_GetTimerStatus(activator) == Timer_Running && Shavit_GetClientTrack(activator) == track) { Shavit_FinishMap(activator, track); } }