example
This commit is contained in:
@@ -37,6 +37,11 @@ namespace Barotrauma
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CharacterInfo CreateCharacterInfo(string speciesName, string name = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||||
|
{
|
||||||
|
return new CharacterInfo(speciesName, name, jobPrefab, ragdollFileName, variant, randSync);
|
||||||
|
}
|
||||||
|
|
||||||
public static void SetClientCharacter(Client client, Character character)
|
public static void SetClientCharacter(Client client, Character character)
|
||||||
{
|
{
|
||||||
GameMain.Server.SetClientCharacter(client, character);
|
GameMain.Server.SetClientCharacter(client, character);
|
||||||
@@ -65,10 +70,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void StartGame()
|
public static void SetSpectatorPos(Client client, Vector2 pos)
|
||||||
{
|
{
|
||||||
GameMain.Server.StartGame();
|
client.SpectatePos = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LuaGame
|
public class LuaGame
|
||||||
@@ -89,9 +95,9 @@ namespace Barotrauma
|
|||||||
GameMain.Server.SendChatMessage(msg, messageType, sender, character);
|
GameMain.Server.SendChatMessage(msg, messageType, sender, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendTraitorMessage(Client client, string msg, TraitorMessageType type)
|
public static void SendTraitorMessage(Client client, string msg, string missionid, TraitorMessageType type)
|
||||||
{
|
{
|
||||||
GameMain.Server.SendTraitorMessage(client, msg, "", type);
|
GameMain.Server.SendTraitorMessage(client, msg, missionid, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendDirectChatMessage(string sendername, string text, Character sender, ChatMessageType messageType = ChatMessageType.Private, Client client = null)
|
public static void SendDirectChatMessage(string sendername, string text, Character sender, ChatMessageType messageType = ChatMessageType.Private, Client client = null)
|
||||||
@@ -163,7 +169,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (CharacterPrefab.FindBySpeciesName(name) != null)
|
if (CharacterPrefab.FindBySpeciesName(name) != null)
|
||||||
{
|
{
|
||||||
Character.Create(name, spawnPosition, ToolBox.RandomSeed(8));
|
spawnedCharacter = Character.Create(name, spawnPosition, ToolBox.RandomSeed(8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +218,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
DebugConsole.ExecuteCommand(command);
|
DebugConsole.ExecuteCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void StartGame()
|
||||||
|
{
|
||||||
|
GameMain.Server.StartGame();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -251,6 +263,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return random.Next(min, max);
|
return random.Next(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float RangeFloat(float min, float max)
|
||||||
|
{
|
||||||
|
double range = (double)max - (double)min;
|
||||||
|
double sample = random.NextDouble();
|
||||||
|
double scaled = (sample * range) + min;
|
||||||
|
float f = (float)scaled;
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hooks:
|
// hooks:
|
||||||
@@ -264,9 +287,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public class LuaHook
|
public class LuaHook
|
||||||
{
|
{
|
||||||
public Script env;
|
public LuaSetup env;
|
||||||
|
|
||||||
public LuaHook(Script e)
|
public LuaHook(LuaSetup e)
|
||||||
{
|
{
|
||||||
env = e;
|
env = e;
|
||||||
}
|
}
|
||||||
@@ -310,7 +333,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = env.Call(hf.function, args);
|
var result = env.lua.Call(hf.function, args);
|
||||||
if (result.IsNil() == false)
|
if (result.IsNil() == false)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
@@ -318,15 +341,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (e is InterpreterException)
|
env.HandleLuaException(e);
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(((InterpreterException)e).DecoratedMessage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (s.EndsWith(".lua"))
|
if (s.EndsWith(".lua"))
|
||||||
{
|
{
|
||||||
Console.WriteLine(s);
|
lua.PrintMessage(s);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -45,15 +45,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (e is InterpreterException)
|
lua.HandleLuaException(e);
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(((InterpreterException)e).DecoratedMessage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,31 @@ namespace Barotrauma
|
|||||||
public LuaHook hook;
|
public LuaHook hook;
|
||||||
public LuaGame game;
|
public LuaGame game;
|
||||||
|
|
||||||
|
public void HandleLuaException(Exception ex)
|
||||||
|
{
|
||||||
|
if(ex is InterpreterException)
|
||||||
|
{
|
||||||
|
PrintMessage(((InterpreterException)ex).DecoratedMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintMessage(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintMessage(object message)
|
||||||
|
{
|
||||||
|
Console.WriteLine(message.ToString());
|
||||||
|
if (GameMain.Server != null)
|
||||||
|
{
|
||||||
|
foreach (var c in GameMain.Server.ConnectedClients)
|
||||||
|
{
|
||||||
|
GameMain.Server.SendDirectChatMessage(message.ToString(), c, ChatMessageType.Console);
|
||||||
|
GameServer.Log("[LUA] " + message.ToString(), ServerLog.MessageType.ServerMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void DoString(string code)
|
public void DoString(string code)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -25,15 +50,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (e is InterpreterException)
|
HandleLuaException(e);
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(((InterpreterException)e).DecoratedMessage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,15 +63,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (e is InterpreterException)
|
HandleLuaException(e);
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(((InterpreterException)e).DecoratedMessage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,27 +75,24 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (e is InterpreterException)
|
HandleLuaException(e);
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(((InterpreterException)e).DecoratedMessage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LuaSetup()
|
public LuaSetup()
|
||||||
{
|
{
|
||||||
Console.WriteLine("Lua!");
|
PrintMessage("Lua!");
|
||||||
|
|
||||||
LuaScriptLoader luaScriptLoader = new LuaScriptLoader(this);
|
LuaScriptLoader luaScriptLoader = new LuaScriptLoader(this);
|
||||||
|
|
||||||
LuaCustomConverters.RegisterAll();
|
LuaCustomConverters.RegisterAll();
|
||||||
|
|
||||||
|
UserData.RegisterType<TraitorMessageType>();
|
||||||
|
UserData.RegisterType<JobPrefab>();
|
||||||
|
UserData.RegisterType<CharacterInfo>();
|
||||||
|
UserData.RegisterType<Rectangle>();
|
||||||
|
UserData.RegisterType<Point>();
|
||||||
UserData.RegisterType<Level.InterestingPosition>();
|
UserData.RegisterType<Level.InterestingPosition>();
|
||||||
UserData.RegisterType<Level.PositionType>();
|
UserData.RegisterType<Level.PositionType>();
|
||||||
UserData.RegisterType<Level>();
|
UserData.RegisterType<Level>();
|
||||||
@@ -107,12 +113,15 @@ namespace Barotrauma
|
|||||||
UserData.RegisterType<Vector2>();
|
UserData.RegisterType<Vector2>();
|
||||||
UserData.RegisterType<Vector3>();
|
UserData.RegisterType<Vector3>();
|
||||||
UserData.RegisterType<Vector4>();
|
UserData.RegisterType<Vector4>();
|
||||||
|
|
||||||
|
|
||||||
lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.LoadMethods);
|
lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.LoadMethods);
|
||||||
|
|
||||||
|
lua.Options.DebugPrint = PrintMessage;
|
||||||
|
|
||||||
lua.Options.ScriptLoader = luaScriptLoader;
|
lua.Options.ScriptLoader = luaScriptLoader;
|
||||||
|
|
||||||
hook = new LuaHook(lua);
|
hook = new LuaHook(this);
|
||||||
game = new LuaGame(this);
|
game = new LuaGame(this);
|
||||||
|
|
||||||
lua.Globals["Player"] = new LuaPlayer();
|
lua.Globals["Player"] = new LuaPlayer();
|
||||||
@@ -132,6 +141,8 @@ namespace Barotrauma
|
|||||||
lua.Globals["Vector2"] = UserData.CreateStatic<Vector2>();
|
lua.Globals["Vector2"] = UserData.CreateStatic<Vector2>();
|
||||||
lua.Globals["Vector3"] = UserData.CreateStatic<Vector3>();
|
lua.Globals["Vector3"] = UserData.CreateStatic<Vector3>();
|
||||||
lua.Globals["PositionType"] = UserData.CreateStatic<Level.PositionType>();
|
lua.Globals["PositionType"] = UserData.CreateStatic<Level.PositionType>();
|
||||||
|
lua.Globals["JobPrefab"] = UserData.CreateStatic<JobPrefab>();
|
||||||
|
lua.Globals["TraitorMessageType"] = UserData.CreateStatic<TraitorMessageType>();
|
||||||
|
|
||||||
foreach (string d in Directory.GetDirectories("Lua"))
|
foreach (string d in Directory.GetDirectories("Lua"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2363,8 +2363,6 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
Log("Round started.", ServerLog.MessageType.ServerMessage);
|
Log("Round started.", ServerLog.MessageType.ServerMessage);
|
||||||
|
|
||||||
GameMain.Lua.hook.Call("roundStart", new MoonSharp.Interpreter.DynValue[] { });
|
|
||||||
|
|
||||||
|
|
||||||
gameStarted = true;
|
gameStarted = true;
|
||||||
initiatedStartGame = false;
|
initiatedStartGame = false;
|
||||||
@@ -2374,6 +2372,9 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
roundStartTime = DateTime.Now;
|
roundStartTime = DateTime.Now;
|
||||||
|
|
||||||
|
GameMain.Lua.hook.Call("roundStart", new MoonSharp.Interpreter.DynValue[] { });
|
||||||
|
|
||||||
|
|
||||||
yield return CoroutineStatus.Success;
|
yield return CoroutineStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,493 @@
|
|||||||
|
local traitormod = {}
|
||||||
|
traitormod.peoplePercentages = {}
|
||||||
|
|
||||||
|
traitormod.roundtraitors = {}
|
||||||
|
traitormod.selectedCodeResponses = {}
|
||||||
|
traitormod.selectedCodePhrases = {}
|
||||||
|
|
||||||
|
traitormod.enabled = true
|
||||||
|
traitormod.traitorShipEnabled = true
|
||||||
|
traitormod.roundGamemode = ""
|
||||||
|
|
||||||
|
local traitorAssignDelay = 0
|
||||||
|
local traitorsAssigned = true
|
||||||
|
local warningClients = {}
|
||||||
|
|
||||||
|
Game.OverrideTraitors(true) -- shutup old traitors
|
||||||
|
|
||||||
|
local config = dofile("lua/traitormod/traitorconfig.lua")
|
||||||
|
local util = dofile("lua/traitormod/util.lua")
|
||||||
|
|
||||||
|
traitormod.config = config
|
||||||
|
|
||||||
|
traitormod.setPercentage = function(client, amount)
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
|
if traitormod.peoplePercentages[client.SteamID] == nil then
|
||||||
|
traitormod.peoplePercentages[client.SteamID] = {}
|
||||||
|
traitormod.peoplePercentages[client.SteamID].penalty = 1
|
||||||
|
traitormod.peoplePercentages[client.SteamID].p = config.firstJoinPercentage
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.peoplePercentages[client.SteamID].p = amount
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.addPenalty = function (client, amount)
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
|
if traitormod.peoplePercentages[client.SteamID] == nil then
|
||||||
|
traitormod.peoplePercentages[client.SteamID] = {}
|
||||||
|
traitormod.peoplePercentages[client.SteamID].penalty = 1
|
||||||
|
traitormod.peoplePercentages[client.SteamID].p = config.firstJoinPercentage
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.peoplePercentages[client.SteamID].penalty = traitormod.peoplePercentages[client.SteamID].penalty + amount
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.getPenalty = function (client)
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
|
if traitormod.peoplePercentages[client.SteamID] == nil then
|
||||||
|
traitormod.peoplePercentages[client.SteamID] = {}
|
||||||
|
traitormod.peoplePercentages[client.SteamID].penalty = 1
|
||||||
|
traitormod.peoplePercentages[client.SteamID].p = config.firstJoinPercentage
|
||||||
|
end
|
||||||
|
|
||||||
|
return traitormod.peoplePercentages[client.SteamID].penalty
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.addPercentage = function(client, amount)
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
|
if traitormod.peoplePercentages[client.SteamID] == nil then
|
||||||
|
traitormod.peoplePercentages[client.SteamID] = {}
|
||||||
|
traitormod.peoplePercentages[client.SteamID].penalty = 1
|
||||||
|
traitormod.peoplePercentages[client.SteamID].p = config.firstJoinPercentage
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.peoplePercentages[client.SteamID].p = traitormod.peoplePercentages[client.SteamID].p + amount
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.getPercentage = function (client)
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
|
if traitormod.peoplePercentages[client.SteamID] == nil then
|
||||||
|
traitormod.peoplePercentages[client.SteamID] = {}
|
||||||
|
traitormod.peoplePercentages[client.SteamID].penalty = 1
|
||||||
|
traitormod.peoplePercentages[client.SteamID].p = config.firstJoinPercentage
|
||||||
|
end
|
||||||
|
|
||||||
|
return traitormod.peoplePercentages[client.SteamID].p
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.chooseCodes = function()
|
||||||
|
local codewords = dofile("lua/traitormod/traitorconfig.lua").codewords -- copy
|
||||||
|
|
||||||
|
local amount = config.amountCodewords
|
||||||
|
|
||||||
|
for i = 1, amount, 1 do
|
||||||
|
local rand = Random.Range(1, #codewords + 1)
|
||||||
|
table.insert(traitormod.selectedCodeResponses, codewords[rand])
|
||||||
|
table.remove(codewords, rand)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, amount, 1 do
|
||||||
|
local rand = Random.Range(1, #codewords + 1)
|
||||||
|
table.insert(traitormod.selectedCodePhrases, codewords[rand])
|
||||||
|
table.remove(codewords, rand)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.selectPlayerPercentages = function(ignore)
|
||||||
|
local players = util.GetValidPlayersNoBotsAndNoTraitors(
|
||||||
|
traitormod.roundtraitors)
|
||||||
|
|
||||||
|
for i = 1, #players, 1 do
|
||||||
|
for key, value in pairs(ignore) do
|
||||||
|
if players[i] == value then
|
||||||
|
table.remove(players, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
|
||||||
|
for key, value in pairs(players) do
|
||||||
|
local client = util.clientChar(value)
|
||||||
|
|
||||||
|
total = total + traitormod.getPercentage(client)
|
||||||
|
end
|
||||||
|
|
||||||
|
local rng = Random.Range(0, total)
|
||||||
|
|
||||||
|
local addup = 0
|
||||||
|
|
||||||
|
for key, value in pairs(players) do
|
||||||
|
local client = util.clientChar(value)
|
||||||
|
|
||||||
|
local percentage = traitormod.getPercentage(client)
|
||||||
|
|
||||||
|
if rng >= addup and rng <= addup + percentage then return value end
|
||||||
|
|
||||||
|
addup = addup + percentage
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.chooseTraitors = function(amount)
|
||||||
|
local traitors = {}
|
||||||
|
|
||||||
|
for i = 1, amount, 1 do
|
||||||
|
local found = traitormod.selectPlayerPercentages(traitors)
|
||||||
|
|
||||||
|
if found == nil then
|
||||||
|
print("Not enough players")
|
||||||
|
break
|
||||||
|
else
|
||||||
|
table.insert(traitors, found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return traitors
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage = function(msg, client, notchatbox)
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
|
if notchatbox == true then
|
||||||
|
Game.SendDirectChatMessage("", msg, nil, 11, client)
|
||||||
|
else
|
||||||
|
Game.SendDirectChatMessage("", msg, nil, 7, client)
|
||||||
|
end
|
||||||
|
|
||||||
|
Game.SendDirectChatMessage("", msg, nil, 1, client)
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.assignNormalTraitors = function(amount)
|
||||||
|
local traitors = traitormod.chooseTraitors(amount)
|
||||||
|
|
||||||
|
for key, value in pairs(traitors) do traitormod.roundtraitors[value] = {} end
|
||||||
|
|
||||||
|
local targets = util.GetValidPlayersNoTraitors(traitormod.roundtraitors)
|
||||||
|
|
||||||
|
for key, value in pairs(traitors) do
|
||||||
|
traitormod.roundtraitors[value].objectiveType = "kill"
|
||||||
|
traitormod.roundtraitors[value].objectiveTarget =
|
||||||
|
targets[Random.Range(1, #targets + 1)]
|
||||||
|
|
||||||
|
local mess =
|
||||||
|
"You are a traitor! Your secret mission is to assassinate " ..
|
||||||
|
traitormod.roundtraitors[value].objectiveTarget.name ..
|
||||||
|
"! Avoid being loud, make the death look like an accident. We will provide you more information after you finish your current mission."
|
||||||
|
|
||||||
|
if util.TableCount(traitormod.roundtraitors) > 1 then
|
||||||
|
mess = mess ..
|
||||||
|
"\n\nIt is possible that there are other agents on this submarine. You dont know their names, but you do have a method of communication. Use the code words to greet the agent and code response to respond. Disguise such words in a normal-looking phrase so the crew doesn't suspect anything."
|
||||||
|
mess = mess .. "\n\n The code words are: "
|
||||||
|
|
||||||
|
for key, va in pairs(traitormod.selectedCodePhrases) do
|
||||||
|
mess = mess .. "\"" .. va .. "\" "
|
||||||
|
end
|
||||||
|
|
||||||
|
mess = mess .. "\n The code response is: "
|
||||||
|
|
||||||
|
for key, va in pairs(traitormod.selectedCodeResponses) do
|
||||||
|
mess = mess .. "\"" .. va .. "\" "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Game.Log(value.name ..
|
||||||
|
" Was assigned to be traitor, his first mission is to kill " ..
|
||||||
|
traitormod.roundtraitors[value].objectiveTarget.name, 6)
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage(mess, util.clientChar(value))
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.chooseNextObjective = function(key, value)
|
||||||
|
local players = util.GetValidPlayersNoTraitors(traitormod.roundtraitors)
|
||||||
|
|
||||||
|
if #players == 0 then
|
||||||
|
traitormod.sendTraitorMessage("Good job agent, You did it.",
|
||||||
|
util.clientChar(key), true)
|
||||||
|
|
||||||
|
value.needNewObjective = false
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local assassinate = players[Random.Range(1, #players + 1)]
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage("Your next mission is to assassinate " ..
|
||||||
|
assassinate.name, util.clientChar(key),
|
||||||
|
true)
|
||||||
|
|
||||||
|
Game.Log(key.name ..
|
||||||
|
" Was assigned another traitor mission, His mission is to kill " ..
|
||||||
|
assassinate.name, 6)
|
||||||
|
|
||||||
|
value.objectiveTarget = assassinate
|
||||||
|
value.objectiveType = "kill"
|
||||||
|
value.needNewObjective = false
|
||||||
|
end
|
||||||
|
|
||||||
|
Hook.Add("roundStart", "traitor_start", function()
|
||||||
|
if not traitormod.enabled then return end
|
||||||
|
|
||||||
|
local players = util.GetValidPlayersNoBots()
|
||||||
|
|
||||||
|
for key, value in pairs(players) do
|
||||||
|
local client = util.clientChar(value)
|
||||||
|
|
||||||
|
if traitormod.getPenalty(client) > 1 then
|
||||||
|
traitormod.addPenalty(client, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.addPercentage(client, config.roundEndPercentageIncrease / traitormod.getPenalty(client))
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.chooseCodes()
|
||||||
|
|
||||||
|
local rng = Random.Range(0, 100)
|
||||||
|
|
||||||
|
if rng < config.traitorShipChance and traitormod.traitorShipEnabled == true then
|
||||||
|
local amount = config.getAmountShipTraitors(#util.GetValidPlayers())
|
||||||
|
|
||||||
|
Game.Log("Infiltraiton Gamemode selected, Random = " .. rng, 6)
|
||||||
|
traitormod.roundGamemode = "Infiltraiton"
|
||||||
|
|
||||||
|
traitormod.traitorShipRoundStart(amount)
|
||||||
|
else
|
||||||
|
traitorsAssigned = false
|
||||||
|
traitorAssignDelay = Timer.GetTime() + config.traitorSpawnDelay
|
||||||
|
|
||||||
|
Game.Log("Assasination Gamemode was selected, Random = " .. rng, 6)
|
||||||
|
traitormod.roundGamemode = "Assasination"
|
||||||
|
|
||||||
|
traitormod.spawnTraitorShipAndHide()
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, value in pairs(Player.GetAllClients()) do
|
||||||
|
Game.SendTraitorMessage(value, 'traitor', 'traitor', TraitorMessageType.Objective) -- enable everyone to sabotage
|
||||||
|
end
|
||||||
|
|
||||||
|
Game.SendMessage(
|
||||||
|
"We are using Custom Traitors Plugin by EvilFactory (https://steamcommunity.com/id/evilfactory/)\n Join discord.gg/f9zvNNuxu9",
|
||||||
|
3)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Hook.Add("roundEnd", "traitor_end", function()
|
||||||
|
if not traitormod.enabled then return end
|
||||||
|
|
||||||
|
local msg = "Traitors of the round: "
|
||||||
|
|
||||||
|
for key, value in pairs(traitormod.roundtraitors) do
|
||||||
|
msg = msg .. "\"" .. key.name .. "\" "
|
||||||
|
end
|
||||||
|
|
||||||
|
msg = msg .. "\n\nGamemode: " .. traitormod.roundGamemode
|
||||||
|
|
||||||
|
Game.SendMessage(msg, 1)
|
||||||
|
|
||||||
|
for key, value in pairs(traitormod.roundtraitors) do
|
||||||
|
local c = util.clientChar(key)
|
||||||
|
traitormod.setPercentage(c, config.traitorPercentageSet)
|
||||||
|
traitormod.addPenalty(c, config.traitorPenalty)
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.roundtraitors = {}
|
||||||
|
traitormod.selectedCodeResponses = {}
|
||||||
|
traitormod.selectedCodePhrases = {}
|
||||||
|
warningClients = {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
Hook.Add("think", "traitor_think", function()
|
||||||
|
if not traitormod.enabled then return end
|
||||||
|
|
||||||
|
if not traitorsAssigned and traitorAssignDelay < Timer.GetTime() then
|
||||||
|
local amount = config.getAmountTraitors(#util.GetValidPlayers())
|
||||||
|
traitormod.assignNormalTraitors(amount)
|
||||||
|
|
||||||
|
traitorsAssigned = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, value in pairs(Player.GetAllCharacters()) do
|
||||||
|
|
||||||
|
if warningClients[value] == nil then
|
||||||
|
|
||||||
|
if value.IsDead == true then
|
||||||
|
local attackers = value.LastAttacker
|
||||||
|
-- and attackers ~= value
|
||||||
|
if util.characterIsTraitor(attackers, traitormod.roundtraitors) and
|
||||||
|
attackers ~= value then
|
||||||
|
traitormod.sendTraitorMessage(
|
||||||
|
"Your death was caused by a traitor on a secret mission.",
|
||||||
|
util.clientChar(value), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
warningClients[value] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, value in pairs(traitormod.roundtraitors) do
|
||||||
|
if key ~= nil and key.IsDead == false then
|
||||||
|
|
||||||
|
if value.needNewObjective == true and Timer.GetTime() >
|
||||||
|
value.objectiveTimer then
|
||||||
|
traitormod.chooseNextObjective(key, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
if value.objectiveTarget ~= nil then
|
||||||
|
|
||||||
|
if value.objectiveTarget.IsDead then
|
||||||
|
traitormod.sendTraitorMessage(
|
||||||
|
"Great job, You killed " .. value.objectiveTarget.name ..
|
||||||
|
". We will provide you your next mission shortly.",
|
||||||
|
util.clientChar(key), true)
|
||||||
|
|
||||||
|
value.objectiveTarget = nil
|
||||||
|
|
||||||
|
value.needNewObjective = true
|
||||||
|
value.objectiveTimer =
|
||||||
|
Timer.GetTime() + config.nextMissionDelay
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
Hook.Add("chatMessage", "chatcommands", function(msg, client)
|
||||||
|
|
||||||
|
if bit32.band(client.Permissions, 0x40) == 0x40 then
|
||||||
|
if msg == "!toggletraitormod" then
|
||||||
|
traitormod.enabled = not traitormod.enabled
|
||||||
|
|
||||||
|
if traitormod.enabled then
|
||||||
|
Game.SendDirectChatMessage("", "TraitorMod Enabled", nil, 1,
|
||||||
|
client)
|
||||||
|
else
|
||||||
|
Game.SendDirectChatMessage("", "TraitorMod Disabled", nil, 1,
|
||||||
|
client)
|
||||||
|
end
|
||||||
|
|
||||||
|
Game.OverrideTraitors(traitormod.enabled)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if msg == "!toggletraitorship" then
|
||||||
|
traitormod.traitorShipEnabled = not traitormod.traitorShipEnabled
|
||||||
|
|
||||||
|
if traitormod.traitorShipEnabled then
|
||||||
|
Game.SendDirectChatMessage("", "Traitor Ship Enabled", nil, 1,
|
||||||
|
client)
|
||||||
|
else
|
||||||
|
Game.SendDirectChatMessage("", "Traitor Ship Disabled", nil, 1,
|
||||||
|
client)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if msg == "!traitors" then
|
||||||
|
local tosend = "Traitors of the round: "
|
||||||
|
|
||||||
|
for key, value in pairs(traitormod.roundtraitors) do
|
||||||
|
tosend = tosend .. "\"" .. key.name .. "\" "
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage(tosend, client)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if msg == "!traitoralive" then
|
||||||
|
local num = 0
|
||||||
|
|
||||||
|
for key, value in pairs(traitormod.roundtraitors) do
|
||||||
|
if key.IsDead == false then
|
||||||
|
num = num + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if num > 0 then
|
||||||
|
traitormod.sendTraitorMessage("There are traitors alive.", client)
|
||||||
|
else
|
||||||
|
traitormod.sendTraitorMessage("All traitors are dead!", client)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if msg == "!percentages" then
|
||||||
|
local msg = ""
|
||||||
|
for key, value in pairs(Player.GetAllClients()) do
|
||||||
|
|
||||||
|
msg = msg .. value.name .. " - " ..
|
||||||
|
math.floor(traitormod.getPercentage(value)) .. "%\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage(msg, client)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if msg == "!percentage" then
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage("You have " ..
|
||||||
|
math.floor(traitormod.getPercentage(client)) ..
|
||||||
|
"% of being traitor", client)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if util.stringstarts(msg, "!traitor") then
|
||||||
|
if util.characterIsTraitor(client.Character, traitormod.roundtraitors) then
|
||||||
|
local msg = "You are a traitor. "
|
||||||
|
|
||||||
|
if traitormod.roundtraitors[client.Character].objectiveTarget ~= nil then
|
||||||
|
msg = msg .. "\nCurrent Mission: kill " ..
|
||||||
|
traitormod.roundtraitors[client.Character]
|
||||||
|
.objectiveTarget.name
|
||||||
|
end
|
||||||
|
|
||||||
|
if util.TableCount(traitormod.roundtraitors) > 1 then
|
||||||
|
|
||||||
|
msg = msg .. "\n\n The code words are: "
|
||||||
|
|
||||||
|
for key, va in pairs(traitormod.selectedCodePhrases) do
|
||||||
|
msg = msg .. "\"" .. va .. "\" "
|
||||||
|
end
|
||||||
|
|
||||||
|
msg = msg .. "\n The code response is: "
|
||||||
|
|
||||||
|
for key, va in pairs(traitormod.selectedCodeResponses) do
|
||||||
|
msg = msg .. "\"" .. va .. "\" "
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage(msg, client)
|
||||||
|
else
|
||||||
|
traitormod.sendTraitorMessage("You are not a traitor.", client)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Traitormod = traitormod
|
||||||
|
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
local traitormod = Traitormod
|
||||||
|
local respawnshuttle = nil
|
||||||
|
local loadedpeople = {}
|
||||||
|
|
||||||
|
local config = dofile("lua/traitormod/traitorconfig.lua")
|
||||||
|
local util = dofile("lua/traitormod/util.lua")
|
||||||
|
|
||||||
|
Game.OverrideRespawnSub(true)
|
||||||
|
|
||||||
|
Hook.Add("think", "traitorShip", function()
|
||||||
|
if respawnshuttle ~= nil then
|
||||||
|
|
||||||
|
local pos1 = Submarine.MainSub.WorldPosition
|
||||||
|
local pos2 = respawnshuttle.WorldPosition
|
||||||
|
|
||||||
|
if Vector2.Distance(pos1, pos2) < config.traitorShipGodModeDistance then
|
||||||
|
respawnshuttle.GodMode = false
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, value in pairs(traitormod.roundtraitors) do
|
||||||
|
local client = util.clientChar(key)
|
||||||
|
|
||||||
|
if client ~= nil then
|
||||||
|
if client.InGame == true and loadedpeople[key] == nil then
|
||||||
|
loadedpeople[key] = {}
|
||||||
|
loadedpeople[key].next = Timer.GetTime() + 15
|
||||||
|
elseif loadedpeople[key] ~= nil and Timer.GetTime() <
|
||||||
|
loadedpeople[key].next then
|
||||||
|
Player.SetClientCharacter(client, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Hook.Add("roundEnd", "traitorShipRemove", function()
|
||||||
|
respawnshuttle = nil
|
||||||
|
|
||||||
|
loadedpeople = {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
traitormod.spawnTraitorShipAndHide = function()
|
||||||
|
Game.SetRespawnSubTeam(2)
|
||||||
|
Game.DispatchRespawnSub()
|
||||||
|
|
||||||
|
local sub = Game.GetRespawnSub()
|
||||||
|
sub.ShowSonarMarker = false
|
||||||
|
|
||||||
|
sub.GodMode = true
|
||||||
|
|
||||||
|
local steering = Game.GetSubmarineSteering(sub)
|
||||||
|
steering.AutoPilot = false
|
||||||
|
|
||||||
|
sub.SetPosition({0, Level.Loaded.BottomPos + 1000})
|
||||||
|
|
||||||
|
return sub
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.spawnTraitorShip = function()
|
||||||
|
Game.SetRespawnSubTeam(2)
|
||||||
|
Game.DispatchRespawnSub()
|
||||||
|
|
||||||
|
local sub = Game.GetRespawnSub()
|
||||||
|
respawnshuttle = sub
|
||||||
|
|
||||||
|
sub.ShowSonarMarker = false
|
||||||
|
|
||||||
|
local positions = Level.Loaded.PositionsOfInterest
|
||||||
|
|
||||||
|
local goodpositions = {}
|
||||||
|
|
||||||
|
for key, value in ipairs(positions) do
|
||||||
|
if value.PositionType == PositionType.MainPath then
|
||||||
|
table.insert(goodpositions, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- sub.SetPosition({Level.Loaded.EndPosition[1], Level.Loaded.EndPosition[2] - 10000})
|
||||||
|
sub.SetPosition(goodpositions[math.floor(#goodpositions / 2)].Position
|
||||||
|
.ToVector2())
|
||||||
|
|
||||||
|
sub.GodMode = true
|
||||||
|
|
||||||
|
local steering = Game.GetSubmarineSteering(sub)
|
||||||
|
|
||||||
|
steering.AutoPilot = false
|
||||||
|
|
||||||
|
return sub
|
||||||
|
end
|
||||||
|
|
||||||
|
traitormod.traitorShipRoundStart = function(maxplayers)
|
||||||
|
local sub = traitormod.spawnTraitorShip()
|
||||||
|
|
||||||
|
local assignedNowTraitors = traitormod.chooseTraitors(maxplayers)
|
||||||
|
|
||||||
|
for index, value in pairs(assignedNowTraitors) do
|
||||||
|
traitormod.roundtraitors[value] = {}
|
||||||
|
|
||||||
|
local mess =
|
||||||
|
"You are a traitor! Your mission is to exterminate the Main Sub's Crew, cooperate with your fellow agents."
|
||||||
|
|
||||||
|
mess = mess ..
|
||||||
|
"\n\nUse the codewords to communicate with the other agents."
|
||||||
|
mess = mess .. "\n\n The code words are: "
|
||||||
|
|
||||||
|
for key, va in pairs(traitormod.selectedCodePhrases) do
|
||||||
|
mess = mess .. "\"" .. va .. "\" "
|
||||||
|
end
|
||||||
|
|
||||||
|
mess = mess .. "\n The code response is: "
|
||||||
|
|
||||||
|
for key, va in pairs(traitormod.selectedCodeResponses) do
|
||||||
|
mess = mess .. "\"" .. va .. "\" "
|
||||||
|
end
|
||||||
|
|
||||||
|
Game.Log(value.name .. " Was assigned to be Ship traitor", 6)
|
||||||
|
|
||||||
|
local cl = util.clientChar(value)
|
||||||
|
|
||||||
|
traitormod.sendTraitorMessage(mess, cl)
|
||||||
|
|
||||||
|
local waypoint = WayPoint.GetRandom(SpawnType.Human, nil, sub)
|
||||||
|
|
||||||
|
value.TeleportTo(waypoint.WorldPosition)
|
||||||
|
-- Player.SetClientCharacter(cl, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
local config = {}
|
||||||
|
|
||||||
|
local codewords = {
|
||||||
|
"pomegrenade", "tabacco", "fish", "nonsense", "europa", "clown",
|
||||||
|
"thalamus", "hungry", "renegade", "angry", "green", "flamingos", "sink",
|
||||||
|
"mask", "boomer", "sweet", "ice", "charybdis", "cult", "secret", "moloch",
|
||||||
|
"husk", "rusted", "ruins", "red", "boat", "cats", "rats", "jeepers", "bench",
|
||||||
|
"tire", "trunk", "blow sticks", "thrashers"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.codewords = codewords
|
||||||
|
config.amountCodewords = 2
|
||||||
|
config.traitorSpawnDelay = 60
|
||||||
|
config.nextMissionDelay = 60
|
||||||
|
config.traitorShipChance = 15
|
||||||
|
config.traitorShipGodModeDistance = 4000
|
||||||
|
|
||||||
|
-- Traitor Selection Options
|
||||||
|
config.roundEndPercentageIncrease = 10
|
||||||
|
config.firstJoinPercentage = 10
|
||||||
|
config.traitorPercentageSet = 5
|
||||||
|
config.traitorPenalty = 5
|
||||||
|
|
||||||
|
-- >=12 players = 3 traitors, >=8 players = 2 traitors, default = 1 traitor
|
||||||
|
config.getAmountTraitors = function (amountClients)
|
||||||
|
if amountClients >= 12 then return 3 end
|
||||||
|
|
||||||
|
if amountClients >= 8 then return 2 end
|
||||||
|
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- shipTraitors and normal traitors will be selected equally
|
||||||
|
config.getAmountShipTraitors = config.getAmountTraitors
|
||||||
|
|
||||||
|
return config;
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
local util = {}
|
||||||
|
|
||||||
|
util.clientChar = function(char)
|
||||||
|
local clients = Player.GetAllClients()
|
||||||
|
|
||||||
|
for key, client in pairs(clients) do
|
||||||
|
if (client.Character == char) then return client end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
util.GetDeadClients = function()
|
||||||
|
local valid = {}
|
||||||
|
|
||||||
|
for key, value in pairs(Player.GetAllClients()) do
|
||||||
|
if value.InGame then
|
||||||
|
if value.Character == nil then table.insert(valid, value) end
|
||||||
|
|
||||||
|
if value.Character ~= nil and value.Character.IsDead == true then
|
||||||
|
table.insert(valid, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return valid
|
||||||
|
end
|
||||||
|
|
||||||
|
util.GetValidPlayers = function()
|
||||||
|
local chars = Player.GetAllCharacters()
|
||||||
|
local valid = {}
|
||||||
|
|
||||||
|
for key, value in pairs(chars) do
|
||||||
|
if (value.IsHuman == true and value.IsDead == false) and
|
||||||
|
value.ClientDisconnected == false then
|
||||||
|
table.insert(valid, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.stringstarts(String, Start)
|
||||||
|
return string.sub(String, 1, string.len(Start)) == Start
|
||||||
|
end
|
||||||
|
|
||||||
|
util.characterIsTraitor = function(char, traitors)
|
||||||
|
|
||||||
|
for key, value in pairs(traitors) do
|
||||||
|
if char == key then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
util.GetValidPlayersNoBotsAndNoTraitors = function(traitors)
|
||||||
|
local chars = Player.GetAllCharacters()
|
||||||
|
local valid = {}
|
||||||
|
|
||||||
|
for key, value in pairs(chars) do
|
||||||
|
if (value.IsHuman == true and value.IsDead == false) and
|
||||||
|
value.ClientDisconnected == false then
|
||||||
|
if value.IsBot == false and traitors[value] == nil then table.insert(valid, value) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
util.GetValidPlayersNoBots = function()
|
||||||
|
local chars = Player.GetAllCharacters()
|
||||||
|
local valid = {}
|
||||||
|
|
||||||
|
for key, value in pairs(chars) do
|
||||||
|
if (value.IsHuman == true and value.IsDead == false) and
|
||||||
|
value.ClientDisconnected == false then
|
||||||
|
if value.IsBot == false then table.insert(valid, value) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
util.TableCount = function (tbl)
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for key, value in pairs(tbl) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
util.GetValidPlayersNoTraitors = function(traitors)
|
||||||
|
local chars = Player.GetAllCharacters()
|
||||||
|
local valid = {}
|
||||||
|
|
||||||
|
for key, value in pairs(chars) do
|
||||||
|
if (value.IsHuman == true and value.IsDead == false) and
|
||||||
|
value.ClientDisconnected == false then
|
||||||
|
if traitors[value] == nil then
|
||||||
|
table.insert(valid, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return util
|
||||||
Reference in New Issue
Block a user