(3dc4135ce) v0.9.5.1
This commit is contained in:
@@ -20,6 +20,7 @@ namespace Barotrauma
|
||||
if (Job != null)
|
||||
{
|
||||
msg.Write(Job.Prefab.Identifier);
|
||||
msg.Write((byte)Job.Variant);
|
||||
msg.Write((byte)Job.Skills.Count);
|
||||
foreach (Skill skill in Job.Skills)
|
||||
{
|
||||
@@ -30,6 +31,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
msg.Write("");
|
||||
msg.Write((byte)0);
|
||||
}
|
||||
// TODO: animations
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!(this is AICharacter) || IsRemotePlayer)
|
||||
{
|
||||
if (!AllowInput)
|
||||
if (!CanMove)
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
if (memInput.Count > 0)
|
||||
@@ -156,7 +156,7 @@ namespace Barotrauma
|
||||
UInt16 networkUpdateID = msg.ReadUInt16();
|
||||
byte inputCount = msg.ReadByte();
|
||||
|
||||
if (AllowInput) Enabled = true;
|
||||
if (AllowInput) { Enabled = true; }
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
@@ -470,7 +470,11 @@ namespace Barotrauma
|
||||
msg.Write(Enabled);
|
||||
|
||||
//character with no characterinfo (e.g. some monster)
|
||||
if (Info == null) return;
|
||||
if (Info == null)
|
||||
{
|
||||
WriteStatus(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
||||
if (ownerClient != null)
|
||||
@@ -492,6 +496,7 @@ namespace Barotrauma
|
||||
msg.Write(this is AICharacter);
|
||||
msg.Write(info.SpeciesName);
|
||||
info.ServerWrite(msg);
|
||||
WriteStatus(msg);
|
||||
|
||||
DebugConsole.Log("Character spawn message length: " + (msg.LengthBytes - msgLength));
|
||||
}
|
||||
|
||||
@@ -197,7 +197,20 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (key.KeyChar != 0)
|
||||
if (key.Modifiers.HasFlag(ConsoleModifiers.Control))
|
||||
{
|
||||
if (key.Key == ConsoleKey.Z)
|
||||
{
|
||||
activeQuestionCallback = null;
|
||||
NewMessage("^Z");
|
||||
}
|
||||
else if (key.Key == ConsoleKey.D)
|
||||
{
|
||||
activeQuestionCallback = null;
|
||||
NewMessage("^D");
|
||||
}
|
||||
}
|
||||
else if (key.KeyChar != 0)
|
||||
{
|
||||
input += key.KeyChar;
|
||||
memoryIndex = -1;
|
||||
@@ -759,10 +772,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Server == null || args.Length == 0) return;
|
||||
|
||||
ShowQuestionPrompt("Reason for banning the endpoint \"" + args[0] + "\"?", (reason) =>
|
||||
ShowQuestionPrompt("Reason for banning the endpoint \"" + args[0] + "\"? (c to cancel)", (reason) =>
|
||||
{
|
||||
ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) =>
|
||||
if (reason == "c" || reason == "C") { return; }
|
||||
ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\") (c to cancel)", (duration) =>
|
||||
{
|
||||
if (duration == "c" || duration == "C") { return; }
|
||||
TimeSpan? banDuration = null;
|
||||
if (!string.IsNullOrWhiteSpace(duration))
|
||||
{
|
||||
@@ -859,6 +874,11 @@ namespace Barotrauma
|
||||
client.SpectateOnly = false;
|
||||
});
|
||||
|
||||
AssignOnExecute("starttraitormissionimmediately", (string[] args) =>
|
||||
{
|
||||
GameMain.Server?.TraitorManager?.SkipStartDelay();
|
||||
});
|
||||
|
||||
AssignOnExecute("difficulty|leveldifficulty", (string[] args) =>
|
||||
{
|
||||
if (GameMain.Server == null || args.Length < 1) return;
|
||||
@@ -1140,14 +1160,7 @@ namespace Barotrauma
|
||||
commands.Add(new Command("mission", "mission [name]/[index]: Select the mission type for the next round. The parameter can either be the name or the index number of the mission type (0 = first mission type, 1 = second mission type, etc).", (string[] args) =>
|
||||
{
|
||||
int index = -1;
|
||||
if (int.TryParse(string.Join(" ", args), out index))
|
||||
{
|
||||
GameMain.NetLobbyScreen.MissionTypeIndex = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.NetLobbyScreen.MissionTypeName = string.Join(" ", args);
|
||||
}
|
||||
GameMain.NetLobbyScreen.MissionTypeName = string.Join(" ", args);
|
||||
NewMessage("Set mission to " + GameMain.NetLobbyScreen.MissionTypeName, Color.Cyan);
|
||||
},
|
||||
() =>
|
||||
|
||||
@@ -243,6 +243,18 @@ namespace Barotrauma
|
||||
maxPlayers,
|
||||
ownerKey,
|
||||
steamId);
|
||||
|
||||
for (int i = 0; i < CommandLineArgs.Length; i++)
|
||||
{
|
||||
switch (CommandLineArgs[i].Trim())
|
||||
{
|
||||
case "-playstyle":
|
||||
Enum.TryParse(CommandLineArgs[i + 1], out PlayStyle playStyle);
|
||||
Server.ServerSettings.PlayStyle = playStyle;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseServer()
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Steering : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
// TODO: an enumeration would be much cleaner
|
||||
public bool MaintainPos;
|
||||
public bool LevelStartSelected;
|
||||
public bool LevelEndSelected;
|
||||
|
||||
@@ -29,14 +29,15 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
sendUpdateTimer -= deltaTime;
|
||||
//update client hulls if the amount of water has changed by >10%
|
||||
//or if oxygen percentage has changed by 5%
|
||||
if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f ||
|
||||
Math.Abs(lastSentOxygen - OxygenPercentage) > 5f ||
|
||||
lastSentFireCount != FireSources.Count ||
|
||||
FireSources.Count > 0)
|
||||
FireSources.Count > 0 ||
|
||||
sendUpdateTimer < -NetConfig.SparseHullUpdateInterval)
|
||||
{
|
||||
sendUpdateTimer -= deltaTime;
|
||||
if (sendUpdateTimer < 0.0f)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this);
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma.Networking
|
||||
public bool CompareTo(IPAddress ipCompare)
|
||||
{
|
||||
if (string.IsNullOrEmpty(IP) || ipCompare == null) { return false; }
|
||||
if (ipCompare.IsIPv4MappedToIPv6 && CompareTo(ipCompare.MapToIPv4().ToString()))
|
||||
if (ipCompare.IsIPv4MappedToIPv6 && CompareTo(ipCompare.MapToIPv4NoThrow().ToString()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void BanPlayer(string name, IPAddress ip, string reason, TimeSpan? duration)
|
||||
{
|
||||
string ipStr = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
|
||||
string ipStr = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4NoThrow().ToString() : ip.ToString();
|
||||
BanPlayer(name, ipStr, 0, reason, duration);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace Barotrauma.Networking
|
||||
public float ChatSpamTimer;
|
||||
public int ChatSpamCount;
|
||||
|
||||
public int RoundsSincePlayedAsTraitor;
|
||||
|
||||
public float KickAFKTimer;
|
||||
|
||||
public double MidRoundSyncTimeOut;
|
||||
@@ -52,12 +54,22 @@ namespace Barotrauma.Networking
|
||||
|
||||
public bool ReadyToStart;
|
||||
|
||||
public List<JobPrefab> JobPreferences;
|
||||
public JobPrefab AssignedJob;
|
||||
public List<Pair<JobPrefab, int>> JobPreferences;
|
||||
public Pair<JobPrefab, int> AssignedJob;
|
||||
|
||||
public float DeleteDisconnectedTimer;
|
||||
|
||||
public CharacterInfo CharacterInfo;
|
||||
private CharacterInfo characterInfo;
|
||||
public CharacterInfo CharacterInfo
|
||||
{
|
||||
get { return characterInfo; }
|
||||
set
|
||||
{
|
||||
if (characterInfo == value) { return; }
|
||||
characterInfo?.Remove();
|
||||
characterInfo = value;
|
||||
}
|
||||
}
|
||||
public NetworkConnection Connection { get; set; }
|
||||
|
||||
public bool SpectateOnly;
|
||||
@@ -84,7 +96,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
var jobs = JobPrefab.List.Values.ToList();
|
||||
// TODO: modding support?
|
||||
JobPreferences = new List<JobPrefab>(jobs.GetRange(0, Math.Min(jobs.Count, 3)));
|
||||
JobPreferences = new List<Pair<JobPrefab, int>>(jobs.GetRange(0, Math.Min(jobs.Count, 3)).Select(j => new Pair<JobPrefab, int>(j, 0)));
|
||||
|
||||
VoipQueue = new VoipQueue(ID, true, true);
|
||||
GameMain.Server.VoipServer.RegisterQueue(VoipQueue);
|
||||
@@ -94,6 +106,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
GameMain.Server.VoipServer.UnregisterQueue(VoipQueue);
|
||||
VoipQueue.Dispose();
|
||||
characterInfo?.Remove();
|
||||
characterInfo = null;
|
||||
}
|
||||
|
||||
public void InitClientSync()
|
||||
@@ -128,7 +142,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (lidgrenConn.IPEndPoint?.Address == null) { return false; }
|
||||
if ((lidgrenConn.IPEndPoint?.Address.IsIPv4MappedToIPv6 ?? false) &&
|
||||
lidgrenConn.IPEndPoint?.Address.MapToIPv4().ToString() == endpoint)
|
||||
lidgrenConn.IPEndPoint?.Address.MapToIPv4NoThrow().ToString() == endpoint)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.IO;
|
||||
using Barotrauma.Steam;
|
||||
using System.Xml.Linq;
|
||||
using System.Threading;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
@@ -77,7 +78,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public TraitorManager TraitorManager;
|
||||
|
||||
private ServerEntityEventManager entityEventManager;
|
||||
private readonly ServerEntityEventManager entityEventManager;
|
||||
|
||||
private FileSender fileSender;
|
||||
#if DEBUG
|
||||
@@ -115,8 +116,8 @@ namespace Barotrauma.Networking
|
||||
public int QueryPort => serverSettings?.QueryPort ?? 0;
|
||||
|
||||
public NetworkConnection OwnerConnection { get; private set; }
|
||||
private int? ownerKey;
|
||||
private UInt64? ownerSteamId;
|
||||
private readonly int? ownerKey;
|
||||
private readonly UInt64? ownerSteamId;
|
||||
|
||||
public GameServer(string name, int port, int queryPort = 0, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10, int? ownKey = null, UInt64? steamId = null)
|
||||
{
|
||||
@@ -215,6 +216,16 @@ namespace Barotrauma.Networking
|
||||
|
||||
GameMain.NetLobbyScreen.Select();
|
||||
GameMain.NetLobbyScreen.RandomizeSettings();
|
||||
if (!string.IsNullOrEmpty(serverSettings.SelectedSubmarine))
|
||||
{
|
||||
Submarine sub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == serverSettings.SelectedSubmarine);
|
||||
if (sub != null) { GameMain.NetLobbyScreen.SelectedSub = sub; }
|
||||
}
|
||||
if (!string.IsNullOrEmpty(serverSettings.SelectedShuttle))
|
||||
{
|
||||
Submarine shuttle = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == serverSettings.SelectedShuttle);
|
||||
if (shuttle != null) { GameMain.NetLobbyScreen.SelectedShuttle = shuttle; }
|
||||
}
|
||||
started = true;
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("GameServer:Start");
|
||||
@@ -1438,7 +1449,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
//no more room in this packet
|
||||
if (outmsg.LengthBytes + tempBuffer.LengthBytes > MsgConstants.MTU - 20)
|
||||
if (outmsg.LengthBytes + tempBuffer.LengthBytes > MsgConstants.MTU - 100)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -1523,6 +1534,7 @@ namespace Barotrauma.Networking
|
||||
outmsg.Write(client.SteamID);
|
||||
outmsg.Write(client.NameID);
|
||||
outmsg.Write(client.Name);
|
||||
outmsg.Write(client.Character == null || !gameStarted ? (client.PreferredJob ?? "") : "");
|
||||
outmsg.Write(client.Character == null || !gameStarted ? (ushort)0 : client.Character.ID);
|
||||
outmsg.Write(client.Muted);
|
||||
outmsg.Write(client.Connection != OwnerConnection); //is kicking the player allowed
|
||||
@@ -1578,7 +1590,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
outmsg.WriteRangedInteger((int)serverSettings.TraitorsEnabled, 0, 2);
|
||||
|
||||
outmsg.WriteRangedInteger((GameMain.NetLobbyScreen.MissionTypeIndex), 0, Enum.GetValues(typeof(MissionType)).Length - 1);
|
||||
outmsg.WriteRangedInteger((int)GameMain.NetLobbyScreen.MissionType, 0, (int)MissionType.All);
|
||||
|
||||
outmsg.Write((byte)GameMain.NetLobbyScreen.SelectedModeIndex);
|
||||
outmsg.Write(GameMain.NetLobbyScreen.LevelSeed);
|
||||
@@ -1807,7 +1819,7 @@ namespace Barotrauma.Networking
|
||||
//don't instantiate a new gamesession if we're playing a campaign
|
||||
if (campaign == null || GameMain.GameSession == null)
|
||||
{
|
||||
GameMain.GameSession = new GameSession(selectedSub, "", selectedMode, (MissionType)GameMain.NetLobbyScreen.MissionTypeIndex);
|
||||
GameMain.GameSession = new GameSession(selectedSub, "", selectedMode, GameMain.NetLobbyScreen.MissionType);
|
||||
}
|
||||
|
||||
List<Client> playingClients = new List<Client>(connectedClients);
|
||||
@@ -1875,9 +1887,13 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
//find the clients in this team
|
||||
List<Client> teamClients = teamCount == 1 ?
|
||||
new List<Client>(playingClients) :
|
||||
playingClients.FindAll(c => c.TeamID == teamID);
|
||||
List<Client> teamClients = teamCount == 1 ? new List<Client>(playingClients) : playingClients.FindAll(c => c.TeamID == teamID);
|
||||
if (serverSettings.AllowSpectating)
|
||||
{
|
||||
teamClients.RemoveAll(c => c.SpectateOnly);
|
||||
}
|
||||
//always allow the server owner to spectate even if it's disallowed in server settings
|
||||
teamClients.RemoveAll(c => c.Connection == OwnerConnection && c.SpectateOnly);
|
||||
|
||||
if (!teamClients.Any() && n > 0) { continue; }
|
||||
|
||||
@@ -1899,9 +1915,9 @@ namespace Barotrauma.Networking
|
||||
client.CharacterInfo = new CharacterInfo(Character.HumanSpeciesName, client.Name);
|
||||
}
|
||||
characterInfos.Add(client.CharacterInfo);
|
||||
if (client.CharacterInfo.Job == null || client.CharacterInfo.Job.Prefab != client.AssignedJob)
|
||||
if (client.CharacterInfo.Job == null || client.CharacterInfo.Job.Prefab != client.AssignedJob.First)
|
||||
{
|
||||
client.CharacterInfo.Job = new Job(client.AssignedJob);
|
||||
client.CharacterInfo.Job = new Job(client.AssignedJob.First, client.AssignedJob.Second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1909,7 +1925,10 @@ namespace Barotrauma.Networking
|
||||
int botsToSpawn = serverSettings.BotSpawnMode == BotSpawnMode.Fill ? serverSettings.BotCount - characterInfos.Count : serverSettings.BotCount;
|
||||
for (int i = 0; i < botsToSpawn; i++)
|
||||
{
|
||||
var botInfo = new CharacterInfo(Character.HumanSpeciesName);
|
||||
var botInfo = new CharacterInfo(Character.HumanSpeciesName)
|
||||
{
|
||||
TeamID = teamID
|
||||
};
|
||||
characterInfos.Add(botInfo);
|
||||
bots.Add(botInfo);
|
||||
}
|
||||
@@ -2016,7 +2035,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
msg.Write((byte)GameMain.Config.LosMode);
|
||||
|
||||
msg.Write((byte)GameMain.NetLobbyScreen.MissionTypeIndex);
|
||||
msg.Write((byte)GameMain.NetLobbyScreen.MissionType);
|
||||
|
||||
msg.Write(selectedSub.Name);
|
||||
msg.Write(selectedSub.MD5Hash.Hash);
|
||||
@@ -2088,7 +2107,7 @@ namespace Barotrauma.Networking
|
||||
GameMain.NetLobbyScreen.LastUpdateID++;
|
||||
}
|
||||
|
||||
if (serverSettings.SaveServerLogs) serverSettings.ServerLog.Save();
|
||||
if (serverSettings.SaveServerLogs) { serverSettings.ServerLog.Save(); }
|
||||
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
|
||||
@@ -2146,18 +2165,21 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
UInt16 nameId = inc.ReadUInt16();
|
||||
string newName = inc.ReadString();
|
||||
string newJob = inc.ReadString();
|
||||
|
||||
if (c == null || string.IsNullOrEmpty(newName) || !NetIdUtils.IdMoreRecent(nameId, c.NameID)) { return false; }
|
||||
|
||||
c.NameID = nameId;
|
||||
|
||||
newName = Client.SanitizeName(newName);
|
||||
if (newName == c.Name) { return false; }
|
||||
if (newName == c.Name && newJob == c.PreferredJob) { return false; }
|
||||
c.PreferredJob = newJob;
|
||||
|
||||
//update client list even if the name cannot be changed to the one sent by the client,
|
||||
//so the client will be informed what their actual name is
|
||||
LastClientListUpdateID++;
|
||||
|
||||
if (newName == c.Name) { return false; }
|
||||
|
||||
if (c.Connection != OwnerConnection)
|
||||
{
|
||||
if (!Client.IsValidName(newName, serverSettings))
|
||||
@@ -2261,7 +2283,7 @@ namespace Barotrauma.Networking
|
||||
if (client.Connection is LidgrenConnection lidgrenConn)
|
||||
{
|
||||
ip = lidgrenConn.IPEndPoint.Address.IsIPv4MappedToIPv6 ?
|
||||
lidgrenConn.IPEndPoint.Address.MapToIPv4().ToString() :
|
||||
lidgrenConn.IPEndPoint.Address.MapToIPv4NoThrow().ToString() :
|
||||
lidgrenConn.IPEndPoint.Address.ToString();
|
||||
if (range) { ip = serverSettings.BanList.ToRange(ip); }
|
||||
}
|
||||
@@ -2896,15 +2918,16 @@ namespace Barotrauma.Networking
|
||||
int moustacheIndex = message.ReadByte();
|
||||
int faceAttachmentIndex = message.ReadByte();
|
||||
|
||||
List<JobPrefab> jobPreferences = new List<JobPrefab>();
|
||||
List<Pair<JobPrefab, int>> jobPreferences = new List<Pair<JobPrefab, int>>();
|
||||
int count = message.ReadByte();
|
||||
// TODO: modding support?
|
||||
for (int i = 0; i < Math.Min(count, 3); i++)
|
||||
{
|
||||
string jobIdentifier = message.ReadString();
|
||||
int variant = message.ReadByte();
|
||||
if (JobPrefab.List.TryGetValue(jobIdentifier, out JobPrefab jobPrefab))
|
||||
{
|
||||
jobPreferences.Add(jobPrefab);
|
||||
jobPreferences.Add(new Pair<JobPrefab, int>(jobPrefab, variant));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2923,6 +2946,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
var jobList = JobPrefab.List.Values.ToList();
|
||||
unassigned = new List<Client>(unassigned);
|
||||
unassigned = unassigned.OrderBy(sp => Rand.Int(int.MaxValue)).ToList();
|
||||
|
||||
Dictionary<JobPrefab, int> assignedClientCount = new Dictionary<JobPrefab, int>();
|
||||
foreach (JobPrefab jp in jobList)
|
||||
@@ -2944,14 +2968,14 @@ namespace Barotrauma.Networking
|
||||
foreach (KeyValuePair<Client, Job> clientJob in campaignAssigned)
|
||||
{
|
||||
assignedClientCount[clientJob.Value.Prefab]++;
|
||||
clientJob.Key.AssignedJob = clientJob.Value.Prefab;
|
||||
clientJob.Key.AssignedJob = new Pair<JobPrefab, int>(clientJob.Value.Prefab, clientJob.Value.Variant);
|
||||
}
|
||||
}
|
||||
|
||||
//count the clients who already have characters with an assigned job
|
||||
foreach (Client c in connectedClients)
|
||||
{
|
||||
if (c.TeamID != teamID || unassigned.Contains(c)) continue;
|
||||
if (c.TeamID != teamID || unassigned.Contains(c)) { continue; }
|
||||
if (c.Character?.Info?.Job != null && !c.Character.IsDead)
|
||||
{
|
||||
assignedClientCount[c.Character.Info.Job.Prefab]++;
|
||||
@@ -2961,8 +2985,8 @@ namespace Barotrauma.Networking
|
||||
//if any of the players has chosen a job that is Always Allowed, give them that job
|
||||
for (int i = unassigned.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (unassigned[i].JobPreferences.Count == 0) continue;
|
||||
if (!unassigned[i].JobPreferences[0].AllowAlways) continue;
|
||||
if (unassigned[i].JobPreferences.Count == 0) { continue; }
|
||||
if (!unassigned[i].JobPreferences[0].First.AllowAlways) { continue; }
|
||||
unassigned[i].AssignedJob = unassigned[i].JobPreferences[0];
|
||||
unassigned.RemoveAt(i);
|
||||
}
|
||||
@@ -2975,32 +2999,75 @@ namespace Barotrauma.Networking
|
||||
|
||||
foreach (JobPrefab jobPrefab in jobList)
|
||||
{
|
||||
if (unassigned.Count == 0) break;
|
||||
if (jobPrefab.MinNumber < 1 || assignedClientCount[jobPrefab] >= jobPrefab.MinNumber) continue;
|
||||
if (unassigned.Count == 0) { break; }
|
||||
if (jobPrefab.MinNumber < 1 || assignedClientCount[jobPrefab] >= jobPrefab.MinNumber) { continue; }
|
||||
|
||||
//find the client that wants the job the most, or force it to random client if none of them want it
|
||||
Client assignedClient = FindClientWithJobPreference(unassigned, jobPrefab, true);
|
||||
|
||||
assignedClient.AssignedJob = jobPrefab;
|
||||
assignedClient.AssignedJob =
|
||||
assignedClient.JobPreferences.FirstOrDefault(jp => jp.First == jobPrefab) ??
|
||||
new Pair<JobPrefab, int>(jobPrefab, 0);
|
||||
|
||||
assignedClientCount[jobPrefab]++;
|
||||
unassigned.Remove(assignedClient);
|
||||
|
||||
//the job still needs more crew members, set unassignedJobsFound to true to keep the while loop running
|
||||
if (assignedClientCount[jobPrefab] < jobPrefab.MinNumber) unassignedJobsFound = true;
|
||||
if (assignedClientCount[jobPrefab] < jobPrefab.MinNumber) { unassignedJobsFound = true; }
|
||||
}
|
||||
}
|
||||
|
||||
List<WayPoint> availableSpawnPoints = WayPoint.WayPointList.FindAll(wp =>
|
||||
wp.SpawnType == SpawnType.Human &&
|
||||
wp.Submarine != null && wp.Submarine.TeamID == teamID);
|
||||
List<WayPoint> unassignedSpawnPoints = new List<WayPoint>(availableSpawnPoints);
|
||||
|
||||
/*bool canAssign = false;
|
||||
do
|
||||
{
|
||||
canAssign = false;
|
||||
foreach (WayPoint spawnPoint in unassignedSpawnPoints)
|
||||
{
|
||||
if (unassigned.Count == 0) { break; }
|
||||
|
||||
JobPrefab job = spawnPoint.AssignedJob ?? JobPrefab.List.Values.GetRandom();
|
||||
if (assignedClientCount[job] >= job.MaxNumber) { continue; }
|
||||
|
||||
Client assignedClient = FindClientWithJobPreference(unassigned, job, true);
|
||||
if (assignedClient != null)
|
||||
{
|
||||
assignedClient.AssignedJob = job;
|
||||
assignedClientCount[job]++;
|
||||
unassigned.Remove(assignedClient);
|
||||
canAssign = true;
|
||||
}
|
||||
}
|
||||
} while (unassigned.Count > 0 && canAssign);*/
|
||||
|
||||
//attempt to give the clients a job they have in their job preferences
|
||||
for (int i = unassigned.Count - 1; i >= 0; i--)
|
||||
{
|
||||
foreach (JobPrefab preferredJob in unassigned[i].JobPreferences)
|
||||
if (unassignedSpawnPoints.Count == 0) { break; }
|
||||
foreach (Pair<JobPrefab, int> preferredJob in unassigned[i].JobPreferences)
|
||||
{
|
||||
//the maximum number of players that can have this job hasn't been reached yet
|
||||
// -> assign it to the client
|
||||
if (assignedClientCount[preferredJob] < preferredJob.MaxNumber && unassigned[i].Karma >= preferredJob.MinKarma)
|
||||
//can't assign this job if maximum number has reached or the clien't karma is too low
|
||||
if (assignedClientCount[preferredJob.First] >= preferredJob.First.MaxNumber || unassigned[i].Karma < preferredJob.First.MinKarma)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//give the client their preferred job if there's a spawnpoint available for that job
|
||||
var matchingSpawnPoint = unassignedSpawnPoints.Find(s => s.AssignedJob == preferredJob.First);
|
||||
//if the job is not available in any spawnpoint (custom job?), treat empty spawnpoints
|
||||
//as a matching ones
|
||||
if (matchingSpawnPoint == null && !availableSpawnPoints.Any(s => s.AssignedJob == preferredJob.First))
|
||||
{
|
||||
matchingSpawnPoint = unassignedSpawnPoints.Find(s => s.AssignedJob == null);
|
||||
}
|
||||
if (matchingSpawnPoint != null)
|
||||
{
|
||||
unassignedSpawnPoints.Remove(matchingSpawnPoint);
|
||||
unassigned[i].AssignedJob = preferredJob;
|
||||
assignedClientCount[preferredJob]++;
|
||||
assignedClientCount[preferredJob.First]++;
|
||||
unassigned.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
@@ -3023,25 +3090,36 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
jobIndex++;
|
||||
skips++;
|
||||
if (jobIndex >= jobList.Count) jobIndex -= jobList.Count;
|
||||
if (skips >= jobList.Count) break;
|
||||
if (jobIndex >= jobList.Count) { jobIndex -= jobList.Count; }
|
||||
if (skips >= jobList.Count) { break; }
|
||||
}
|
||||
c.AssignedJob = jobList[jobIndex];
|
||||
assignedClientCount[c.AssignedJob]++;
|
||||
c.AssignedJob =
|
||||
c.JobPreferences.FirstOrDefault(jp => jp.First == jobList[jobIndex]) ??
|
||||
new Pair<JobPrefab, int>(jobList[jobIndex], 0);
|
||||
assignedClientCount[c.AssignedJob.First]++;
|
||||
}
|
||||
else //some jobs still left, choose one of them by random
|
||||
//if one of the client's preferences is still available, give them that job
|
||||
else if (c.JobPreferences.Any(jp => remainingJobs.Contains(jp.First)))
|
||||
{
|
||||
c.AssignedJob = remainingJobs[Rand.Range(0, remainingJobs.Count)];
|
||||
assignedClientCount[c.AssignedJob]++;
|
||||
foreach (Pair<JobPrefab, int> preferredJob in c.JobPreferences)
|
||||
{
|
||||
c.AssignedJob = preferredJob;
|
||||
assignedClientCount[preferredJob.First]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else //none of the client's preferred jobs available, choose a random job
|
||||
{
|
||||
c.AssignedJob = new Pair<JobPrefab, int>(remainingJobs[Rand.Range(0, remainingJobs.Count)], 0);
|
||||
assignedClientCount[c.AssignedJob.First]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AssignBotJobs(List<CharacterInfo> bots, Character.TeamType teamID)
|
||||
{
|
||||
var jobList = JobPrefab.List.Values.ToList();
|
||||
Dictionary<JobPrefab, int> assignedPlayerCount = new Dictionary<JobPrefab, int>();
|
||||
foreach (JobPrefab jp in jobList)
|
||||
foreach (JobPrefab jp in JobPrefab.List.Values)
|
||||
{
|
||||
assignedPlayerCount.Add(jp, 0);
|
||||
}
|
||||
@@ -3061,25 +3139,39 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
List<CharacterInfo> unassignedBots = new List<CharacterInfo>(bots);
|
||||
foreach (CharacterInfo bot in bots)
|
||||
{
|
||||
foreach (JobPrefab jobPrefab in jobList)
|
||||
{
|
||||
if (jobPrefab.MinNumber < 1 || assignedPlayerCount[jobPrefab] >= jobPrefab.MinNumber) continue;
|
||||
bot.Job = new Job(jobPrefab);
|
||||
assignedPlayerCount[jobPrefab]++;
|
||||
unassignedBots.Remove(bot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//find a suitable job for the rest of the players
|
||||
List<WayPoint> spawnPoints = WayPoint.WayPointList.FindAll(wp =>
|
||||
wp.SpawnType == SpawnType.Human &&
|
||||
wp.Submarine != null && wp.Submarine.TeamID == teamID)
|
||||
.OrderBy(sp => Rand.Int(int.MaxValue))
|
||||
.OrderBy(sp => sp.AssignedJob == null ? 0 : 1)
|
||||
.ToList();
|
||||
|
||||
bool canAssign = false;
|
||||
do
|
||||
{
|
||||
canAssign = false;
|
||||
foreach (WayPoint spawnPoint in spawnPoints)
|
||||
{
|
||||
if (unassignedBots.Count == 0) { break; }
|
||||
|
||||
JobPrefab jobPrefab = spawnPoint.AssignedJob ?? JobPrefab.List.Values.GetRandom();
|
||||
if (assignedPlayerCount[jobPrefab] >= jobPrefab.MaxNumber) { continue; }
|
||||
|
||||
unassignedBots[0].Job = new Job(jobPrefab);
|
||||
assignedPlayerCount[jobPrefab]++;
|
||||
unassignedBots.Remove(unassignedBots[0]);
|
||||
canAssign = true;
|
||||
}
|
||||
} while (unassignedBots.Count > 0 && canAssign);
|
||||
|
||||
//find a suitable job for the rest of the bots
|
||||
foreach (CharacterInfo c in unassignedBots)
|
||||
{
|
||||
//find all jobs that are still available
|
||||
var remainingJobs = jobList.FindAll(jp => assignedPlayerCount[jp] < jp.MaxNumber);
|
||||
var remainingJobs = JobPrefab.List.Values.Where(jp => assignedPlayerCount[jp] < jp.MaxNumber);
|
||||
//all jobs taken, give a random job
|
||||
if (remainingJobs.Count == 0)
|
||||
if (remainingJobs.Count() == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to assign a suitable job for bot \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
|
||||
c.Job = Job.Random();
|
||||
@@ -3087,7 +3179,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else //some jobs still left, choose one of them by random
|
||||
{
|
||||
c.Job = new Job(remainingJobs[Rand.Range(0, remainingJobs.Count)]);
|
||||
c.Job = new Job(remainingJobs.GetRandom());
|
||||
assignedPlayerCount[c.Job.Prefab]++;
|
||||
}
|
||||
}
|
||||
@@ -3100,7 +3192,7 @@ namespace Barotrauma.Networking
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
if (c.Karma < job.MinKarma) continue;
|
||||
int index = c.JobPreferences.IndexOf(job);
|
||||
int index = c.JobPreferences.IndexOf(c.JobPreferences.Find(j => j.First == job));
|
||||
if (index == -1) index = 1000;
|
||||
|
||||
if (preferredClient == null || index < bestPreference)
|
||||
@@ -3119,6 +3211,17 @@ namespace Barotrauma.Networking
|
||||
return preferredClient;
|
||||
}
|
||||
|
||||
public void UpdateMissionState(int state)
|
||||
{
|
||||
foreach (var client in connectedClients)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ServerPacketHeader.MISSION);
|
||||
msg.Write((ushort)state);
|
||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Log(string line, ServerLog.MessageType messageType)
|
||||
{
|
||||
if (GameMain.Server == null || !GameMain.Server.ServerSettings.SaveServerLogs) return;
|
||||
@@ -3152,6 +3255,9 @@ namespace Barotrauma.Networking
|
||||
started = false;
|
||||
|
||||
serverSettings.BanList.Save();
|
||||
|
||||
if (GameMain.NetLobbyScreen.SelectedSub != null) { serverSettings.SelectedSubmarine = GameMain.NetLobbyScreen.SelectedSub.Name; }
|
||||
if (GameMain.NetLobbyScreen.SelectedShuttle != null) { serverSettings.SelectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle.Name; }
|
||||
serverSettings.SaveSettings();
|
||||
|
||||
if (registeredToMaster)
|
||||
|
||||
+36
-34
@@ -342,7 +342,7 @@ namespace Barotrauma.Networking
|
||||
if (!Client.IsValidName(name, serverSettings))
|
||||
{
|
||||
if (OwnerConnection != null ||
|
||||
!IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4()) &&
|
||||
!IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4NoThrow()) &&
|
||||
ownerKey == null || ownKey == 0 && ownKey != ownerKey)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "The name \"" + name + "\" is invalid");
|
||||
@@ -362,36 +362,37 @@ namespace Barotrauma.Networking
|
||||
return;
|
||||
}
|
||||
|
||||
Int32 contentPackageCount = inc.ReadVariableInt32();
|
||||
List<ClientContentPackage> contentPackages = new List<ClientContentPackage>();
|
||||
int contentPackageCount = inc.ReadVariableInt32();
|
||||
List<ClientContentPackage> clientContentPackages = new List<ClientContentPackage>();
|
||||
for (int i = 0; i < contentPackageCount; i++)
|
||||
{
|
||||
string packageName = inc.ReadString();
|
||||
string packageHash = inc.ReadString();
|
||||
contentPackages.Add(new ClientContentPackage(packageName, packageHash));
|
||||
clientContentPackages.Add(new ClientContentPackage(packageName, packageHash));
|
||||
}
|
||||
|
||||
//check if the client is missing any of our packages
|
||||
List<ContentPackage> missingPackages = new List<ContentPackage>();
|
||||
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
|
||||
foreach (ContentPackage serverContentPackage in GameMain.SelectedPackages)
|
||||
{
|
||||
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
|
||||
bool packageFound = false;
|
||||
for (int i = 0; i < contentPackageCount; i++)
|
||||
{
|
||||
if (contentPackages[i].Name == contentPackage.Name && contentPackages[i].Hash == contentPackage.MD5hash.Hash)
|
||||
{
|
||||
packageFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!packageFound) missingPackages.Add(contentPackage);
|
||||
if (!serverContentPackage.HasMultiplayerIncompatibleContent) continue;
|
||||
bool packageFound = clientContentPackages.Any(cp => cp.Name == serverContentPackage.Name && cp.Hash == serverContentPackage.MD5hash.Hash);
|
||||
if (!packageFound) { missingPackages.Add(serverContentPackage); }
|
||||
}
|
||||
|
||||
//check if the client is using packages we don't have
|
||||
List<ClientContentPackage> redundantPackages = new List<ClientContentPackage>();
|
||||
foreach (ClientContentPackage clientContentPackage in clientContentPackages)
|
||||
{
|
||||
bool packageFound = GameMain.SelectedPackages.Any(cp => cp.Name == clientContentPackage.Name && cp.MD5hash.Hash == clientContentPackage.Hash);
|
||||
if (!packageFound) { redundantPackages.Add(clientContentPackage); }
|
||||
}
|
||||
|
||||
if (missingPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
else if (missingPackages.Count > 1)
|
||||
@@ -400,7 +401,23 @@ namespace Barotrauma.Networking
|
||||
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr(redundantPackages[0])}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (using an incompatible content package " + GetPackageStr(redundantPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count > 1)
|
||||
{
|
||||
List<string> packageStrs = new List<string>();
|
||||
redundantPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (using incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -476,21 +493,6 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
protected struct ClientContentPackage
|
||||
{
|
||||
public string Name;
|
||||
public string Hash;
|
||||
|
||||
public ClientContentPackage(string name, string hash)
|
||||
{
|
||||
Name = name; Hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPackageStr(ContentPackage contentPackage)
|
||||
{
|
||||
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
|
||||
}
|
||||
|
||||
private void UpdatePendingClient(PendingClient pendingClient, float deltaTime)
|
||||
{
|
||||
@@ -519,7 +521,7 @@ namespace Barotrauma.Networking
|
||||
pendingClients.Remove(pendingClient);
|
||||
|
||||
if (OwnerConnection == null &&
|
||||
IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4()) &&
|
||||
IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4NoThrow()) &&
|
||||
ownerKey != null && pendingClient.OwnerKey != 0 && pendingClient.OwnerKey == ownerKey)
|
||||
{
|
||||
ownerKey = null;
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
using Facepunch.Steamworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class ServerPeer
|
||||
{
|
||||
protected struct ClientContentPackage
|
||||
{
|
||||
public string Name;
|
||||
public string Hash;
|
||||
|
||||
public ClientContentPackage(string name, string hash)
|
||||
{
|
||||
Name = name; Hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetPackageStr(ContentPackage contentPackage)
|
||||
{
|
||||
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
|
||||
}
|
||||
protected string GetPackageStr(ClientContentPackage contentPackage)
|
||||
{
|
||||
return "\"" + contentPackage.Name + "\" (hash " + Md5Hash.GetShortHash(contentPackage.Hash) + ")";
|
||||
}
|
||||
|
||||
public delegate void MessageCallback(NetworkConnection connection, IReadMessage message);
|
||||
public delegate void DisconnectCallback(NetworkConnection connection, string reason);
|
||||
public delegate void InitializationCompleteCallback(NetworkConnection connection);
|
||||
@@ -28,6 +49,8 @@ namespace Barotrauma.Networking
|
||||
public abstract void Start();
|
||||
public abstract void Close(string msg = null);
|
||||
public abstract void Update(float deltaTime);
|
||||
|
||||
|
||||
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod);
|
||||
public abstract void Disconnect(NetworkConnection conn, string msg = null);
|
||||
}
|
||||
|
||||
+34
-32
@@ -200,7 +200,7 @@ namespace Barotrauma.Networking
|
||||
return;
|
||||
}
|
||||
|
||||
if (IPAddress.IsLoopback(inc.SenderConnection.RemoteEndPoint.Address.MapToIPv4()))
|
||||
if (IPAddress.IsLoopback(inc.SenderConnection.RemoteEndPoint.Address.MapToIPv4NoThrow()))
|
||||
{
|
||||
inc.SenderConnection.Approve();
|
||||
netConnection = inc.SenderConnection;
|
||||
@@ -403,35 +403,36 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
int contentPackageCount = (int)inc.ReadVariableUInt32();
|
||||
List<ClientContentPackage> contentPackages = new List<ClientContentPackage>();
|
||||
List<ClientContentPackage> clientContentPackages = new List<ClientContentPackage>();
|
||||
for (int i = 0; i < contentPackageCount; i++)
|
||||
{
|
||||
string packageName = inc.ReadString();
|
||||
string packageHash = inc.ReadString();
|
||||
contentPackages.Add(new ClientContentPackage(packageName, packageHash));
|
||||
clientContentPackages.Add(new ClientContentPackage(packageName, packageHash));
|
||||
}
|
||||
|
||||
//check if the client is missing any of our packages
|
||||
List<ContentPackage> missingPackages = new List<ContentPackage>();
|
||||
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
|
||||
foreach (ContentPackage serverContentPackage in GameMain.SelectedPackages)
|
||||
{
|
||||
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
|
||||
bool packageFound = false;
|
||||
for (int i = 0; i < (int)contentPackageCount; i++)
|
||||
{
|
||||
if (contentPackages[i].Name == contentPackage.Name && contentPackages[i].Hash == contentPackage.MD5hash.Hash)
|
||||
{
|
||||
packageFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!packageFound) missingPackages.Add(contentPackage);
|
||||
if (!serverContentPackage.HasMultiplayerIncompatibleContent) continue;
|
||||
bool packageFound = clientContentPackages.Any(cp => cp.Name == serverContentPackage.Name && cp.Hash == serverContentPackage.MD5hash.Hash);
|
||||
if (!packageFound) { missingPackages.Add(serverContentPackage); }
|
||||
}
|
||||
|
||||
//check if the client is using packages we don't have
|
||||
List<ClientContentPackage> redundantPackages = new List<ClientContentPackage>();
|
||||
foreach (ClientContentPackage clientContentPackage in clientContentPackages)
|
||||
{
|
||||
bool packageFound = GameMain.SelectedPackages.Any(cp => cp.Name == clientContentPackage.Name && cp.MD5hash.Hash == clientContentPackage.Hash);
|
||||
if (!packageFound) { redundantPackages.Add(clientContentPackage); }
|
||||
}
|
||||
|
||||
if (missingPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
else if (missingPackages.Count > 1)
|
||||
@@ -440,7 +441,23 @@ namespace Barotrauma.Networking
|
||||
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr(redundantPackages[0])}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (using an incompatible content package " + GetPackageStr(redundantPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count > 1)
|
||||
{
|
||||
List<string> packageStrs = new List<string>();
|
||||
redundantPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (using incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -482,21 +499,6 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
protected struct ClientContentPackage
|
||||
{
|
||||
public string Name;
|
||||
public string Hash;
|
||||
|
||||
public ClientContentPackage(string name, string hash)
|
||||
{
|
||||
Name = name; Hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPackageStr(ContentPackage contentPackage)
|
||||
{
|
||||
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
|
||||
}
|
||||
|
||||
private void UpdatePendingClient(PendingClient pendingClient)
|
||||
{
|
||||
|
||||
@@ -235,7 +235,7 @@ namespace Barotrauma.Networking
|
||||
GameMain.Server.AssignJobs(clients);
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
c.CharacterInfo.Job = new Job(c.AssignedJob);
|
||||
c.CharacterInfo.Job = new Job(c.AssignedJob.First, c.AssignedJob.Second);
|
||||
}
|
||||
|
||||
//the spawnpoints where the characters will spawn
|
||||
|
||||
@@ -128,10 +128,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (flags.HasFlag(NetFlags.Misc))
|
||||
{
|
||||
int missionType = GameMain.NetLobbyScreen.MissionTypeIndex + incMsg.ReadByte() - 1;
|
||||
while (missionType < 0) missionType += Enum.GetValues(typeof(MissionType)).Length;
|
||||
while (missionType >= Enum.GetValues(typeof(MissionType)).Length) missionType -= Enum.GetValues(typeof(MissionType)).Length;
|
||||
GameMain.NetLobbyScreen.MissionTypeIndex = missionType;
|
||||
int orBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
int andBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
GameMain.NetLobbyScreen.MissionType = (Barotrauma.MissionType)(((int)GameMain.NetLobbyScreen.MissionType | orBits) & andBits);
|
||||
|
||||
int traitorSetting = (int)TraitorsEnabled + incMsg.ReadByte() - 1;
|
||||
if (traitorSetting < 0) traitorSetting = 2;
|
||||
@@ -310,6 +309,9 @@ namespace Barotrauma.Networking
|
||||
ServerMessageText = doc.Root.GetAttributeString("ServerMessage", "");
|
||||
|
||||
GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier;
|
||||
//handle Random as the mission type, which is no longer a valid setting
|
||||
//MissionType.All offers equivalent functionality
|
||||
if (MissionType == "Random") { MissionType = "All"; }
|
||||
GameMain.NetLobbyScreen.MissionTypeName = MissionType;
|
||||
|
||||
GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode);
|
||||
|
||||
@@ -62,6 +62,8 @@ namespace Barotrauma.Steam
|
||||
Instance.server.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
|
||||
Instance.server.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
|
||||
Instance.server.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString());
|
||||
Instance.server.SetKey("karmaenabled", server.ServerSettings.KarmaEnabled.ToString());
|
||||
Instance.server.SetKey("friendlyfireenabled", server.ServerSettings.AllowFriendlyFire.ToString());
|
||||
Instance.server.SetKey("allowspectating", server.ServerSettings.AllowSpectating.ToString());
|
||||
Instance.server.SetKey("allowrespawn", server.ServerSettings.AllowRespawn.ToString());
|
||||
Instance.server.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString());
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Barotrauma.Networking
|
||||
if (wlp == null) return false;
|
||||
if (!string.IsNullOrWhiteSpace(wlp.IP))
|
||||
{
|
||||
if (address.IsIPv4MappedToIPv6 && wlp.IP == address.MapToIPv4().ToString())
|
||||
if (address.IsIPv4MappedToIPv6 && wlp.IP == address.MapToIPv4NoThrow().ToString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
{
|
||||
GameMain game = null;
|
||||
|
||||
#if !DEBUG
|
||||
#if !DEBUG || TRUE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
@@ -49,7 +49,7 @@ namespace Barotrauma
|
||||
DebugConsole.InputThread?.Abort(); DebugConsole.InputThread?.Join();
|
||||
if (GameSettings.SendUserStatistics) GameAnalytics.OnQuit();
|
||||
SteamManager.ShutDown();
|
||||
#if !DEBUG
|
||||
#if !DEBUG || TRUE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -74,26 +74,28 @@ namespace Barotrauma
|
||||
get { return GameModes[SelectedModeIndex]; }
|
||||
}
|
||||
|
||||
private int missionTypeIndex;
|
||||
public int MissionTypeIndex
|
||||
private MissionType missionType;
|
||||
public MissionType MissionType
|
||||
{
|
||||
get { return missionTypeIndex; }
|
||||
get { return missionType; }
|
||||
set
|
||||
{
|
||||
lastUpdateID++;
|
||||
missionTypeIndex = MathHelper.Clamp(value, 0, Enum.GetValues(typeof(MissionType)).Length - 1);
|
||||
missionType = value;
|
||||
if (GameMain.NetworkMember?.ServerSettings != null)
|
||||
{
|
||||
GameMain.NetworkMember.ServerSettings.MissionType = missionType.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string MissionTypeName
|
||||
{
|
||||
get { return ((MissionType)missionTypeIndex).ToString(); }
|
||||
get { return missionType.ToString(); }
|
||||
set
|
||||
{
|
||||
if (Enum.TryParse(value, out MissionType missionType))
|
||||
{
|
||||
missionTypeIndex = (int)missionType;
|
||||
}
|
||||
Enum.TryParse(value, out MissionType type);
|
||||
MissionType = type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,13 @@ namespace Barotrauma
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
// Quick fix
|
||||
if (tagPrefabName == null && matchIdentifier)
|
||||
{
|
||||
tagPrefabName = TextManager.FormatServerMessage($"entityname.{tag}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public sealed class GoalEntityTransformation : Goal
|
||||
{
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[catalystitem]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { catalystItemName });
|
||||
|
||||
private bool isCompleted;
|
||||
public override bool IsCompleted => isCompleted;
|
||||
|
||||
private string catalystItemIdentifier, catalystItemName;
|
||||
|
||||
private Vector2 activeEntitySavedPosition;
|
||||
private Entity activeEntity;
|
||||
private int activeEntityIndex;
|
||||
private const float gracePeriod = 1f;
|
||||
private const float graceDistance = 200f;
|
||||
private float graceTimer;
|
||||
private double transformationTime;
|
||||
|
||||
private enum EntityTypes { Character, Item }
|
||||
|
||||
private string[] entities;
|
||||
private EntityTypes[] entityTypes;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
isCompleted = HasTransformed(deltaTime);
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted(ICollection<Traitor> traitors)
|
||||
{
|
||||
return graceTimer <= gracePeriod;
|
||||
}
|
||||
|
||||
private bool HasTransformed(float deltaTime)
|
||||
{
|
||||
if (activeEntity != null && !activeEntity.Removed)
|
||||
{
|
||||
activeEntitySavedPosition = activeEntity.WorldPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transformationTime == 0)
|
||||
{
|
||||
graceTimer = 0.0f;
|
||||
activeEntityIndex++;
|
||||
transformationTime = Timing.TotalTime;
|
||||
}
|
||||
graceTimer += deltaTime;
|
||||
|
||||
switch (entityTypes[activeEntityIndex])
|
||||
{
|
||||
case EntityTypes.Character:
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.Submarine == null || Traitors.All(t => character.Submarine.TeamID != t.Character.TeamID) || character.SpawnTime + gracePeriod < transformationTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (character.SpeciesName.ToLowerInvariant() == entities[activeEntityIndex] && Vector2.Distance(activeEntitySavedPosition, character.WorldPosition) < graceDistance)
|
||||
{
|
||||
activeEntity = character;
|
||||
transformationTime = 0.0;
|
||||
return activeEntityIndex == entities.Length - 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EntityTypes.Item:
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID) || item.SpawnTime + gracePeriod < transformationTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.prefab.Identifier == entities[activeEntityIndex] && Vector2.Distance(activeEntitySavedPosition, item.WorldPosition) < graceDistance)
|
||||
{
|
||||
activeEntity = item;
|
||||
transformationTime = 0.0;
|
||||
return activeEntityIndex == entities.Length - 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
{
|
||||
if (!base.Start(traitor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
catalystItemName = TextManager.FormatServerMessage($"entityname.{catalystItemIdentifier}");
|
||||
|
||||
activeEntity = null;
|
||||
activeEntityIndex = 0;
|
||||
|
||||
switch (entityTypes[activeEntityIndex])
|
||||
{
|
||||
case EntityTypes.Character:
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.Submarine == null || Traitors.All(t => character.Submarine.TeamID != t.Character.TeamID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (character.SpeciesName.ToLowerInvariant() == entities[activeEntityIndex].ToLowerInvariant())
|
||||
{
|
||||
activeEntity = character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EntityTypes.Item:
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.prefab.Identifier.ToLowerInvariant() == entities[0].ToLowerInvariant())
|
||||
{
|
||||
activeEntity = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
graceTimer = 0.0f;
|
||||
return activeEntity != null;
|
||||
}
|
||||
|
||||
public GoalEntityTransformation(string[] entities, string[] entityTypes, string catalystItemIdentifier) : base()
|
||||
{
|
||||
this.entities = entities;
|
||||
|
||||
this.entityTypes = new EntityTypes[entityTypes.Length];
|
||||
|
||||
for (int i = 0; i < this.entityTypes.Length; i++)
|
||||
{
|
||||
this.entityTypes[i] = (EntityTypes)Enum.Parse(typeof(EntityTypes), entityTypes[i], true);
|
||||
}
|
||||
|
||||
this.catalystItemIdentifier = catalystItemIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -9,6 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
public class GoalFindItem : HumanoidGoal
|
||||
{
|
||||
private readonly TraitorMission.CharacterFilter filter;
|
||||
private readonly string identifier;
|
||||
private readonly bool preferNew;
|
||||
private readonly bool allowNew;
|
||||
@@ -16,12 +18,17 @@ namespace Barotrauma
|
||||
private readonly HashSet<string> allowedContainerIdentifiers = new HashSet<string>();
|
||||
|
||||
private ItemPrefab targetPrefab;
|
||||
private ItemPrefab containedPrefab;
|
||||
private Item targetContainer;
|
||||
private Item target;
|
||||
private HashSet<Item> existingItems = new HashSet<Item>();
|
||||
private string targetNameText;
|
||||
private string targetContainerNameText;
|
||||
private string targetHullNameText;
|
||||
private float percentage;
|
||||
private int spawnAmount = 1;
|
||||
|
||||
private const string itemContainerId = "toolbox";
|
||||
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[identifier]", "[target]", "[targethullname]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { targetNameText ?? "", targetContainerNameText ?? "", targetHullNameText ?? "" });
|
||||
@@ -85,7 +92,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (suitableItems.Count == 0) { return null; }
|
||||
return suitableItems[TraitorMission.Random(suitableItems.Count)];
|
||||
return suitableItems[TraitorManager.RandomInt(suitableItems.Count)];
|
||||
}
|
||||
|
||||
protected Item FindTargetContainer(ICollection<Traitor> traitors, ItemPrefab targetPrefabCandidate)
|
||||
@@ -124,12 +131,40 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
targetPrefab = FindItemPrefab(identifier);
|
||||
if (targetPrefab == null)
|
||||
|
||||
string targetPrefabTextId;
|
||||
|
||||
if (percentage > 0f)
|
||||
{
|
||||
return false;
|
||||
spawnAmount = (int)Math.Floor(Character.CharacterList.FindAll(c => c.TeamID == traitor.Character.TeamID && c != traitor.Character && !c.IsDead && (filter == null || filter(c))).Count * percentage);
|
||||
}
|
||||
var targetPrefabTextId = targetPrefab.GetItemNameTextId();
|
||||
|
||||
if (spawnAmount > 1 && allowNew)
|
||||
{
|
||||
containedPrefab = FindItemPrefab(identifier);
|
||||
targetPrefab = FindItemPrefab(itemContainerId);
|
||||
|
||||
if (containedPrefab == null || targetPrefab == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
targetPrefabTextId = containedPrefab.GetItemNameTextId();
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnAmount = 1;
|
||||
containedPrefab = null;
|
||||
targetPrefab = FindItemPrefab(identifier);
|
||||
|
||||
if (targetPrefab == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
targetPrefabTextId = targetPrefab.GetItemNameTextId();
|
||||
}
|
||||
|
||||
targetNameText = targetPrefabTextId != null ? TextManager.FormatServerMessage(targetPrefabTextId) : targetPrefab.Name;
|
||||
targetContainer = FindTargetContainer(Traitors, targetPrefab);
|
||||
if (targetContainer == null)
|
||||
@@ -170,20 +205,29 @@ namespace Barotrauma
|
||||
base.Update(deltaTime);
|
||||
if (target == null)
|
||||
{
|
||||
target = targetContainer.OwnInventory.Items.FirstOrDefault(item => item != null && item.Prefab.Identifier == identifier && !existingItems.Contains(item));
|
||||
target = targetContainer.OwnInventory.Items.FirstOrDefault(item => item != null && item.Prefab.Identifier == (containedPrefab != null ? itemContainerId : identifier) && !existingItems.Contains(item));
|
||||
if (target != null)
|
||||
{
|
||||
if (containedPrefab != null)
|
||||
{
|
||||
for (int i = 0; i < spawnAmount; i++)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(containedPrefab, target.OwnInventory);
|
||||
}
|
||||
}
|
||||
existingItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GoalFindItem(string identifier, bool preferNew, bool allowNew, bool allowExisting, params string[] allowedContainerIdentifiers)
|
||||
public GoalFindItem(TraitorMission.CharacterFilter filter, string identifier, bool preferNew, bool allowNew, bool allowExisting, float percentage, params string[] allowedContainerIdentifiers)
|
||||
{
|
||||
this.filter = filter;
|
||||
this.identifier = identifier;
|
||||
this.preferNew = preferNew;
|
||||
this.allowNew = allowNew;
|
||||
this.allowExisting = allowExisting;
|
||||
this.percentage = percentage / 100f;
|
||||
this.allowedContainerIdentifiers.UnionWith(allowedContainerIdentifiers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public sealed class GoalInjectTarget : Goal
|
||||
{
|
||||
public TraitorMission.CharacterFilter Filter { get; private set; }
|
||||
public List<Character> Targets { get; private set; }
|
||||
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]", "[poison]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { traitor.Mission.GetTargetNames(Targets) ?? "(unknown)", poisonName });
|
||||
|
||||
private bool isCompleted = false;
|
||||
public override bool IsCompleted => isCompleted;
|
||||
|
||||
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && Targets.Contains(character));
|
||||
|
||||
private string poisonId;
|
||||
private string afflictionId;
|
||||
private string poisonName;
|
||||
private int targetCount;
|
||||
private float targetPercentage;
|
||||
private bool[] targetWasInfected;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
isCompleted = WereAllTargetsInfected();
|
||||
}
|
||||
|
||||
private bool WereAllTargetsInfected()
|
||||
{
|
||||
for (int i = 0; i < targetWasInfected.Length; i++)
|
||||
{
|
||||
if (targetWasInfected[i]) continue;
|
||||
targetWasInfected[i] = Targets[i].CharacterHealth.GetAffliction(afflictionId) != null;
|
||||
}
|
||||
|
||||
return targetWasInfected.All(t => t == true);
|
||||
}
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
{
|
||||
if (!base.Start(traitor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
poisonName = TextManager.FormatServerMessage(poisonId) ?? poisonId;
|
||||
|
||||
Targets = traitor.Mission.FindKillTarget(traitor.Character, Filter, targetCount, targetPercentage);
|
||||
targetWasInfected = new bool[Targets.Count];
|
||||
return Targets != null && !Targets.All(t => t.IsDead);
|
||||
}
|
||||
|
||||
public GoalInjectTarget(TraitorMission.CharacterFilter filter, string poisonId, string afflictionId, int targetCount, float targetPercentage) : base()
|
||||
{
|
||||
Filter = filter;
|
||||
this.poisonId = poisonId;
|
||||
this.afflictionId = afflictionId;
|
||||
this.targetCount = targetCount;
|
||||
this.targetPercentage = targetPercentage / 100f;
|
||||
|
||||
if (this.targetPercentage < 1.0f)
|
||||
{
|
||||
InfoTextId = "traitorgoalpoisoninfo";
|
||||
}
|
||||
else
|
||||
{
|
||||
InfoTextId = "traitorgoalpoisoneveryoneinfo";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public sealed class GoalKeepTransformedAlive : Goal
|
||||
{
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[speciesname]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { targetCharacterName });
|
||||
|
||||
public override bool IsCompleted => isCompleted;
|
||||
private bool isCompleted;
|
||||
|
||||
private const float gracePeriod = 1f;
|
||||
private string speciesId;
|
||||
private string targetCharacterName;
|
||||
private Character targetCharacter;
|
||||
private float timer;
|
||||
|
||||
public override bool CanBeCompleted(ICollection<Traitor> traitors)
|
||||
{
|
||||
return timer < gracePeriod || targetCharacter != null && !targetCharacter.IsDead;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (timer <= gracePeriod)
|
||||
{
|
||||
timer += deltaTime;
|
||||
}
|
||||
|
||||
isCompleted = targetCharacter != null && !targetCharacter.IsDead && timer >= gracePeriod;
|
||||
}
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
{
|
||||
if (!base.Start(traitor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startTime = Timing.TotalTime;
|
||||
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.Submarine == null || Traitors.All(t => character.Submarine.TeamID != t.Character.TeamID) || character.SpawnTime + gracePeriod < startTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (character.SpeciesName.ToLowerInvariant() == speciesId)
|
||||
{
|
||||
targetCharacter = character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
targetCharacterName = TextManager.FormatServerMessage($"character.{speciesId}").ToLowerInvariant();
|
||||
|
||||
return targetCharacter != null;
|
||||
}
|
||||
|
||||
public GoalKeepTransformedAlive(string speciesId) : base()
|
||||
{
|
||||
this.speciesId = speciesId.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,20 +9,109 @@ namespace Barotrauma
|
||||
public sealed class GoalKillTarget : Goal
|
||||
{
|
||||
public TraitorMission.CharacterFilter Filter { get; private set; }
|
||||
public Character Target { get; private set; }
|
||||
public List<Character> Targets { get; private set; }
|
||||
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { Target?.Name ?? "(unknown)" });
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]", "[causeofdeath]", "[targethullname]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[]
|
||||
{ traitor.Mission.GetTargetNames(Targets) ?? "(unknown)", GetCauseOfDeath(), targetHull != null ? TextManager.Get($"roomname.{targetHull}") : string.Empty });
|
||||
|
||||
private bool isCompleted = false;
|
||||
public override bool IsCompleted => isCompleted;
|
||||
|
||||
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && character == Target);
|
||||
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && Targets.Contains(character));
|
||||
|
||||
private CauseOfDeathType requiredCauseOfDeath;
|
||||
private string afflictionId;
|
||||
private string targetHull;
|
||||
private int targetCount;
|
||||
private float targetPercentage;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
isCompleted = Target?.IsDead ?? false;
|
||||
isCompleted = DoesDeathMatchCriteria();
|
||||
}
|
||||
|
||||
private bool DoesDeathMatchCriteria()
|
||||
{
|
||||
if (Targets == null || Targets.Any(t => !t.IsDead)) return false;
|
||||
|
||||
bool typeMatch = false;
|
||||
|
||||
for (int i = 0; i < Targets.Count; i++)
|
||||
{
|
||||
// No specified cause of death required or missing cause of death
|
||||
if (requiredCauseOfDeath == CauseOfDeathType.Unknown || Targets[i].CauseOfDeath == null)
|
||||
{
|
||||
typeMatch = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (Targets[i].CauseOfDeath.Type)
|
||||
{
|
||||
// If a cause of death is labeled as unknown, side with the traitor and accept this regardless of the required type
|
||||
case CauseOfDeathType.Unknown:
|
||||
typeMatch = true;
|
||||
break;
|
||||
case CauseOfDeathType.Pressure:
|
||||
case CauseOfDeathType.Suffocation:
|
||||
case CauseOfDeathType.Drowning:
|
||||
typeMatch = requiredCauseOfDeath == Targets[i].CauseOfDeath.Type;
|
||||
break;
|
||||
case CauseOfDeathType.Affliction:
|
||||
typeMatch = Targets[i].CauseOfDeath.Type == requiredCauseOfDeath && Targets[i].CauseOfDeath.Affliction.Identifier == afflictionId;
|
||||
break;
|
||||
case CauseOfDeathType.Disconnected:
|
||||
typeMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetHull != null)
|
||||
{
|
||||
if (Targets[i].CurrentHull != null)
|
||||
{
|
||||
if (typeMatch && Targets[i].CurrentHull.RoomName == targetHull || Targets[i].CurrentHull.RoomName.Contains(targetHull))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outside the submarine, not supported for now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeMatch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetCauseOfDeath()
|
||||
{
|
||||
if (requiredCauseOfDeath != CauseOfDeathType.Affliction || afflictionId == string.Empty)
|
||||
{
|
||||
return requiredCauseOfDeath.ToString().ToLower();
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextManager.Get($"afflictionname.{afflictionId}").ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
@@ -31,14 +120,43 @@ namespace Barotrauma
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Target = traitor.Mission.FindKillTarget(traitor.Character, Filter);
|
||||
return Target != null && !Target.IsDead;
|
||||
|
||||
Targets = traitor.Mission.FindKillTarget(traitor.Character, Filter, targetCount, targetPercentage);
|
||||
return Targets != null && !Targets.All(t => t.IsDead);
|
||||
}
|
||||
|
||||
public GoalKillTarget(TraitorMission.CharacterFilter filter) : base()
|
||||
public GoalKillTarget(TraitorMission.CharacterFilter filter, CauseOfDeathType requiredCauseOfDeath, string afflictionId, string targetHull, int targetCount, float targetPercentage) : base()
|
||||
{
|
||||
InfoTextId = "TraitorGoalKillTargetInfo";
|
||||
Filter = filter;
|
||||
this.requiredCauseOfDeath = requiredCauseOfDeath;
|
||||
this.afflictionId = afflictionId;
|
||||
this.targetHull = targetHull;
|
||||
this.targetCount = targetCount;
|
||||
this.targetPercentage = targetPercentage / 100f;
|
||||
|
||||
if (this.targetPercentage < 1f)
|
||||
{
|
||||
if (this.requiredCauseOfDeath == CauseOfDeathType.Unknown && targetHull == null)
|
||||
{
|
||||
InfoTextId = "traitorgoalkilltargetinfo";
|
||||
}
|
||||
else if (this.requiredCauseOfDeath != CauseOfDeathType.Unknown && targetHull == null)
|
||||
{
|
||||
InfoTextId = "traitorgoalkilltargetinfowithcause";
|
||||
}
|
||||
else if (this.requiredCauseOfDeath == CauseOfDeathType.Unknown && targetHull != null)
|
||||
{
|
||||
InfoTextId = "traitorgoalkilltargetinfowithhull";
|
||||
}
|
||||
else if (this.requiredCauseOfDeath != CauseOfDeathType.Unknown && targetHull != null)
|
||||
{
|
||||
InfoTextId = "traitorgoalkilltargetinfowithcauseandhull";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InfoTextId = "traitorgoalkilleveryoneinfo";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@ namespace Barotrauma
|
||||
{
|
||||
private readonly float requiredDistance;
|
||||
private readonly float requiredDistanceSqr;
|
||||
private float requiredDistanceInMeters;
|
||||
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[distance]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { $"{requiredDistance:0.00}" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { $"{requiredDistanceInMeters:0.00}" });
|
||||
|
||||
public override bool IsCompleted
|
||||
{
|
||||
@@ -22,12 +23,21 @@ namespace Barotrauma
|
||||
{
|
||||
return Traitors.Any(traitor =>
|
||||
{
|
||||
if (traitor.Character?.Submarine == null)
|
||||
Submarine ownSub = null;
|
||||
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
{
|
||||
return false;
|
||||
if (Submarine.MainSubs[i] != null && Submarine.MainSubs[i].TeamID == traitor.Character.TeamID)
|
||||
{
|
||||
ownSub = Submarine.MainSubs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ownSub == null) return false;
|
||||
|
||||
var characterPosition = traitor.Character.WorldPosition;
|
||||
var submarinePosition = traitor.Character.Submarine.WorldPosition;
|
||||
var submarinePosition = ownSub.WorldPosition;
|
||||
var distance = Vector2.DistanceSquared(characterPosition, submarinePosition);
|
||||
return distance >= requiredDistanceSqr;
|
||||
});
|
||||
@@ -37,8 +47,9 @@ namespace Barotrauma
|
||||
public GoalReachDistanceFromSub(float requiredDistance) : base()
|
||||
{
|
||||
InfoTextId = "TraitorGoalReachDistanceFromSub";
|
||||
this.requiredDistance = requiredDistance;
|
||||
requiredDistanceSqr = requiredDistance * requiredDistance;
|
||||
requiredDistanceInMeters = requiredDistance;
|
||||
this.requiredDistance = requiredDistance / Physics.DisplayToRealWorldRatio;
|
||||
requiredDistanceSqr = this.requiredDistance * this.requiredDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public sealed class GoalUnwiring : HumanoidGoal
|
||||
{
|
||||
private readonly string tag;
|
||||
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]", "[connectionname]" });
|
||||
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { targetItemPrefabName ?? "", targetConnectionDisplayName ?? targetConnectionName });
|
||||
|
||||
private bool isCompleted = false;
|
||||
public override bool IsCompleted => isCompleted;
|
||||
|
||||
private readonly List<ConnectionPanel> targetConnectionPanels = new List<ConnectionPanel>();
|
||||
private string targetItemPrefabName;
|
||||
private string targetConnectionName;
|
||||
private string targetConnectionDisplayName;
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
{
|
||||
if (!base.Start(traitor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.Prefab?.Identifier == tag || item.HasTag(tag))
|
||||
{
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null)
|
||||
{
|
||||
targetConnectionPanels.Add(connectionPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetConnectionPanels.Count > 0)
|
||||
{
|
||||
var textId = targetConnectionPanels[0].Item.Prefab.GetItemNameTextId();
|
||||
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetConnectionPanels[0].Item.Prefab.Name;
|
||||
}
|
||||
|
||||
return targetConnectionPanels.Count > 0;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
isCompleted = AreTargetsUnwired();
|
||||
}
|
||||
|
||||
private bool AreTargetsUnwired()
|
||||
{
|
||||
for (int i = 0; i < targetConnectionPanels.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < targetConnectionPanels[i].Connections.Count; j++)
|
||||
{
|
||||
if (targetConnectionPanels[i].Connections[j] == null || targetConnectionPanels[i].Connections[j].Wires == null) continue;
|
||||
if (targetConnectionName != string.Empty)
|
||||
{
|
||||
if (targetConnectionPanels[i].Connections[j].Name != targetConnectionName) continue;
|
||||
}
|
||||
if (!targetConnectionPanels[i].Connections[j].Wires.All(w => w == null)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public GoalUnwiring(string tag, string targetConnectionName, string targetConnectionDisplayTag) : base()
|
||||
{
|
||||
this.tag = tag;
|
||||
this.targetConnectionName = targetConnectionName;
|
||||
|
||||
if (targetConnectionDisplayTag != string.Empty)
|
||||
{
|
||||
targetConnectionDisplayName = TextManager.FormatServerMessage(targetConnectionDisplayTag);
|
||||
InfoTextId = "TraitorGoalUnwireInfo";
|
||||
}
|
||||
else
|
||||
{
|
||||
InfoTextId = "TraitorGoalUnwireAllInfo";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (var i = allGoalsCount; i > 1;)
|
||||
{
|
||||
int j = TraitorMission.Random(i--);
|
||||
int j = TraitorManager.RandomInt(i--);
|
||||
var temp = indices[j];
|
||||
indices[j] = indices[i];
|
||||
indices[i] = temp;
|
||||
@@ -125,10 +125,12 @@ namespace Barotrauma
|
||||
completedGoals.Add(goal);
|
||||
}
|
||||
}
|
||||
if (pendingGoals.Count <= 0)
|
||||
|
||||
if (pendingGoals.Count <= 0 && completedGoals.Count < allGoals.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
IsStarted = true;
|
||||
|
||||
traitor.SendChatMessageBox(StartMessageText, traitor.Mission?.Identifier);
|
||||
|
||||
@@ -11,6 +11,14 @@ namespace Barotrauma
|
||||
{
|
||||
partial class TraitorManager
|
||||
{
|
||||
public static readonly Random Random = new Random((int)DateTime.UtcNow.Ticks);
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static int RandomInt(int n) => Random.Next(n);
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static double RandomDouble() => Random.NextDouble();
|
||||
|
||||
public readonly Dictionary<Character.TeamType, Traitor.TraitorMission> Missions = new Dictionary<Character.TeamType, Traitor.TraitorMission>();
|
||||
|
||||
public string GetCodeWords(Character.TeamType team) => Missions.TryGetValue(team, out var mission) ? mission.CodeWords : "";
|
||||
@@ -21,33 +29,12 @@ namespace Barotrauma
|
||||
private float startCountdown = 0.0f;
|
||||
private GameServer server;
|
||||
|
||||
private readonly Dictionary<ulong, int> traitorCountsBySteamId = new Dictionary<ulong, int>();
|
||||
private readonly Dictionary<string, int> traitorCountsByEndPoint = new Dictionary<string, int>();
|
||||
|
||||
public bool ShouldEndRound
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int GetTraitorCount(Tuple<ulong, string> steamIdAndEndPoint)
|
||||
{
|
||||
if (steamIdAndEndPoint.Item1 > 0 && traitorCountsBySteamId.TryGetValue(steamIdAndEndPoint.Item1, out var steamIdResult))
|
||||
{
|
||||
return steamIdResult;
|
||||
}
|
||||
return traitorCountsByEndPoint.TryGetValue(steamIdAndEndPoint.Item2, out var endPointResult) ? endPointResult : 0;
|
||||
}
|
||||
|
||||
public void SetTraitorCount(Tuple<ulong, string> steamIdAndEndPoint, int count)
|
||||
{
|
||||
if (steamIdAndEndPoint.Item1 > 0)
|
||||
{
|
||||
traitorCountsBySteamId[steamIdAndEndPoint.Item1] = count;
|
||||
}
|
||||
traitorCountsByEndPoint[steamIdAndEndPoint.Item2] = count;
|
||||
}
|
||||
|
||||
public bool IsTraitor(Character character)
|
||||
{
|
||||
if (Traitors == null)
|
||||
@@ -80,11 +67,13 @@ namespace Barotrauma
|
||||
|
||||
ShouldEndRound = false;
|
||||
|
||||
Traitor.TraitorMission.InitializeRandom();
|
||||
this.server = server;
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinStartDelay, server.ServerSettings.TraitorsMaxStartDelay, (float)Traitor.TraitorMission.RandomDouble());
|
||||
traitorCountsBySteamId.Clear();
|
||||
traitorCountsByEndPoint.Clear();
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinStartDelay, server.ServerSettings.TraitorsMaxStartDelay, (float)RandomDouble());
|
||||
}
|
||||
|
||||
public void SkipStartDelay()
|
||||
{
|
||||
startCountdown = 0.01f;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
@@ -134,7 +123,7 @@ namespace Barotrauma
|
||||
if (missionCompleted)
|
||||
{
|
||||
Missions.Clear();
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)Traitor.TraitorMission.RandomDouble());
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)RandomDouble());
|
||||
}
|
||||
}
|
||||
else if (startCountdown > 0.0f && server.GameStarted)
|
||||
@@ -145,7 +134,7 @@ namespace Barotrauma
|
||||
int playerCharactersCount = server.ConnectedClients.Sum(client => client.Character != null && !client.Character.IsDead ? 1 : 0);
|
||||
if (playerCharactersCount < server.ServerSettings.TraitorsMinPlayerCount)
|
||||
{
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)Traitor.TraitorMission.RandomDouble());
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)RandomDouble());
|
||||
return;
|
||||
}
|
||||
if (GameMain.GameSession.Mission is CombatMission)
|
||||
@@ -184,7 +173,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
Missions.Clear();
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)Traitor.TraitorMission.RandomDouble());
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)RandomDouble());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,42 +187,5 @@ namespace Barotrauma
|
||||
|
||||
return TextManager.JoinServerMessages("\n\n", Missions.Select(mission => mission.Value.GlobalEndMessage).ToArray());
|
||||
}
|
||||
|
||||
public static T WeightedRandom<T>(IList<T> collection, int startIndex, int count, Func<int, int> random, Func<T, int> readSelectedWeight, Action<T, int> writeSelectedWeight, int entryWeight, int selectionWeight) where T : class
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var maxWeight = readSelectedWeight(collection[startIndex]);
|
||||
var totalWeight = entryWeight + maxWeight;
|
||||
for (var i = 1; i < count; ++i)
|
||||
{
|
||||
var weight = readSelectedWeight(collection[startIndex + i]);
|
||||
maxWeight = Math.Max(maxWeight, weight);
|
||||
totalWeight += weight;
|
||||
}
|
||||
maxWeight += entryWeight;
|
||||
totalWeight = count * maxWeight - totalWeight;
|
||||
var selected = random(totalWeight);
|
||||
for(var i = 0; i < count; ++i)
|
||||
{
|
||||
var entry = collection[startIndex + i];
|
||||
var weight = readSelectedWeight(entry);
|
||||
selected -= maxWeight;
|
||||
selected += weight;
|
||||
if (selected <= 0)
|
||||
{
|
||||
writeSelectedWeight(entry, weight + selectionWeight);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T WeightedRandom<T>(IList<T> collection, Func<int, int> random, Func<T, int> readSelectedWeight, Action<T, int> writeSelectedWeight, int entryWeight, int selectionWeight) where T : class
|
||||
{
|
||||
return WeightedRandom<T>(collection, 0, collection.Count, random, readSelectedWeight, writeSelectedWeight, entryWeight, selectionWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,16 @@ namespace Barotrauma
|
||||
{
|
||||
public class TraitorMission
|
||||
{
|
||||
private static System.Random random = null;
|
||||
|
||||
public static void InitializeRandom() => random = new System.Random((int)DateTime.UtcNow.Ticks);
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static int Random(int n) => random.Next(n);
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static double RandomDouble() => random.NextDouble();
|
||||
|
||||
private static string wordsTxt = Path.Combine("Content", "CodeWords.txt");
|
||||
|
||||
private readonly List<Objective> allObjectives = new List<Objective>();
|
||||
private readonly List<Objective> pendingObjectives = new List<Objective>();
|
||||
private readonly List<Objective> completedObjectives = new List<Objective>();
|
||||
|
||||
public virtual bool IsCompleted => pendingObjectives.Count <= 0;
|
||||
/// <summary>
|
||||
/// Has the mission been completed (does not mean that the traitor necessarily won, the mission is considered completed if the traitor fails for whatever reason)
|
||||
/// </summary>
|
||||
public bool IsCompleted => pendingObjectives.Count <= 0;
|
||||
|
||||
public readonly Dictionary<string, Traitor> Traitors = new Dictionary<string, Traitor>();
|
||||
|
||||
@@ -168,14 +161,8 @@ namespace Barotrauma
|
||||
{
|
||||
++numCandidates;
|
||||
}
|
||||
var selected = TraitorManager.WeightedRandom(availableCandidates, 0, numCandidates, Random, t =>
|
||||
{
|
||||
var previousClient = server.FindPreviousClientData(t.Item1);
|
||||
return Math.Max(
|
||||
previousClient != null ? traitorManager.GetTraitorCount(previousClient) : 0,
|
||||
traitorManager.GetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? "")));
|
||||
}, (t, c) => { traitorManager.SetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? ""), c); }, 2, 3);
|
||||
|
||||
var selected = ToolBox.SelectWeightedRandom(availableCandidates, availableCandidates.Select(c => Math.Max(c.Item1.RoundsSincePlayedAsTraitor, 0.1f)).ToList(), TraitorManager.Random);
|
||||
assignedCandidates.Add(Tuple.Create(currentRole, selected));
|
||||
foreach (var candidate in roleCandidates.Values)
|
||||
{
|
||||
@@ -189,7 +176,7 @@ namespace Barotrauma
|
||||
return assignedCandidates;
|
||||
}
|
||||
|
||||
public virtual bool CanBeStarted(GameServer server, TraitorManager traitorManager, Character.TeamType team)
|
||||
public bool CanBeStarted(GameServer server, TraitorManager traitorManager, Character.TeamType team)
|
||||
{
|
||||
foreach (var role in Roles)
|
||||
{
|
||||
@@ -202,7 +189,7 @@ namespace Barotrauma
|
||||
return AssignTraitors(server, traitorManager, team) != null;
|
||||
}
|
||||
|
||||
public virtual bool Start(GameServer server, TraitorManager traitorManager, Character.TeamType team)
|
||||
public bool Start(GameServer server, TraitorManager traitorManager, Character.TeamType team)
|
||||
{
|
||||
var assignedCandidates = AssignTraitors(server, traitorManager, team);
|
||||
if (assignedCandidates == null)
|
||||
@@ -210,11 +197,17 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (Client client in server.ConnectedClients)
|
||||
{
|
||||
client.RoundsSincePlayedAsTraitor++;
|
||||
}
|
||||
|
||||
Traitors.Clear();
|
||||
foreach (var candidate in assignedCandidates)
|
||||
{
|
||||
var traitor = new Traitor(this, candidate.Item1, candidate.Item2.Item1.Character);
|
||||
Traitors.Add(candidate.Item1, traitor);
|
||||
candidate.Item2.Item1.RoundsSincePlayedAsTraitor = 0;
|
||||
}
|
||||
CodeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
|
||||
CodeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
|
||||
@@ -250,15 +243,17 @@ namespace Barotrauma
|
||||
|
||||
public delegate void TraitorWinHandler();
|
||||
|
||||
public virtual void Update(float deltaTime, TraitorWinHandler winHandler)
|
||||
public void Update(float deltaTime, TraitorWinHandler winHandler)
|
||||
{
|
||||
if (pendingObjectives.Count <= 0 || Traitors.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Traitors.Values.Any(traitor => traitor.Character?.IsDead ?? true))
|
||||
if (Traitors.Values.Any(traitor => traitor.Character?.IsDead ?? true || traitor.Character.Removed))
|
||||
{
|
||||
Traitors.Values.ForEach(traitor => traitor.UpdateCurrentObjective("", Identifier));
|
||||
pendingObjectives.Clear();
|
||||
Traitors.Clear();
|
||||
return;
|
||||
}
|
||||
var startedObjectives = new List<Objective>();
|
||||
@@ -321,28 +316,69 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public delegate bool CharacterFilter(Character character);
|
||||
public Character FindKillTarget(Character traitor, CharacterFilter filter)
|
||||
public List<Character> FindKillTarget(Character traitor, CharacterFilter filter, int count = -1, float percentage = -1f)
|
||||
{
|
||||
if (traitor == null) { return null; }
|
||||
|
||||
List<Character> validCharacters = Character.CharacterList.FindAll(c =>
|
||||
c.TeamID == traitor.TeamID &&
|
||||
c != traitor &&
|
||||
!c.IsDead &&
|
||||
(filter == null || filter(c)));
|
||||
List<Character> validCharacters = Character.CharacterList.FindAll(c => c.TeamID == traitor.TeamID &&
|
||||
c != traitor && !c.IsDead &&
|
||||
(filter == null || filter(c)));
|
||||
|
||||
int targetCount = 1;
|
||||
if (count > 0)
|
||||
{
|
||||
targetCount = count;
|
||||
}
|
||||
else if (percentage > 0f)
|
||||
{
|
||||
targetCount = (int)Math.Max(1, Math.Floor(validCharacters.Count * percentage));
|
||||
}
|
||||
|
||||
List<Character> targetCharacters = new List<Character>();
|
||||
|
||||
if (validCharacters.Count > 0)
|
||||
{
|
||||
return validCharacters[Random(validCharacters.Count)];
|
||||
for (int i = 0; i < targetCount; i++)
|
||||
{
|
||||
if (validCharacters.Count == 0) break;
|
||||
Character character = validCharacters[TraitorManager.RandomInt(validCharacters.Count)];
|
||||
targetCharacters.Add(character);
|
||||
validCharacters.Remove(character);
|
||||
}
|
||||
return targetCharacters;
|
||||
}
|
||||
|
||||
#if ALLOW_SOLO_TRAITOR
|
||||
return traitor;
|
||||
targetCharacters.Add(traitor);
|
||||
return targetCharacters;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public string GetTargetNames(List<Character> targets)
|
||||
{
|
||||
string names = string.Empty;
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
names += targets[i].Name;
|
||||
|
||||
if (i < targets.Count - 1)
|
||||
{
|
||||
names += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
if (names.Length > 0)
|
||||
{
|
||||
return names;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextManager.FormatServerMessage("unknown");
|
||||
}
|
||||
}
|
||||
|
||||
public TraitorMission(string identifier, string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, IEnumerable<KeyValuePair<string, RoleFilter>> roles, ICollection<Objective> objectives)
|
||||
{
|
||||
Identifier = identifier;
|
||||
|
||||
@@ -12,12 +12,11 @@ namespace Barotrauma
|
||||
public class TraitorMissionEntry
|
||||
{
|
||||
public readonly TraitorMissionPrefab Prefab;
|
||||
public int SelectedWeight;
|
||||
public float SelectedWeight;
|
||||
|
||||
public TraitorMissionEntry(XElement element)
|
||||
{
|
||||
Prefab = new TraitorMissionPrefab(element);
|
||||
SelectedWeight = 0;
|
||||
}
|
||||
}
|
||||
public static readonly List<TraitorMissionEntry> List = new List<TraitorMissionEntry>();
|
||||
@@ -39,7 +38,14 @@ namespace Barotrauma
|
||||
|
||||
public static TraitorMissionPrefab RandomPrefab()
|
||||
{
|
||||
return TraitorManager.WeightedRandom(List, Traitor.TraitorMission.Random, entry => entry.SelectedWeight, (entry, weight) => entry.SelectedWeight = weight, 2, 3)?.Prefab;
|
||||
var selected = ToolBox.SelectWeightedRandom(List, List.Select(mission => Math.Max(mission.SelectedWeight, 0.1f)).ToList(), TraitorManager.Random);
|
||||
//the weight of the missions that didn't get selected keeps growing the make them more likely to get picked
|
||||
foreach (var mission in List)
|
||||
{
|
||||
mission.SelectedWeight += 10;
|
||||
}
|
||||
selected.SelectedWeight = 0.0f;
|
||||
return selected.Prefab;
|
||||
}
|
||||
|
||||
private class AttributeChecker : IDisposable
|
||||
@@ -113,15 +119,23 @@ namespace Barotrauma
|
||||
case "killtarget":
|
||||
{
|
||||
checker.Optional(targetFilters.Keys.ToArray());
|
||||
List<Traitor.TraitorMission.CharacterFilter> filters = new List<Traitor.TraitorMission.CharacterFilter>();
|
||||
checker.Optional("causeofdeath");
|
||||
checker.Optional("affliction");
|
||||
checker.Optional("roomname");
|
||||
checker.Optional("targetcount");
|
||||
checker.Optional("targetpercentage");
|
||||
List<Traitor.TraitorMission.CharacterFilter> killFilters = new List<Traitor.TraitorMission.CharacterFilter>();
|
||||
foreach (var attribute in Config.Attributes())
|
||||
{
|
||||
if (targetFilters.TryGetValue(attribute.Name.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), out var filter))
|
||||
{
|
||||
filters.Add((character) => filter(attribute.Value, character));
|
||||
killFilters.Add((character) => filter(attribute.Value, character));
|
||||
}
|
||||
}
|
||||
goal = new Traitor.GoalKillTarget((character) => filters.All(f => f(character)));
|
||||
goal = new Traitor.GoalKillTarget((character) => killFilters.All(f => f(character)),
|
||||
(CauseOfDeathType)Enum.Parse(typeof(CauseOfDeathType), Config.GetAttributeString("causeofdeath", "Unknown"), true),
|
||||
Config.GetAttributeString("affliction", null), Config.GetAttributeString("targethull", null), Config.GetAttributeInt("targetcount", -1),
|
||||
Config.GetAttributeFloat("targetpercentage", -1f));
|
||||
break;
|
||||
}
|
||||
case "destroyitems":
|
||||
@@ -157,8 +171,16 @@ namespace Barotrauma
|
||||
break;
|
||||
case "finditem":
|
||||
checker.Required("identifier");
|
||||
checker.Optional("preferNew", "allowNew", "allowExisting", "allowedContainers");
|
||||
goal = new Traitor.GoalFindItem(Config.GetAttributeString("identifier", null), Config.GetAttributeBool("preferNew", true), Config.GetAttributeBool("allowNew", true), Config.GetAttributeBool("allowExisting", true), Config.GetAttributeStringArray("allowedContainers", new string[] {"steelcabinet", "mediumsteelcabinet", "suppliescabinet"}));
|
||||
checker.Optional("preferNew", "allowNew", "allowExisting", "allowedContainers", "percentage");
|
||||
List<Traitor.TraitorMission.CharacterFilter> itemCountFilters = new List<Traitor.TraitorMission.CharacterFilter>();
|
||||
foreach (var attribute in Config.Attributes())
|
||||
{
|
||||
if (targetFilters.TryGetValue(attribute.Name.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), out var filter))
|
||||
{
|
||||
itemCountFilters.Add((character) => filter(attribute.Value, character));
|
||||
}
|
||||
}
|
||||
goal = new Traitor.GoalFindItem((character) => itemCountFilters.All(f => f(character)), Config.GetAttributeString("identifier", null), Config.GetAttributeBool("preferNew", true), Config.GetAttributeBool("allowNew", true), Config.GetAttributeBool("allowExisting", true), Config.GetAttributeFloat("percentage", -1f), Config.GetAttributeStringArray("allowedContainers", new string[] {"steelcabinet", "mediumsteelcabinet", "suppliescabinet"}));
|
||||
break;
|
||||
case "replaceinventory":
|
||||
checker.Required("containers", "replacements");
|
||||
@@ -167,7 +189,39 @@ namespace Barotrauma
|
||||
break;
|
||||
case "reachdistancefromsub":
|
||||
checker.Optional("distance");
|
||||
goal = new Traitor.GoalReachDistanceFromSub(Config.GetAttributeFloat("distance", 10000.0f));
|
||||
goal = new Traitor.GoalReachDistanceFromSub(Config.GetAttributeFloat("distance", 125f));
|
||||
break;
|
||||
case "injectpoison":
|
||||
checker.Optional(targetFilters.Keys.ToArray());
|
||||
checker.Required("poison");
|
||||
checker.Required("affliction");
|
||||
checker.Optional("targetcount");
|
||||
checker.Optional("targetpercentage");
|
||||
List<Traitor.TraitorMission.CharacterFilter> poisonFilters = new List<Traitor.TraitorMission.CharacterFilter>();
|
||||
foreach (var attribute in Config.Attributes())
|
||||
{
|
||||
if (targetFilters.TryGetValue(attribute.Name.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), out var filter))
|
||||
{
|
||||
poisonFilters.Add((character) => filter(attribute.Value, character));
|
||||
}
|
||||
}
|
||||
goal = new Traitor.GoalInjectTarget((character) => poisonFilters.All(f => f(character)), Config.GetAttributeString("poison", null),
|
||||
Config.GetAttributeString("affliction", null), Config.GetAttributeInt("targetcount", -1), Config.GetAttributeFloat("targetpercentage", -1f));
|
||||
break;
|
||||
case "unwire":
|
||||
checker.Required("tag");
|
||||
checker.Optional("connectionname");
|
||||
checker.Optional("connectiondisplayname");
|
||||
goal = new Traitor.GoalUnwiring(Config.GetAttributeString("tag", null), Config.GetAttributeString("connectionname", null), Config.GetAttributeString("connectiondisplayname)", null));
|
||||
break;
|
||||
case "transformentity":
|
||||
checker.Required("entities", "entitytypes");
|
||||
checker.Optional("catalystid");
|
||||
goal = new Traitor.GoalEntityTransformation(Config.GetAttributeStringArray("entities", null), Config.GetAttributeStringArray("entitytypes", null), Config.GetAttributeString("catalystid", null));
|
||||
break;
|
||||
case "keeptransformedalive":
|
||||
checker.Required("speciesname");
|
||||
goal = new Traitor.GoalKeepTransformedAlive(Config.GetAttributeString("speciesname", null));
|
||||
break;
|
||||
default:
|
||||
GameServer.Log($"Unrecognized goal type \"{goalType}\".", ServerLog.MessageType.Error);
|
||||
|
||||
Reference in New Issue
Block a user