Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -0,0 +1,126 @@
#nullable enable
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class TraitorEvent : ScriptedEvent
{
public enum State
{
Incomplete,
Completed,
Failed
}
public Action? OnStateChanged;
private new readonly TraitorEventPrefab prefab;
public new TraitorEventPrefab Prefab => prefab;
private LocalizedString codeWord;
private State currentState;
public State CurrentState
{
get { return currentState; }
set
{
if (currentState == value) { return; }
currentState = value;
OnStateChanged?.Invoke();
}
}
private Client? traitor;
public Client? Traitor => traitor;
private readonly HashSet<Client> secondaryTraitors = new HashSet<Client>();
public IEnumerable<Client> SecondaryTraitors => secondaryTraitors;
public override string ToString()
{
return $"{nameof(TraitorEvent)} ({prefab.Identifier})";
}
private readonly static HashSet<Identifier> nonActionChildElementNames = new HashSet<Identifier>()
{
"icon".ToIdentifier(),
"reputationrequirement".ToIdentifier(),
"missionrequirement".ToIdentifier(),
"levelrequirement".ToIdentifier()
};
protected override IEnumerable<Identifier> NonActionChildElementNames => nonActionChildElementNames;
public TraitorEvent(TraitorEventPrefab prefab) : base(prefab)
{
this.prefab = prefab;
codeWord = string.Empty;
}
public override void Init(EventSet? parentSet = null)
{
base.Init(parentSet);
if (traitor == null)
{
DebugConsole.ThrowError($"Error when initializing event \"{prefab.Identifier}\": traitor not set.\n" + Environment.StackTrace);
}
}
public override LocalizedString ReplaceVariablesInEventText(LocalizedString str)
{
if (codeWord.IsNullOrEmpty())
{
//store the code word so the same random word is used in all the actions in the event
codeWord = TextManager.Get("traitor.codeword");
}
return str
.Replace("[traitor]", traitor?.Name ?? "none")
.Replace("[target]", (GetTargets("target".ToIdentifier()).FirstOrDefault() as Character)?.DisplayName ?? "none")
.Replace("[codeword]", codeWord.Value);
}
public void SetTraitor(Client traitor)
{
if (traitor.Character == null)
{
throw new InvalidOperationException($"Tried to set a client who's not controlling a character (\"{traitor.Name}\") as the traitor.");
}
this.traitor = traitor;
traitor.Character.IsTraitor = true;
AddTarget(Tags.Traitor, traitor.Character);
AddTarget(Tags.AnyTraitor, traitor.Character);
AddTargetPredicate(Tags.NonTraitor, e => e is Character c && (c.IsPlayer || c.IsBot) && !c.IsTraitor && c.TeamID == traitor.TeamID && !c.IsIncapacitated);
AddTargetPredicate(Tags.NonTraitorPlayer, e => e is Character c && c.IsPlayer && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
}
public void SetSecondaryTraitors(IEnumerable<Client> traitors)
{
int index = 0;
foreach (var traitor in traitors)
{
if (traitor.Character == null)
{
throw new InvalidOperationException($"Tried to set a client who's not controlling a character (\"{traitor.Name}\") as a secondary traitor.");
}
if (this.traitor == traitor)
{
DebugConsole.ThrowError($"Tried to assign the main traitor {traitor.Name} as a secondary traitor.");
continue;
}
secondaryTraitors.Add(traitor);
traitor.Character.IsTraitor = true;
AddTarget(Tags.SecondaryTraitor, traitor.Character);
AddTarget((Tags.SecondaryTraitor.ToString() + index).ToIdentifier(), traitor.Character);
AddTarget(Tags.AnyTraitor, traitor.Character);
index++;
}
}
}
}
@@ -0,0 +1,350 @@
#nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class TraitorEventPrefab : EventPrefab
{
class MissionRequirement
{
public Identifier MissionIdentifier;
public Identifier MissionTag;
public MissionType MissionType;
public MissionRequirement(XElement element, TraitorEventPrefab prefab)
{
MissionIdentifier = element.GetAttributeIdentifier(nameof(MissionIdentifier), Identifier.Empty);
MissionTag = element.GetAttributeIdentifier(nameof(MissionTag), Identifier.Empty);
MissionType = element.GetAttributeEnum(nameof(MissionType), MissionType.None);
if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionType == MissionType.None)
{
DebugConsole.ThrowError($"Error in traitor event \"{prefab.Identifier}\". Mission requirement with no {nameof(MissionIdentifier)}, {nameof(MissionTag)} or {nameof(MissionType)}.");
}
}
public bool Match(Mission mission)
{
if (mission == null) { return MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionType == MissionType.None; }
if (!MissionIdentifier.IsEmpty)
{
return mission.Prefab.Identifier == MissionIdentifier;
}
else if (!MissionTag.IsEmpty)
{
return mission.Prefab.Tags.Contains(MissionTag);
}
else if (MissionType != MissionType.None)
{
return mission.Prefab.Type == MissionType;
}
return false;
}
}
class LevelRequirement
{
private enum LevelType
{
LocationConnection,
Outpost,
Any
}
private readonly LevelType levelType;
public ImmutableArray<Identifier> LocationTypes { get; }
/// <summary>
/// Minimimum difficulty of the level for this event to get selected. Defaults to 0.
/// </summary>
private readonly float minDifficulty;
/// <summary>
/// Minimimum difficulty of the level for this event to get selected. Defaults to 5 or <see cref="minDifficulty"/>, whichever is lower.
/// </summary>
private readonly float minDifficultyInCampaign;
//feels a little weird to have something this specific here, but couldn't think of a better way to implement this
public ImmutableArray<PropertyConditional> RequiredItemConditionals;
public LevelRequirement(XElement element, TraitorEventPrefab prefab)
{
levelType = element.GetAttributeEnum(nameof(LevelType), LevelType.Any);
LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty<Identifier>()).ToImmutableArray();
minDifficulty = element.GetAttributeFloat(nameof(minDifficulty), 0.0f);
minDifficultyInCampaign = element.GetAttributeFloat(nameof(minDifficultyInCampaign), Math.Max(minDifficulty, 5.0f));
List<PropertyConditional> requiredItemConditionals = new List<PropertyConditional>();
foreach (var subElement in element.Elements())
{
if (subElement.NameAsIdentifier() == "itemconditional")
{
requiredItemConditionals.AddRange(PropertyConditional.FromXElement(subElement));
}
}
RequiredItemConditionals = requiredItemConditionals.ToImmutableArray();
}
public bool Match(Level level)
{
if (level?.LevelData == null) { return false; }
switch (levelType)
{
case LevelType.LocationConnection:
if (level.LevelData.Type != LevelData.LevelType.LocationConnection) { return false; }
break;
case LevelType.Outpost:
if (level.LevelData.Type != LevelData.LevelType.Outpost) { return false; }
break;
}
if (GameMain.GameSession?.Campaign != null)
{
if (level.Difficulty < minDifficultyInCampaign) { return false; }
}
else
{
if (level.Difficulty < minDifficulty) { return false; }
}
if (level.StartLocation == null)
{
if (LocationTypes.Any()) { return false; }
}
else
{
if (LocationTypes.Any() && !LocationTypes.Contains(level.StartLocation.Type.Identifier)) { return false; }
}
if (RequiredItemConditionals.Any())
{
bool matchFound = false;
foreach (var item in Item.ItemList)
{
if (RequiredItemConditionals.All(c => item.ConditionalMatches(c)))
{
matchFound = true;
break;
}
}
if (!matchFound) { return false; }
}
return true;
}
}
class ReputationRequirement
{
public Identifier Faction;
public Identifier CompareToFaction;
public float CompareToValue;
public readonly PropertyConditional.ComparisonOperatorType Operator;
public ReputationRequirement(XElement element, TraitorEventPrefab prefab)
{
Faction = element.GetAttributeIdentifier(nameof(Faction), Identifier.Empty);
string conditionStr = element.GetAttributeString("reputation", string.Empty);
string[] splitString = conditionStr.Split(' ');
string value;
if (splitString.Length > 0)
{
//the first part of the string is the operator, skip it
value = string.Join(" ", splitString.Skip(1));
}
else
{
DebugConsole.ThrowError(
$"{conditionStr} in {prefab.Identifier} is too short."+
"It should start with an operator followed by a faction identifier or a floating point value.");
return;
}
Operator = PropertyConditional.GetComparisonOperatorType(splitString[0]);
if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
{
CompareToValue = floatVal;
}
else
{
CompareToFaction = value.ToIdentifier();
}
}
public bool Match(CampaignMode campaign)
{
var faction1 = campaign.GetFaction(Faction);
if (faction1 == null)
{
DebugConsole.ThrowError($"Could not find the faction {Faction}.");
return false;
}
if (!CompareToFaction.IsEmpty)
{
var faction2 = campaign.GetFaction(Faction);
if (faction2 == null)
{
DebugConsole.ThrowError($"Could not find the faction {CompareToFaction}.");
return false;
}
return PropertyConditional.CompareFloat(faction1.Reputation.Value, faction2.Reputation.Value, Operator);
}
return PropertyConditional.CompareFloat(faction1.Reputation.Value, CompareToValue, Operator);
}
}
public readonly Sprite? Icon;
public readonly Color IconColor;
public const int MinDangerLevel = 1;
public const int MaxDangerLevel = 3;
public ImmutableHashSet<Identifier> Tags;
private readonly ImmutableArray<ReputationRequirement> reputationRequirements;
private readonly ImmutableArray<MissionRequirement> missionRequirements;
private readonly ImmutableArray<LevelRequirement> levelRequirements;
public bool HasReputationRequirements => reputationRequirements.Any();
public bool HasMissionRequirements => missionRequirements.Any();
public bool HasLevelRequirements => levelRequirements.Any();
/// <summary>
/// An event with one of these tags must've been completed previously for this event to trigger.
/// </summary>
public ImmutableHashSet<Identifier> RequiredCompletedTags;
public readonly int DangerLevel;
/// <summary>
/// Minimum number of non-spectating human players on the server for the event to get selected.
/// </summary>
public readonly int MinPlayerCount;
/// <summary>
/// Number of players to assign as a "secondary traitor".
/// If both this and <see cref="SecondaryTraitorPercentage"/> are defined, this is treated as a minimum number of secondary traitors.
/// </summary>
public readonly int SecondaryTraitorAmount;
/// <summary>
/// Percentage of players to assign as a "secondary traitor".
/// </summary>
public readonly float SecondaryTraitorPercentage;
/// <summary>
/// Does accusing a secondary traitor count as correctly identifying the traitor?
/// </summary>
public readonly bool AllowAccusingSecondaryTraitor;
/// <summary>
/// Money penalty if the crew votes a wrong player as the traitor
/// </summary>
public readonly int MoneyPenaltyForUnfoundedTraitorAccusation;
/// <summary>
/// Is this event chainable, i.e. does the same traitor get another, higher-lvl one if they complete this one successfully?
/// </summary>
public readonly bool IsChainable;
public readonly float StealPercentageOfExperience;
public TraitorEventPrefab(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default)
: base(element, file, fallbackIdentifier)
{
DangerLevel = MathHelper.Clamp(element.GetAttributeInt(nameof(DangerLevel), MinDangerLevel), MinDangerLevel, MaxDangerLevel);
MinPlayerCount = element.GetAttributeInt(nameof(MinPlayerCount), 0);
SecondaryTraitorAmount = element.GetAttributeInt(nameof(SecondaryTraitorAmount), 0);
SecondaryTraitorPercentage = element.GetAttributeFloat(nameof(SecondaryTraitorPercentage), 0.0f);
AllowAccusingSecondaryTraitor = element.GetAttributeBool(nameof(AllowAccusingSecondaryTraitor), true);
MoneyPenaltyForUnfoundedTraitorAccusation = element.GetAttributeInt(nameof(MoneyPenaltyForUnfoundedTraitorAccusation), 100);
Tags = element.GetAttributeIdentifierImmutableHashSet(nameof(Tags), ImmutableHashSet<Identifier>.Empty);
RequiredCompletedTags = element.GetAttributeIdentifierImmutableHashSet(nameof(RequiredCompletedTags), ImmutableHashSet<Identifier>.Empty);
StealPercentageOfExperience = element.GetAttributeFloat(nameof(StealPercentageOfExperience), 0.0f);
IsChainable = element.GetAttributeBool(nameof(IsChainable), true);
List<ReputationRequirement> reputationRequirements = new List<ReputationRequirement>();
List<LevelRequirement> levelRequirements = new List<LevelRequirement>();
List<MissionRequirement> missionRequirements = new List<MissionRequirement>();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "reputationrequirement":
reputationRequirements.Add(new ReputationRequirement(subElement!, this));
break;
case "missionrequirement":
missionRequirements.Add(new MissionRequirement(subElement!, this));
break;
case "levelrequirement":
levelRequirements.Add(new LevelRequirement(subElement!, this));
break;
case "icon":
Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White);
break;
}
}
this.reputationRequirements = reputationRequirements.ToImmutableArray();
this.levelRequirements = levelRequirements.ToImmutableArray();
this.missionRequirements = missionRequirements.ToImmutableArray();
}
public bool ReputationRequirementsMet(CampaignMode? campaign)
{
if (campaign == null)
{
//no requirements in the campaign
return true;
}
foreach (ReputationRequirement requirement in reputationRequirements)
{
if (!requirement.Match(campaign)) { return false; }
}
return true;
}
public bool MissionRequirementsMet(GameSession? gameSession)
{
if (gameSession == null) { return false; }
foreach (MissionRequirement requirement in missionRequirements)
{
if (gameSession.Missions.None(m => requirement.Match(m))) { return false; }
}
return true;
}
public bool LevelRequirementsMet(Level? level)
{
if (level == null) { return false; }
//by default (if no requirements are specified) traitor events happen in LocationConnections.
if (levelRequirements.None() && level.Type != LevelData.LevelType.LocationConnection)
{
return false;
}
foreach (LevelRequirement requirement in levelRequirements)
{
if (!requirement.Match(level)) { return false; }
}
return true;
}
public override void Dispose()
{
Icon?.Remove();
}
public override string ToString()
{
return $"{nameof(TraitorEventPrefab)} ({Identifier})";
}
}
}
@@ -0,0 +1,49 @@
#nullable enable
using Barotrauma.Networking;
using System.Linq;
namespace Barotrauma
{
sealed partial class TraitorManager
{
public struct TraitorResults : INetSerializableStruct
{
[NetworkSerialize]
public byte VotedAsTraitorClientSessionId;
[NetworkSerialize]
public bool VotedCorrectTraitor;
[NetworkSerialize]
public bool ObjectiveSuccessful;
[NetworkSerialize]
public int MoneyPenalty;
[NetworkSerialize]
public Identifier TraitorEventIdentifier;
public TraitorResults(Client? votedAsTraitor, TraitorEvent traitorEvent)
{
VotedAsTraitorClientSessionId = votedAsTraitor?.SessionId ?? 0;
VotedCorrectTraitor = votedAsTraitor == traitorEvent.Traitor;
if (traitorEvent.Prefab.AllowAccusingSecondaryTraitor && !VotedCorrectTraitor)
{
VotedCorrectTraitor = traitorEvent.SecondaryTraitors.Contains(votedAsTraitor);
}
ObjectiveSuccessful = traitorEvent.CurrentState == TraitorEvent.State.Completed;
MoneyPenalty = votedAsTraitor != null && !VotedCorrectTraitor ?
traitorEvent.Prefab.MoneyPenaltyForUnfoundedTraitorAccusation :
0;
TraitorEventIdentifier = traitorEvent.Prefab.Identifier;
}
public Client? GetTraitorClient()
{
int sessionId = VotedAsTraitorClientSessionId;
return GameMain.NetworkMember?.ConnectedClients?.FirstOrDefault(c => c.SessionId == sessionId);
}
}
}
}
@@ -1,15 +0,0 @@
using System.Collections.Generic;
namespace Barotrauma
{
partial class TraitorMissionResult
{
public readonly string EndMessage;
public readonly Identifier MissionIdentifier;
public readonly bool Success;
public readonly List<Character> Characters = new List<Character>();
}
}