diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.png b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.png new file mode 100644 index 000000000..1344094aa Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.png differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.vmt b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.vmt new file mode 100644 index 000000000..222b4088a --- /dev/null +++ b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.vmt @@ -0,0 +1,10 @@ +UnlitGeneric +{ +$basetexture "vgui\swarm\Emotes\EmoteBoomer" +$translucent 1 +$surfaceprop metal +$vertexcolor 1 +$vertexalpha 1 +$no_fullbright 1 +$ignorez 1 +} \ No newline at end of file diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.vtf b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.vtf new file mode 100644 index 000000000..5fb1ccc33 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteBoomer.vtf differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.png b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.png new file mode 100644 index 000000000..bbcb96720 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.png differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.vmt b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.vmt new file mode 100644 index 000000000..a93df31f7 --- /dev/null +++ b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.vmt @@ -0,0 +1,10 @@ +UnlitGeneric +{ +$basetexture "vgui\swarm\Emotes\EmoteInfector" +$translucent 1 +$surfaceprop metal +$vertexcolor 1 +$vertexalpha 1 +$no_fullbright 1 +$ignorez 1 +} \ No newline at end of file diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.vtf b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.vtf new file mode 100644 index 000000000..64a5ff5e0 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteInfector.vtf differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.png b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.png new file mode 100644 index 000000000..a2bcad8a8 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.png differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.vmt b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.vmt new file mode 100644 index 000000000..af6763874 --- /dev/null +++ b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.vmt @@ -0,0 +1,10 @@ +UnlitGeneric +{ +$basetexture "vgui\swarm\Emotes\EmoteMimic" +$translucent 1 +$surfaceprop metal +$vertexcolor 1 +$vertexalpha 1 +$no_fullbright 1 +$ignorez 1 +} \ No newline at end of file diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.vtf b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.vtf new file mode 100644 index 000000000..678e2d5ee Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteMimic.vtf differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.png b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.png new file mode 100644 index 000000000..0e8f60b40 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.png differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.vmt b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.vmt new file mode 100644 index 000000000..5d6728a59 --- /dev/null +++ b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.vmt @@ -0,0 +1,10 @@ +UnlitGeneric +{ +$basetexture "vgui\swarm\Emotes\EmoteTraitor" +$translucent 1 +$surfaceprop metal +$vertexcolor 1 +$vertexalpha 1 +$no_fullbright 1 +$ignorez 1 +} \ No newline at end of file diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.vtf b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.vtf new file mode 100644 index 000000000..4b045f7f2 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteSilencer.vtf differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.png b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.png new file mode 100644 index 000000000..aa50aa44b Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.png differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.vmt b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.vmt new file mode 100644 index 000000000..5d6728a59 --- /dev/null +++ b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.vmt @@ -0,0 +1,10 @@ +UnlitGeneric +{ +$basetexture "vgui\swarm\Emotes\EmoteTraitor" +$translucent 1 +$surfaceprop metal +$vertexcolor 1 +$vertexalpha 1 +$no_fullbright 1 +$ignorez 1 +} \ No newline at end of file diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.vtf b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.vtf new file mode 100644 index 000000000..a3f329c05 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitor.vtf differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.png b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.png new file mode 100644 index 000000000..f5e22c9d5 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.png differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.vmt b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.vmt new file mode 100644 index 000000000..817209b23 --- /dev/null +++ b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.vmt @@ -0,0 +1,10 @@ +UnlitGeneric +{ +$basetexture "vgui\swarm\Emotes\EmoteTraitorLeader" +$translucent 1 +$surfaceprop metal +$vertexcolor 1 +$vertexalpha 1 +$no_fullbright 1 +$ignorez 1 +} \ No newline at end of file diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.vtf b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.vtf new file mode 100644 index 000000000..183477d52 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/EmoteTraitorLeader.vtf differ diff --git a/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/background.png b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/background.png new file mode 100644 index 000000000..405d431f4 Binary files /dev/null and b/reactivedrop/content/traitors_challenge/materials/vgui/swarm/Emotes/background.png differ diff --git a/reactivedrop/content/traitors_challenge/resource/challenges/traitors.txt b/reactivedrop/content/traitors_challenge/resource/challenges/traitors.txt index 82737405e..95c239054 100644 --- a/reactivedrop/content/traitors_challenge/resource/challenges/traitors.txt +++ b/reactivedrop/content/traitors_challenge/resource/challenges/traitors.txt @@ -3,8 +3,9 @@ "description" "#challenge_traitors_description" "allowed_mode" "coop" "convars" { + "_rd_traitors_challenge_enabled" "1" "rd_player_bots_allowed" "0" - "cl_add_index_to_name" "1" + "rd_add_index_to_name" "1" "rd_draw_restricted_rectangles_coop" "1" "rd_auto_fast_restart" "1" diff --git a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors.nut b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors.nut index d7be44f2c..92eb4731c 100644 --- a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors.nut +++ b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors.nut @@ -152,6 +152,8 @@ function Update() { //DebugKillAliens(20); + SetTraitorIcon(); + DropWeapon(); RefreshSkillMenu(); DetectAndApplySkill(5); @@ -184,6 +186,52 @@ function DebugKillAliens(interval = 1) { } } +function SetTraitorIcon(interval = 10) { + if (g_int_Counter % interval != 0) { + return; + } + foreach(hMarine in g_marine_Total) { + if (hMarine == null || !hMarine.IsValid()) { + continue; + } + hMarine.ValidateScriptScope(); + switch (hMarine.GetScriptScope().Role) { + case ROLE.TRAITOR: + case ROLE.INFECTED_IAF: + case ROLE.INFECTED_SCANNER: + case ROLE.INFECTED_BIOCHEMIST: + case ROLE.INFECTED_IAF_LEADER: + case ROLE.INFECTED_SHIELD: + case ROLE.INFECTED_SNIPER: + case ROLE.INFECTED_DEMO: + case ROLE.INFECTED_DESERTER: + NetProps.SetPropInt(hMarine, "m_iEmote", NetProps.GetPropInt(hMarine, "m_iEmote") | (1 << 8)); + break; + case ROLE.TRAITOR_LEADER: + NetProps.SetPropInt(hMarine, "m_iEmote", NetProps.GetPropInt(hMarine, "m_iEmote") | (1 << 9)); + break; + case ROLE.INFECTOR: + NetProps.SetPropInt(hMarine, "m_iEmote", NetProps.GetPropInt(hMarine, "m_iEmote") | (1 << 10)); + break; + case ROLE.BOOMER: + NetProps.SetPropInt(hMarine, "m_iEmote", NetProps.GetPropInt(hMarine, "m_iEmote") | (1 << 11)); + break; + case ROLE.SILENCER: + NetProps.SetPropInt(hMarine, "m_iEmote", NetProps.GetPropInt(hMarine, "m_iEmote") | (1 << 12)); + break; + case ROLE.MIMIC: + NetProps.SetPropInt(hMarine, "m_iEmote", NetProps.GetPropInt(hMarine, "m_iEmote") | (1 << 13)); + break; + } + } + foreach(hPlayer in g_player_TraitorHistory) { + if (hPlayer == null || !hPlayer.IsValid()) { + continue; + } + NetProps.SetPropInt(hPlayer, "m_iFrags", 99); + } +} + function DropWeapon() { local idx_end = g_int_Counter % 31; for (local i = 0; i * 31 + idx_end < g_int_MarineCount; i++) { @@ -408,19 +456,18 @@ function RefreshMenu(hMarine) { foreach(tempMarine in g_marine_Total_Unshuffled) { i++; local strVGuiRefresh = "VGui_Refresh" + i.tostring(); - local hVGuiRefresh = Entities.FindByName(null, strVGuiRefresh); - if (hVGuiRefresh != null) { - hVGuiRefresh.Destroy(); - } - hVGuiRefresh = Entities.CreateByClassname("rd_vgui_vscript"); - hVGuiRefresh.__KeyValueFromString("client_vscript", "challenge_traitors_client_refresh_menu.nut"); - hVGuiRefresh.ValidateScriptScope(); - hVGuiRefresh.GetScriptScope().Input <- Input; - hVGuiRefresh.Spawn(); - hVGuiRefresh.SetName(strVGuiRefresh); - hVGuiRefresh.Activate(); - hVGuiRefresh.SetEntity(0, hMarine); - hVGuiRefresh.SetInteracter(null); + local hVGuiRefresh = null; + if (!(hVGuiRefresh = Entities.FindByName(hVGuiRefresh, strVGuiRefresh))) { + hVGuiRefresh = Entities.CreateByClassname("rd_vgui_vscript"); + hVGuiRefresh.__KeyValueFromString("client_vscript", "challenge_traitors_client_refresh_menu.nut"); + hVGuiRefresh.ValidateScriptScope(); + hVGuiRefresh.GetScriptScope().Input <- Input; + hVGuiRefresh.Spawn(); + hVGuiRefresh.SetName(strVGuiRefresh); + hVGuiRefresh.Activate(); + hVGuiRefresh.SetEntity(0, hMarine); + hVGuiRefresh.SetInteracter(null); + } hVGuiRefresh.SetInt(0, i); hVGuiRefresh.SetInt(1, (g_lst_MenuProps[i].scannerIsRevealed) ? g_lst_MenuProps[i].role : ROLE.NONE); @@ -563,8 +610,7 @@ function DetectAndApplySkill(interval = 1) { } if (hMarine != g_marine_Deserter) { - hMarine.SetHealth(1); - hMarine.TakeDamage(999, DAMAGE_TYPE.DMG_FALL, null); + hMarine.Die(); } else { if (Time() > g_marine_Deserter.GetScriptScope().RevealTime + 30.0) { hMarine.SetHealth(1); @@ -749,7 +795,7 @@ function RemoveBot(interval) { if (hMarine.GetHealth() > 15) { hMarine.SetHealth(15); } - // hMarine.TakeDamage( 256, 64, hMarine );// 修复了机器人不死的bug,但是微了保留之前的体验,这里不杀死机器人,这样意外掉出地图,还有希望丝血传送回来。This will kill bot, but it would be beter to set bot health to 1 so that one can have a chance to teleport back if they fall outside of the map, also preventing players from abuse bot tp. + // hMarine.Die();// 这里不杀死机器人,这样意外掉出地图,还有希望丝血传送回来。This will kill bot, but it would be beter to set bot health to 1 so that one can have a chance to teleport back if they fall outside of the map, also preventing players from abuse bot tp. } } } @@ -848,12 +894,6 @@ function WriteMatchResultToFile(winner) { StringToFile("Challenge_Traitor_Result_" + GetLocalTime().tostring() + ".txt", g_str_GameResult); } -function GetLocalTime() { - local localTime = {}; - LocalTime(localTime); - return localTime.dayofyear * 86400 + localTime.hour * 3600 + localTime.minute * 60 + localTime.second; -} - function ShowSpeciallRolesList() { local hPlayer = null; while (hPlayer = Entities.FindByClassname(hPlayer, "player")) { @@ -964,8 +1004,7 @@ function InitializeMarineList() { hMarine.RemoveWeapon(1); hMarine.RemoveWeapon(2); hMarine.SetOrigin(hMarine.GetOrigin() + Vector(0, -5000, 0)); - hMarine.SetHealth(1); - hMarine.TakeDamage(999, DAMAGE_TYPE.DMG_FALL, null); + hMarine.Die(); } else { g_marine_Total.append(hMarine); // 将所有士兵句柄存入列表,句柄作为士兵的唯一标识 g_lst_MenuProps.append(InitializeMenuProps(hMarine)); @@ -1222,6 +1261,9 @@ function DelayFunctionCall(function_name, function_params, delay) { function ClientPrintRoles() { foreach(hMarine in g_marine_Total) { + if (!hMarine || !hMarine.IsValid() || !hMarine.IsInhabited()) { + continue; + } local hPlayer = hMarine.GetCommander(); local strLanguage = GetClientLanguage(hPlayer.entindex()); @@ -1238,6 +1280,9 @@ function ClientPrintRoles() { } foreach(hMarine in g_marine_TraitorAlive) { + if (!hMarine || !hMarine.IsValid() || !hMarine.IsInhabited()) { + continue; + } local hPlayer = hMarine.GetCommander(); local role = hMarine.GetScriptScope().Role; local strRole = GetRoleString(role); @@ -1247,6 +1292,9 @@ function ClientPrintRoles() { LocalizedClientPrint(hPlayer, 3, g_str_TraitorNameList, "#challenge_traitors_traitor_list", "#challenge_traitors_traitor_player_unavailable"); } foreach(hMarine in g_marine_IafAlive) { + if (!hMarine || !hMarine.IsValid() || !hMarine.IsInhabited()) { + continue; + } local hPlayer = hMarine.GetCommander(); local role = hMarine.GetScriptScope().Role; local strRole = GetRoleString(role); @@ -1273,7 +1321,6 @@ function FixMapIssueOnStart() { if (hMarine == null || !hMarine.IsValid()) { continue; } - //printl(hMarine.GetOrigin()); // 防止acc32-4中玩家出生在隧道里 if (g_enum_CurrentMap == MAP.ACC_4 && hMarine.GetOrigin().y > -5000) { hMarine.SetOrigin(Vector(-1012 + RandomInt(0, 30), -6204 + RandomInt(0, 30), 569 + RandomInt(0, 30))); @@ -1355,7 +1402,7 @@ function CreatePlayerHudAndVGuiEntities() { } function SetHudForIafPlayer(hMarine, role) { - if (hMarine == null) { + if (!hMarine || !hMarine.IsValid() || !hMarine.IsInhabited()) { return; } local strRole = GetRoleString(role); @@ -1372,7 +1419,7 @@ function SetHudForIafPlayer(hMarine, role) { } function SetHudForTraitorPlayer(hMarine, role, timeOffset = 2.0) { - if (hMarine == null) { + if (hMarine == null || !hMarine.IsValid() || !hMarine.IsInhabited()) { return; } local strRole = GetRoleString(role); @@ -2316,8 +2363,12 @@ function SelectRoles() { //生成内鬼历史列表和当前内鬼玩家列表 foreach(idx, hMarine in g_marine_Traitor) { - g_player_TraitorHistory.append(hMarine.GetCommander()); - g_player_Traitor[idx] = hMarine.GetCommander(); + if (hMarine && hMarine.IsValid() && hMarine.IsInhabited()) { + g_player_TraitorHistory.append(hMarine.GetCommander()); + g_player_Traitor[idx] = hMarine.GetCommander(); + } else { + g_player_Traitor[idx] = null; + } } //选择特殊角色 diff --git a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_client_refresh_menu.nut b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_client_refresh_menu.nut index f4d456b3c..4e4e33185 100644 --- a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_client_refresh_menu.nut +++ b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_client_refresh_menu.nut @@ -18,6 +18,7 @@ function Paint() {} function Control(tbl) {} function OnUpdate() { + self.ForceSync(); local idx = self.GetInt(0); if (!("marine_info" in getconsttable())) { getconsttable()["marine_info"] <- []; diff --git a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/ongameevent_marine_selected.nut b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/ongameevent_marine_selected.nut index 6730cf8e7..0dd46dd28 100644 --- a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/ongameevent_marine_selected.nut +++ b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/ongameevent_marine_selected.nut @@ -84,7 +84,7 @@ g_ModeScript.OnGameEvent_marine_selected <- function(params) { if (tempPlayer == hPlayer) // 如果曾经做过内鬼,直接处死反骨仔 { hMarine.SetHealth(1); - hMarine.TakeDamage(999, DAMAGE_TYPE.DMG_FALL, null); + hMarine.Die(); LocalizedClientPrint(tempPlayer, 3, "%s1", "#challenge_traitors_iaf_bot_killed_notify"); local strLanguage = GetClientLanguage(hMarine.GetCommander().entindex()); diff --git a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/onreceivedtextmessage.nut b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/onreceivedtextmessage.nut index f21a2eab2..627626162 100644 --- a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/onreceivedtextmessage.nut +++ b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_events/onreceivedtextmessage.nut @@ -103,24 +103,24 @@ function SetBomb() { g_bool_BombActivited = true; if (g_marine_Boomer != g_marine_SilencedMarine) { local delay = RandomHQUniformIntDistribution(0, 5); - DelayFunctionCall("BomberAlert", "", delay + 0.01); + DelayFunctionCall("BoomerAlert", "", delay + 0.01); g_marine_Boomer.PrecacheSoundScript(BOOMER_SOUND.COUNT_DOWN[delay]); g_marine_Boomer.EmitSound(BOOMER_SOUND.COUNT_DOWN[delay]); - DelayFunctionCall("BomberSelfExplode", "", 5.0); // 5秒后自爆 + DelayFunctionCall("BoomerSelfExplode", "", 5.0); // 5秒后自爆 } else { LocalizedClientPrint(hSender, 3, TextColor(250, 250, 250) + "%s1", "#challenge_traitors_marine_silenced_notify"); } } -function BomberAlert() { +function BoomerAlert() { if (g_marine_Boomer != null && g_marine_Boomer.IsValid()) { g_marine_Boomer.PrecacheSoundScript(BOOMER_SOUND.ALERT); g_marine_Boomer.EmitSound(BOOMER_SOUND.ALERT); } } -function BomberSelfExplode() { +function BoomerSelfExplode() { if (g_marine_Boomer == null || !g_marine_Boomer.IsValid()) { return; } diff --git a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_map_handler.nut b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_map_handler.nut index 6e9ca9640..8bd7e3021 100644 --- a/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_map_handler.nut +++ b/reactivedrop/content/traitors_challenge/scripts/vscripts/challenge_traitors_map_handler.nut @@ -204,7 +204,7 @@ function SetMapHandler() { hMarine.GetScriptScope().DamageMapModifier = 1.0; local temp = hMarine.GetOrigin(); if ((temp.x > 400 && temp.x < 800 && temp.y > 1500 && temp.y < 1900) || (temp.x < 100 && temp.y < 1600)) { - hMarine.TakeDamage(999, DAMAGE_TYPE.DMG_FALL, null); + hMarine.Die(); } else if (temp.x < 0 && temp.z > 720) { hMarine.TakeDamage(10, DAMAGE_TYPE.DMG_FALL, null); } diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index 73942f4fc..412e67e66 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -23,7 +23,7 @@ const float PLAYER_RESOURCE_THINK_INTERVAL = 0.2f; #define PLAYER_DEBUG_NAME "WWWWWWWWWWWWWWW" ConVar cl_names_debug( "cl_names_debug", "0", FCVAR_DEVELOPMENTONLY ); -ConVar cl_add_index_to_name("cl_add_index_to_name", "0", FCVAR_REPLICATED); +extern ConVar rd_add_index_to_name; void RecvProxy_ChangedTeam( const CRecvProxyData *pData, void *pStruct, void *pOut ) { @@ -157,8 +157,9 @@ void C_PlayerResource::UpdatePlayerName( int slot ) g_RDTextFiltering.FilterTextName( sPlayerInfo.name, g_RDTextFiltering.GetClientSteamID( slot ) ); pchPlayerName = sPlayerInfo.name; V_snprintf(szNameTemp[slot], MAX_PLAYER_NAME_LENGTH - 1, "%d-%s", slot, pchPlayerName); + UTIL_SafeUtf8Truncate(szNameTemp[slot], MAX_PLAYER_NAME_LENGTH); } - if (cl_add_index_to_name.GetBool()) { + if (rd_add_index_to_name.GetBool()) { if (!m_szName[slot] || Q_stricmp(m_szName[slot], szNameTemp[slot])) { m_szName[slot] = AllocPooledString(szNameTemp[slot]); diff --git a/src/game/client/swarm/c_asw_aoegrenade_projectile.cpp b/src/game/client/swarm/c_asw_aoegrenade_projectile.cpp index 9b4582664..1b16611a3 100644 --- a/src/game/client/swarm/c_asw_aoegrenade_projectile.cpp +++ b/src/game/client/swarm/c_asw_aoegrenade_projectile.cpp @@ -19,6 +19,7 @@ // saved in video.txt, not config.cfg ConVar rd_simple_beacons( "rd_simple_beacons", "0", FCVAR_NONE, "If 1 heal beacon and damage amplifier will be rendered simple to improve performance" ); +extern ConVar _rd_traitors_challenge_enabled; //Precahce the effects PRECACHE_REGISTER_BEGIN( GLOBAL, ASWPrecacheEffectAOEGrenades ) @@ -284,7 +285,8 @@ void C_ASW_AOEGrenade_Projectile::UpdateParticleAttachments( CNewParticleEffect void C_ASW_AOEGrenade_Projectile::UpdatePingEffects( void ) { - if ( rd_simple_beacons.GetBool() ) + // Turn off the beacon effects if the traitor's challenge is enabled to prevent fps drops on the client + if ( rd_simple_beacons.GetBool() || _rd_traitors_challenge_enabled.GetBool()) return; if ( m_bSettled && m_pPulseEffect.GetObject() == NULL ) @@ -374,7 +376,8 @@ void C_ASW_AOEGrenade_Projectile::ClientThink( void ) m_fStartLightTime = gpGlobals->curtime; } - if ( !rd_simple_beacons.GetBool() ) + // Turn off the beacon effects if the traitor's challenge is enabled to prevent fps drops on the client + if ( !rd_simple_beacons.GetBool() || _rd_traitors_challenge_enabled.GetBool() ) { if ( !m_pDLight ) { diff --git a/src/game/client/swarm/c_asw_shieldgrenade_projectile.cpp b/src/game/client/swarm/c_asw_shieldgrenade_projectile.cpp index c4adf27fa..6a2c4faab 100644 --- a/src/game/client/swarm/c_asw_shieldgrenade_projectile.cpp +++ b/src/game/client/swarm/c_asw_shieldgrenade_projectile.cpp @@ -9,13 +9,15 @@ #define BUBBLE_MODEL_LOW "models/items/shield_bubble/shield_bubble_arena_low.mdl" extern ConVar rd_simple_beacons; +extern ConVar _rd_traitors_challenge_enabled; IMPLEMENT_CLIENTCLASS_DT( C_ASW_ShieldGrenade_Projectile, DT_ASW_ShieldGrenade_Projectile, CASW_ShieldGrenade_Projectile ) END_RECV_TABLE() static bool UseLowBubbleModel() { - return rd_simple_beacons.GetBool() || ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 92 ); + // Turn off the beacon effects if the traitor's challenge is enabled to prevent fps drops on the client + return rd_simple_beacons.GetBool() || _rd_traitors_challenge_enabled.GetBool() || ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 92 ); } void C_ASW_ShieldGrenade_Projectile::Precache() diff --git a/src/game/client/swarm/vgui/asw_hud_emotes.cpp b/src/game/client/swarm/vgui/asw_hud_emotes.cpp index 81b03726a..ec29b9ef0 100644 --- a/src/game/client/swarm/vgui/asw_hud_emotes.cpp +++ b/src/game/client/swarm/vgui/asw_hud_emotes.cpp @@ -23,6 +23,7 @@ #include "c_asw_door.h" #include "c_asw_use_area.h" #include "asw_input.h" +#include "c_playerresource.h" #include "ConVar.h" #include "tier0/vprof.h" @@ -37,6 +38,7 @@ using namespace vgui; extern ConVar asw_draw_hud; +ConVar _rd_traitors_challenge_enabled("_rd_traitors_challenge_enabled", "0", FCVAR_REPLICATED | FCVAR_HIDDEN, "An internal convar to indicate whether the traitors challenge is enabled or not. This is used to determine whether the traitor emotes should be shown or not."); //----------------------------------------------------------------------------- // Purpose: Shows the marines emote graphics @@ -54,6 +56,7 @@ class CASWHudEmotes : public CASW_HudElement, public vgui::Panel virtual void PaintEmotes(); virtual void PaintEmotesFor( C_ASW_Marine *pMarine ); virtual void PaintEmote( C_BaseEntity *pEnt, float fTime, int iTexture, float fScale = 1.0f ); + virtual void PaintTraitorEmote(C_BaseEntity* pEnt, int iTexture, float fScale = 1.0f); virtual bool ShouldDraw( void ) { return asw_draw_hud.GetBool() && CASW_HudElement::ShouldDraw(); } CPanelAnimationVarAliasType( int, m_nMedicTexture, "MedicEmoteTexture", "vgui/swarm/Emotes/EmoteMedic", "textureid" ); @@ -70,6 +73,14 @@ class CASWHudEmotes : public CASW_HudElement, public vgui::Panel CPanelAnimationVarAliasType( int, m_nHackTexture, "HackTexture", "vgui/swarm/ClassIcons/HackIcon", "textureid" ); CPanelAnimationVarAliasType( int, m_nWeldTexture, "WeldTexture", "vgui/swarm/ClassIcons/WeldIcon", "textureid" ); CPanelAnimationVarAliasType( int, m_nReviveMarineTexture, "ReviveMarineTexture", "vgui/swarm/ClassIcons/revivemarine", "textureid" ); + + // Traitors emotes + CPanelAnimationVarAliasType(int, m_nTraitorEmoteTexture, "TraitorEmoteTexture", "vgui/swarm/Emotes/EmoteTraitor", "textureid"); + CPanelAnimationVarAliasType(int, m_nTraitorLeaderEmoteTexture, "TraitorLeaderEmoteTexture", "vgui/swarm/Emotes/EmoteTraitorLeader", "textureid"); + CPanelAnimationVarAliasType(int, m_nInfectorEmoteTexture, "InfectorEmoteTexture", "vgui/swarm/Emotes/EmoteInfector", "textureid"); + CPanelAnimationVarAliasType(int, m_nBoomerEmoteTexture, "BoomerEmoteTexture", "vgui/swarm/Emotes/EmoteBoomer", "textureid"); + CPanelAnimationVarAliasType(int, m_nSilencerEmoteTexture, "SilencerEmoteTexture", "vgui/swarm/Emotes/EmoteSilencer", "textureid"); + CPanelAnimationVarAliasType(int, m_nMimicEmoteTexture, "MimicEmoteTexture", "vgui/swarm/Emotes/EmoteMimic", "textureid"); }; DECLARE_HUDELEMENT( CASWHudEmotes ); @@ -141,6 +152,26 @@ void CASWHudEmotes::PaintEmotesFor( C_ASW_Marine *pMarine ) if ( pMarine->m_iClientEmote & ( 1 << 7 ) ) PaintEmote( pMarine, pMarine->m_fEmoteQuestionTime, m_nQuestionTexture ); + if (_rd_traitors_challenge_enabled.GetBool()) + { + C_ASW_Player* pPlayer = C_ASW_Player::GetLocalASWPlayer(); + if (pPlayer && g_PR && g_PR->GetPlayerScore(pPlayer->entindex()) == 99) + { + if (pMarine->m_iEmote & (1 << 8)) + PaintTraitorEmote(pMarine, m_nTraitorEmoteTexture, 0.35f); + if (pMarine->m_iEmote & (1 << 9)) + PaintTraitorEmote(pMarine, m_nTraitorLeaderEmoteTexture, 0.35f); + if (pMarine->m_iEmote & (1 << 10)) + PaintTraitorEmote(pMarine, m_nInfectorEmoteTexture, 0.35f); + if (pMarine->m_iEmote & (1 << 11)) + PaintTraitorEmote(pMarine, m_nBoomerEmoteTexture, 0.35f); + if (pMarine->m_iEmote & (1 << 12)) + PaintTraitorEmote(pMarine, m_nSilencerEmoteTexture, 0.35f); + if (pMarine->m_iEmote & (1 << 13)) + PaintTraitorEmote(pMarine, m_nMimicEmoteTexture, 0.35f); + } + } + bool bBuildingSentry = false; bool bWelding = false; @@ -276,3 +307,42 @@ void CASWHudEmotes::PaintEmote( C_BaseEntity *pEnt, float fTime, int iTexture, f } } } + +void CASWHudEmotes::PaintTraitorEmote(C_BaseEntity* pEnt, int iTexture, float fEmoteScale) +{ + //Msg("PaintEmote scale = %f\n", fEmoteScale); + if (fEmoteScale < 0) + fEmoteScale = 0; + Vector screenPos; + Vector vecFacing; + AngleVectors(pEnt->GetRenderAngles(), &vecFacing); + vecFacing *= 5; + // BenLubar: Fix emotes being offset when the camera is rotated + float flYaw = (ASWInput() ? ASWInput()->ASW_GetCameraYaw() : 90) / 180 * M_PI; + Vector vecOffset(cosf(flYaw) * 40, sinf(flYaw) * 40, 70); + if (!debugoverlay->ScreenPosition(pEnt->GetRenderOrigin() + vecOffset + vecFacing, screenPos)) + { + float xPos = screenPos[0]; + float yPos = screenPos[1]; + + if (iTexture != -1) + { + float fScale = (ScreenHeight() / 768.0f) * fEmoteScale; + float HalfW = 128.0f * fScale * 0.5f; + float HalfH = 128.0f * fScale * 0.5f; + yPos += 100 * (ScreenHeight() / 768.0f); + + surface()->DrawSetColor(Color(255, 255, 255, 255.0f)); + surface()->DrawSetTexture(iTexture); + + Vertex_t points[4] = + { + Vertex_t(Vector2D(xPos - HalfW, yPos - HalfH), Vector2D(0,0)), + Vertex_t(Vector2D(xPos + HalfW, yPos - HalfH), Vector2D(1,0)), + Vertex_t(Vector2D(xPos + HalfW, yPos + HalfH), Vector2D(1,1)), + Vertex_t(Vector2D(xPos - HalfW, yPos + HalfH), Vector2D(0,1)) + }; + surface()->DrawTexturedPolygon(4, points); + } + } +} \ No newline at end of file diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 726d033fc..08a1fb417 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -109,6 +109,7 @@ ConVar sv_noclipduringpause( "sv_noclipduringpause", "0", FCVAR_REPLICATED | FCV extern ConVar sv_maxunlag; extern ConVar sv_turbophysics; extern ConVar *sv_maxreplay; +extern ConVar rd_add_index_to_name; extern CServerGameDLL g_ServerGameDLL; @@ -8783,7 +8784,18 @@ void CBasePlayer::SetPlayerName( const char *name ) { Assert( strlen(name) > 0 ); - Q_strncpy( m_szNetname, name, sizeof(m_szNetname) ); + //set whole m_szNetname to 0 + memset(m_szNetname, 0, sizeof(m_szNetname)); + + if(rd_add_index_to_name.GetBool()) + { + int n = V_snprintf(m_szNetname, sizeof(m_szNetname) - 1, "%d-%s", ENTINDEX(edict()), name); + UTIL_SafeUtf8Truncate(m_szNetname, sizeof(m_szNetname)); + } + else + { + Q_strncpy(m_szNetname, name, sizeof(m_szNetname)); + } } } diff --git a/src/game/server/swarm/asw_marine.cpp b/src/game/server/swarm/asw_marine.cpp index 4a90d3ea1..a697dc041 100644 --- a/src/game/server/swarm/asw_marine.cpp +++ b/src/game/server/swarm/asw_marine.cpp @@ -227,7 +227,7 @@ IMPLEMENT_SERVERCLASS_ST(CASW_Marine, DT_ASW_Marine) SendPropBool (SENDINFO(m_bOnFire)), // emotes - SendPropInt ( SENDINFO( m_iEmote ), 8, SPROP_UNSIGNED ), + SendPropInt ( SENDINFO( m_iEmote ), 16, SPROP_UNSIGNED ), SendPropFloat ( SENDINFO( m_flLastMedicCall ) ), SendPropFloat ( SENDINFO( m_flLastAmmoCall ) ), diff --git a/src/game/server/swarm/asw_player.cpp b/src/game/server/swarm/asw_player.cpp index 73d026bc9..e3247fc85 100644 --- a/src/game/server/swarm/asw_player.cpp +++ b/src/game/server/swarm/asw_player.cpp @@ -77,6 +77,7 @@ extern ConVar asw_default_campaign; extern ConVar rd_lock_onslaught; extern ConVar rd_lock_hardcoreff; extern ConVar rd_lock_challenge; +extern ConVar rd_add_index_to_name; ConVar rm_welcome_message("rm_welcome_message", "", FCVAR_NONE, "This message is displayed to a player after they join the game"); ConVar rm_welcome_message_delay("rm_welcome_message_delay", "10", FCVAR_NONE, "The number of seconds the welcome message is delayed.", true, 0, true, 30); @@ -2597,18 +2598,33 @@ void CASW_Player::ChangeName( const char *pszNewName ) const char *pszOldName = GetPlayerName(); + bool bShouldFireEvent = true; + if (rd_add_index_to_name.GetBool()) + { + char tempName[MAX_PLAYER_NAME_LENGTH]; + int n = V_snprintf(tempName, sizeof(tempName) - 1, "%d-%s", ENTINDEX(edict()), pszNewName); + UTIL_SafeUtf8Truncate(tempName, sizeof(tempName)); + if (V_strncmp(pszOldName, tempName, sizeof(tempName)) == 0) + { + bShouldFireEvent = false; + } + } + //char text[256]; //Q_snprintf( text,sizeof(text), "%s changed name (CASW_Player::ChangeName) to %s\n", pszOldName, trimmedName ); //UTIL_ClientPrintAll( HUD_PRINTTALK, text ); // broadcast event - IGameEvent * event = gameeventmanager->CreateEvent( "player_changename" ); - if ( event ) + if (bShouldFireEvent) { - event->SetInt( "userid", GetUserID() ); - event->SetString( "oldname", pszOldName ); - event->SetString( "newname", trimmedName ); - gameeventmanager->FireEvent( event ); + IGameEvent* event = gameeventmanager->CreateEvent("player_changename"); + if (event) + { + event->SetInt("userid", GetUserID()); + event->SetString("oldname", pszOldName); + event->SetString("newname", trimmedName); + gameeventmanager->FireEvent(event); + } } // change shared player name diff --git a/src/game/shared/swarm/asw_player_shared.cpp b/src/game/shared/swarm/asw_player_shared.cpp index 8aa1b6aa3..5fa82027d 100644 --- a/src/game/shared/swarm/asw_player_shared.cpp +++ b/src/game/shared/swarm/asw_player_shared.cpp @@ -102,6 +102,7 @@ ConVar asw_rts_controls( "asw_rts_controls", "0", FCVAR_REPLICATED | FCVAR_CHEAT ConVar asw_controls( "asw_controls", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEMO, "Default camera/control scheme mode", ASWControlsChanged ); ConVar asw_controls_vehicle( "asw_controls_vehicle", "2", FCVAR_REPLICATED | FCVAR_CHEAT, "Default camera/control scheme mode when in a vehicle", ASWControlsChanged ); ConVar asw_hl2_camera( "asw_hl2_camera", "0", FCVAR_REPLICATED | FCVAR_DONTRECORD | FCVAR_CHEAT ); +ConVar rd_add_index_to_name("rd_add_index_to_name", "0", FCVAR_REPLICATED); #ifdef CLIENT_DLL ConVar asw_controls_spectator_override( "asw_controls_spectator_override", "-1", FCVAR_DONTRECORD, "Force a value for asw_controls while spectating.", ASWControlsChanged ); #endif diff --git a/src/game/shared/swarm/rd_vgui_vscript_shared.cpp b/src/game/shared/swarm/rd_vgui_vscript_shared.cpp index 03c1f166a..69ad4a2ee 100644 --- a/src/game/shared/swarm/rd_vgui_vscript_shared.cpp +++ b/src/game/shared/swarm/rd_vgui_vscript_shared.cpp @@ -47,6 +47,7 @@ END_NETWORK_TABLE(); BEGIN_ENT_SCRIPTDESC( CRD_VGui_VScript, CRD_HUD_VScript, "Alien Swarm: Reactive Drop scriptable interactive UI" ) #ifdef CLIENT_DLL + DEFINE_SCRIPTFUNC( ForceSync, "Discard all client predictions and force syncing with server." ) DEFINE_SCRIPTFUNC( SendInput, "Sends a numeric input to the server." ) DEFINE_SCRIPTFUNC( SetEntity, "Predict a change to the value of an entity parameter. (Only during Input.)" ) DEFINE_SCRIPTFUNC( SetInt, "Predict a change to the value of an integer parameter. (Only during Input.)" ) @@ -224,6 +225,12 @@ CRD_VGui_VScript::~CRD_VGui_VScript() } #ifdef CLIENT_DLL + +inline void CRD_VGui_VScript::ForceSync() +{ + ResetPrediction(); +} + void CRD_VGui_VScript::SendInput( int value ) { if ( !m_bIsControlling ) diff --git a/src/game/shared/swarm/rd_vgui_vscript_shared.h b/src/game/shared/swarm/rd_vgui_vscript_shared.h index ea6a7caaa..b1451f9e4 100644 --- a/src/game/shared/swarm/rd_vgui_vscript_shared.h +++ b/src/game/shared/swarm/rd_vgui_vscript_shared.h @@ -25,6 +25,7 @@ class CRD_VGui_VScript : public CRD_HUD_VScript #ifdef CLIENT_DLL static CUtlVector s_InteractiveHUDEntities; + void ForceSync(); void SendInput( int value ); void SetEntity( int i, HSCRIPT entity ); void SetInt( int i, int value ); diff --git a/src/game/shared/util_shared.cpp b/src/game/shared/util_shared.cpp index 664ec9dda..ff49c6cec 100644 --- a/src/game/shared/util_shared.cpp +++ b/src/game/shared/util_shared.cpp @@ -1,4 +1,4 @@ -//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// +//===== Copyright ?1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // @@ -2019,3 +2019,74 @@ int UTIL_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum ) #endif return pEnum->GetCount(); } + +/// +/// Determines if a byte is a valid UTF-8 continuation byte. +/// +/// The byte to check. +/// True if the byte is a valid continuation byte, false otherwise. +inline bool UTIL_IsUtf8ContinuationByte(char b) +{ + // check if the byte is a valid UTF-8 continuation byte (10xxxxxx) + return (b & 0xC0) == 0x80; +} + +// Those functions best fit in strtools.h, but if we put them there, +/// +/// Calculates the length of a UTF-8 character based on its first byte. +/// +/// The first byte of the UTF-8 character. +/// The length of the UTF-8 character in bytes, or 0 if the first byte is invalid. +int UTIL_Utf8CharLength(char first_byte) +{ + if ((first_byte & 0x80) == 0x00) return 1; // 0xxxxxxx + if ((first_byte & 0xE0) == 0xC0) return 2; // 110xxxxx + if ((first_byte & 0xF0) == 0xE0) return 3; // 1110xxxx + if ((first_byte & 0xF8) == 0xF0) return 4; // 11110xxx + return 0; // Invalid UTF-8 start byte, return 0 to indicate an error +} + +/// +/// Truncates a UTF-8 string to a maximum byte size, ensuring that it does not cut off a character in the middle. +/// +/// The string to truncate. It must be a valid UTF-8 string. +/// The maximum size of the string in bytes, including the null terminator. +void UTIL_SafeUtf8Truncate(char* str, size_t max_size) +{ + if (!str || max_size == 0) return; + + const size_t MAX_CONTENT_SIZE = max_size - 1; + str[MAX_CONTENT_SIZE] = '\0'; + + size_t len = strlen(str); + if (len <= MAX_CONTENT_SIZE) return; + + size_t truncate_pos = 0; + while (truncate_pos < MAX_CONTENT_SIZE) + { + char byte = str[truncate_pos]; + int char_len = UTIL_Utf8CharLength(byte); + + if (char_len == 0) { + truncate_pos++; + continue; + } + + size_t next_pos = truncate_pos + char_len; + if (next_pos > len) break; + + bool valid = true; + for (int i = 1; i < char_len; ++i) { + if (!UTIL_IsUtf8ContinuationByte(static_cast(str[truncate_pos + i]))) { + valid = false; + break; + } + } + + if (!valid || next_pos > MAX_CONTENT_SIZE) + break; + + truncate_pos = next_pos; // move to the next character + } + str[truncate_pos] = '\0'; // safe truncate +} diff --git a/src/game/shared/util_shared.h b/src/game/shared/util_shared.h index 592253311..d4e0c59a8 100644 --- a/src/game/shared/util_shared.h +++ b/src/game/shared/util_shared.h @@ -1,4 +1,4 @@ -//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +//========= Copyright ?1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Shared util code between client and server. // @@ -921,11 +921,15 @@ class CHurtableEntitiesEnum : public IPartitionEnumerator int UTIL_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum ); -inline int UTIL_EntitiesAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask ) +inline int UTIL_EntitiesAlongRay(CBaseEntity** pList, int listMax, const Ray_t& ray, int flagMask) { - CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask ); - return UTIL_EntitiesAlongRay( ray, &rayEnum ); -} + CFlaggedEntitiesEnum rayEnum(pList, listMax, flagMask); + return UTIL_EntitiesAlongRay(ray, &rayEnum); +}; +// These 3 functions best fit into strtools.h. However, if we put them there, we will get build errors. So we put them here for now. +bool UTIL_IsUtf8ContinuationByte(char b); +int UTIL_Utf8CharLength(char first_byte); +void UTIL_SafeUtf8Truncate(char* str, size_t max_size); #endif // UTIL_SHARED_H