Unstable v0.19.3.0

This commit is contained in:
Juan Pablo Arce
2022-09-02 15:10:56 -03:00
parent 28789616bd
commit 3f2c843247
336 changed files with 7152 additions and 7739 deletions
@@ -1,8 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -20,6 +18,9 @@ namespace Barotrauma
[Serialize(true, IsPropertySaveable.Yes, "When set to false when TargetLimb is not specified prevent checking limb-specific afflictions")]
public bool AllowLimbAfflictions { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes, "Minimum strength of the affliction")]
public float MinStrength { get; set; }
public CheckAfflictionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
protected override bool? DetermineSuccess()
@@ -32,14 +33,15 @@ namespace Barotrauma
if (target.CharacterHealth == null) { continue; }
if (TargetLimb == LimbType.None)
{
if (target.CharacterHealth.GetAffliction(Identifier, AllowLimbAfflictions) != null) { return true; }
var affliction = target.CharacterHealth.GetAffliction(Identifier, AllowLimbAfflictions);
if (affliction != null && affliction.Strength >= MinStrength) { return true; }
}
IEnumerable<Affliction> afflictions = target.CharacterHealth.GetAllAfflictions().Where(affliction =>
{
LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type;
if (limbType == null) { return false; }
return limbType == TargetLimb || true;
return limbType == TargetLimb && affliction.Strength >= MinStrength;
});
if (afflictions.Any(a => a.Identifier == Identifier)) { return true; }
@@ -0,0 +1,71 @@
using System.Xml.Linq;
namespace Barotrauma
{
class CheckConditionalAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TargetTag { get; set; }
private PropertyConditional Conditional { get; }
public CheckConditionalAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
if (TargetTag.IsEmpty)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed.");
}
foreach (var attribute in element.Attributes())
{
if (PropertyConditional.IsValid(attribute) && !IsTargetTagAttribute(attribute))
{
Conditional = new PropertyConditional(attribute);
break;
}
}
if (Conditional == null)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed.");
}
static bool IsTargetTagAttribute(XAttribute attribute)
{
return attribute.Name.ToString().Equals("targettag", System.StringComparison.OrdinalIgnoreCase);
}
}
private string GetEventName()
{
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
}
protected override bool? DetermineSuccess()
{
ISerializableEntity target = null;
if (!TargetTag.IsEmpty)
{
foreach (var t in ParentEvent.GetTargets(TargetTag))
{
if (t is ISerializableEntity e)
{
target = e;
break;
}
}
}
if (target == null)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
}
if (target == null || Conditional == null)
{
return true;
}
if (target is Item item)
{
return item.ConditionalMatches(Conditional);
}
return Conditional.Matches(target);
}
}
}
@@ -0,0 +1,59 @@
namespace Barotrauma
{
class CheckOrderAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier OrderIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier OrderOption { get; set; }
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
protected override bool? DetermineSuccess()
{
ISerializableEntity target = null;
if (!TargetTag.IsEmpty)
{
foreach (var t in ParentEvent.GetTargets(TargetTag))
{
if (t is ISerializableEntity e)
{
target = e;
break;
}
}
}
if (target == null)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
return true;
}
if (target is Character character)
{
var currentOrderInfo = character.GetCurrentOrderWithTopPriority();
if (currentOrderInfo?.Identifier == OrderIdentifier)
{
if (OrderOption.IsEmpty)
{
return true;
}
else
{
return currentOrderInfo?.Option == OrderOption;
}
}
return false;
}
return true;
}
private string GetEventName()
{
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
}
}
}
@@ -0,0 +1,116 @@
using System;
using System.Linq;
namespace Barotrauma
{
class MessageBoxAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
public Identifier Header { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier Text { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string IconStyle { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool HideCloseButton { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string CloseOnInput { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnInteractTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnPickUpTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnEquipTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnExitRoomName { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool IsTutorialObjective { get; set; }
private bool isFinished = false;
public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
public override void Update(float deltaTime)
{
if (isFinished) { return; }
#if CLIENT
CreateMessageBox();
if (IsTutorialObjective && GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
tutorialMode.Tutorial?.TriggerTutorialSegment(new Tutorials.Tutorial.Segment(Text, CreateMessageBox));
}
#endif
isFinished = true;
}
#if CLIENT
public void CreateMessageBox()
{
new GUIMessageBox(
headerText: TextManager.Get(Header),
text: RichString.Rich(TextManager.ParseInputTypes(TextManager.Get(Text).Fallback(Text.ToString()), useColorHighlight: true)),
buttons: Array.Empty<LocalizedString>(),
type: GUIMessageBox.Type.Tutorial,
iconStyle: IconStyle,
autoCloseCondition: GetAutoCloseCondition(),
hideCloseButton: HideCloseButton);
}
#endif
private Func<bool> GetAutoCloseCondition()
{
var character = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Character;
Func<bool> autoCloseCondition = null;
if (!string.IsNullOrEmpty(CloseOnInput) && Enum.TryParse(CloseOnInput, true, out InputType closeOnInput))
{
#if CLIENT
autoCloseCondition = () => PlayerInput.KeyDown(closeOnInput);
#endif
}
else if (!CloseOnInteractTag.IsEmpty)
{
autoCloseCondition = () => character?.SelectedItem != null && character.SelectedItem.HasTag(CloseOnInteractTag);
}
else if (!CloseOnPickUpTag.IsEmpty)
{
autoCloseCondition = () => character?.Inventory != null && character.Inventory.FindItemByTag(CloseOnPickUpTag, recursive: true) != null;
}
else if (!CloseOnEquipTag.IsEmpty)
{
autoCloseCondition = () => character != null && character.HasEquippedItem(CloseOnEquipTag);
}
else if (!CloseOnExitRoomName.IsEmpty)
{
autoCloseCondition = () => character?.CurrentHull != null && character.CurrentHull.RoomName.ToIdentifier() != CloseOnExitRoomName;
}
return autoCloseCondition;
}
public override bool IsFinished(ref string goToLabel)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MessageBoxAction)}";
}
}
}
@@ -145,9 +145,9 @@ namespace Barotrauma
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
outmsg.Write((byte)EventManager.NetworkEventType.MISSION);
outmsg.Write(prefab.Identifier);
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
outmsg.WriteByte((byte)EventManager.NetworkEventType.MISSION);
outmsg.WriteIdentifier(prefab.Identifier);
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
}
}
@@ -57,6 +57,9 @@ namespace Barotrauma
private readonly HashSet<Identifier> targetModuleTags = new HashSet<Identifier>();
[Serialize(true, IsPropertySaveable.Yes, description: "If false, we won't spawn another character if one with the same identifier has already been spawned.")]
public bool AllowDuplicates { get; set; }
[Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")]
public string TargetModuleTags
{
@@ -115,6 +118,12 @@ namespace Barotrauma
HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier);
if (humanPrefab != null)
{
if (!AllowDuplicates &&
Character.CharacterList.Any(c => c.Info?.HumanPrefabIds.NpcIdentifier == NPCIdentifier && c.Info?.HumanPrefabIds.NpcSetIdentifier == NPCSetIdentifier))
{
spawned = true;
return;
}
ISpatialEntity spawnPos = GetSpawnPos();
if (spawnPos != null)
{
@@ -145,6 +154,11 @@ namespace Barotrauma
}
else if (!SpeciesName.IsEmpty)
{
if (!AllowDuplicates && Character.CharacterList.Any(c => c.SpeciesName == SpeciesName))
{
spawned = true;
return;
}
ISpatialEntity spawnPos = GetSpawnPos();
if (spawnPos != null)
{
@@ -1,10 +1,7 @@
using Barotrauma.Extensions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -36,6 +33,7 @@ namespace Barotrauma
("crew", v => TagCrew()),
("humanprefabidentifier", TagHumansByIdentifier),
("structureidentifier", TagStructuresByIdentifier),
("structurespecialtag", TagStructuresBySpecialTag),
("itemidentifier", TagItemsByIdentifier),
("itemtag", TagItemsByTag),
("hullname", TagHullsByName)
@@ -100,6 +98,11 @@ namespace Barotrauma
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
}
private void TagStructuresBySpecialTag(Identifier tag)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.SpecialTag.ToIdentifier() == tag);
}
private void TagItemsByIdentifier(Identifier identifier)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.Prefab.Identifier == identifier);
@@ -0,0 +1,32 @@
namespace Barotrauma
{
class TutorialCompleteAction : EventAction
{
private bool isFinished;
public TutorialCompleteAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
public override void Update(float deltaTime)
{
if (isFinished) { return; }
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
tutorialMode.Tutorial?.Complete();
}
#endif
isFinished = true;
}
public override bool IsFinished(ref string goToLabel)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
}
}
@@ -0,0 +1,95 @@
#if CLIENT
using Barotrauma.Tutorials;
#endif
namespace Barotrauma
{
class TutorialSegmentAction : EventAction
{
public enum SegmentActionType { Trigger, Complete, Remove };
[Serialize(SegmentActionType.Trigger, IsPropertySaveable.Yes)]
public SegmentActionType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier Id { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier ObjectiveTextTag { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool AutoPlayVideo { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TextTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string VideoFile { get; set; }
[Serialize(450, IsPropertySaveable.Yes)]
public int Width { get; set; }
[Serialize(80, IsPropertySaveable.Yes)]
public int Height { get; set; }
#if CLIENT
private readonly Tutorial.Segment segment;
#endif
private bool isFinished;
public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
#if CLIENT
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger)
{
segment = new Tutorial.Segment(Id, ObjectiveTextTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center),
new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height));
}
#endif
}
public override void Update(float deltaTime)
{
if (isFinished) { return; }
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
if (tutorialMode.Tutorial is Tutorial tutorial)
{
switch (Type)
{
case SegmentActionType.Trigger:
tutorial.TriggerTutorialSegment(segment);
break;
case SegmentActionType.Complete:
tutorial.CompleteTutorialSegment(Id);
break;
case SegmentActionType.Remove:
tutorial.RemoveTutorialSegment(Id);
break;
}
}
}
else
{
DebugConsole.ShowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!");
}
#endif
isFinished = true;
}
public override bool IsFinished(ref string goToLabel)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
}
}
@@ -55,9 +55,9 @@ namespace Barotrauma
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
outmsg.Write((byte)EventManager.NetworkEventType.UNLOCKPATH);
outmsg.Write((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection));
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
outmsg.WriteByte((byte)EventManager.NetworkEventType.UNLOCKPATH);
outmsg.WriteUInt16((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection));
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
}
}
@@ -109,6 +109,7 @@ namespace Barotrauma
subs[1].FlipX();
#if SERVER
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
roundEndTimer = RoundEndDuration;
#endif
}
@@ -3,7 +3,9 @@ using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using PositionType = Barotrauma.Level.PositionType;
namespace Barotrauma
{
@@ -29,6 +31,25 @@ namespace Barotrauma
private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
private readonly PositionType positionType = PositionType.Cave;
/// <remarks>
/// The list order is important.
/// It defines the order in which we "override" <see cref="positionType"/> in case no valid position types are found
/// in the level when generating them in <see cref="Level.GenerateMissionResources(ItemPrefab, int, PositionType, out float)"/>.
/// </remarks>
public static readonly ImmutableArray<PositionType> ValidPositionTypes = new PositionType[]
{
PositionType.Cave,
PositionType.SidePath,
PositionType.MainPath,
PositionType.AbyssCave,
}.ToImmutableArray();
/// <summary>
/// Percentage. Value between 0 and 1.
/// </summary>
private readonly float resourceHandoverAmount;
public override IEnumerable<Vector2> SonarPositions
{
get
@@ -39,8 +60,23 @@ namespace Barotrauma
}
}
public override LocalizedString SuccessMessage => ModifyMessage(base.SuccessMessage);
public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage);
public override LocalizedString Description => ModifyMessage(description);
public override LocalizedString Name => ModifyMessage(base.Name, false);
public override LocalizedString SonarLabel => ModifyMessage(base.SonarLabel, false);
public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
{
var positionType = prefab.ConfigElement.GetAttributeEnum("PositionType", in this.positionType);
if (ValidPositionTypes.Contains(positionType))
{
this.positionType = positionType;
}
float handoverAmount = prefab.ConfigElement.GetAttributeFloat("ResourceHandoverAmount", 0.0f);
resourceHandoverAmount = Math.Clamp(handoverAmount, 0.0f, 1.0f);
var configElement = prefab.ConfigElement.GetChildElement("Items");
foreach (var c in configElement.GetChildElements("Item"))
{
@@ -92,27 +128,28 @@ namespace Barotrauma
caves.Clear();
if (IsClient) { return; }
foreach (var kvp in resourceClusters)
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
{
var prefab = ItemPrefab.Find(null, kvp.Key);
if (prefab == null)
if (!(MapEntityPrefab.FindByIdentifier(identifier) is ItemPrefab prefab))
{
DebugConsole.ThrowError("Error in MineralMission - " +
"couldn't find an item prefab with the identifier " + kvp.Key);
DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")");
continue;
}
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.Amount, out float rotation);
if (spawnedResources.Count < kvp.Value.Amount)
{
DebugConsole.ThrowError("Error in MineralMission - " +
"spawned " + spawnedResources.Count + "/" + kvp.Value.Amount + " of " + prefab.Name);
}
if (spawnedResources.None()) { continue; }
this.spawnedResources.Add(kvp.Key, spawnedResources);
foreach (Level.Cave cave in Level.Loaded.Caves)
var spawnedResources = level.GenerateMissionResources(prefab, cluster.Amount, positionType, out float rotation);
if (spawnedResources.Count < cluster.Amount)
{
foreach (Item spawnedResource in spawnedResources)
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{cluster.Amount} of {prefab.Name}");
}
if (spawnedResources.None()) { continue; }
this.spawnedResources.Add(identifier, spawnedResources);
foreach (var cave in Level.Loaded.Caves)
{
foreach (var spawnedResource in spawnedResources)
{
if (cave.Area.Contains(spawnedResource.WorldPosition))
{
@@ -123,6 +160,7 @@ namespace Barotrauma
}
}
}
CalculateMissionClusterPositions();
FindRelevantLevelResources();
}
@@ -151,6 +189,28 @@ namespace Barotrauma
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
if (!IsClient)
{
// When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission)
var handoverResources = new List<Item>();
foreach (Identifier identifier in resourceClusters.Keys)
{
if (relevantLevelResources.TryGetValue(identifier, out var availableResources))
{
var collectedResources = availableResources.Where(HasBeenCollected);
if (collectedResources.Count() < 1) { continue; }
int handoverCount = (int)MathF.Round(resourceHandoverAmount * collectedResources.Count());
for (int i = 0; i < handoverCount; i++)
{
handoverResources.Add(collectedResources.ElementAt(i));
}
}
}
foreach (var resource in handoverResources)
{
resource.Remove();
}
}
GiveReward();
completed = true;
}
@@ -237,5 +297,27 @@ namespace Barotrauma
missionClusterPositions.Add((kvp.Key, pos));
}
}
protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true)
{
int i = 1;
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
{
Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? "");
Replace($"[resourcequantity{i}]", cluster.Amount.ToString());
i++;
}
Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount));
return message;
void Replace(string find, string replace)
{
if (color)
{
replace = $"‖color:gui.orange‖{replace}‖end‖";
}
message = message.Replace(find, replace);
}
}
}
}
@@ -41,7 +41,7 @@ namespace Barotrauma
public readonly ImmutableArray<LocalizedString> Headers;
public readonly ImmutableArray<LocalizedString> Messages;
public LocalizedString Name => Prefab.Name;
public virtual LocalizedString Name => Prefab.Name;
private readonly LocalizedString successMessage;
public virtual LocalizedString SuccessMessage
@@ -276,6 +276,10 @@ namespace Barotrauma
partial void ShowMessageProjSpecific(int missionState);
protected virtual LocalizedString ModifyMessage(LocalizedString message, bool color = true)
{
return message;
}
private void TryTriggerEvents(int state)
{