Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into unstable
This commit is contained in:
@@ -87,11 +87,11 @@ namespace Barotrauma
|
||||
if (_attackLimb != value)
|
||||
{
|
||||
_previousAttackLimb = _attackLimb;
|
||||
_previousAttackLimb?.AttachedRope?.Snap();
|
||||
if (_previousAttackLimb != null && _previousAttackLimb.attack.SnapRopeOnNewAttack) { _previousAttackLimb.AttachedRope?.Snap(); }
|
||||
}
|
||||
else if (_attackLimb != null && _attackLimb.attack.CoolDownTimer <= 0)
|
||||
{
|
||||
_attackLimb.AttachedRope?.Snap();
|
||||
if (_attackLimb != null && _attackLimb.attack.SnapRopeOnNewAttack) { _attackLimb.AttachedRope?.Snap(); }
|
||||
}
|
||||
_attackLimb = value;
|
||||
attackVector = null;
|
||||
@@ -3660,7 +3660,7 @@ namespace Barotrauma
|
||||
targetDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
float margin = 30000;
|
||||
float margin = Level.OutsideBoundsCurrentMargin;
|
||||
if (pos.X < -margin)
|
||||
{
|
||||
// Too far left
|
||||
@@ -3847,7 +3847,7 @@ namespace Barotrauma
|
||||
|
||||
public override void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write((byte)State);
|
||||
msg.WriteByte((byte)State);
|
||||
PetBehavior?.ServerWrite(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -466,7 +466,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
|
||||
|
||||
//if someone is grabbing the bot and the bot isn't trying to run anywhere, let them keep dragging and "control" the bot
|
||||
if (Character.SelectedBy == null || run)
|
||||
{
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
|
||||
}
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
@@ -511,9 +516,9 @@ namespace Barotrauma
|
||||
{
|
||||
newDir = Direction.Left;
|
||||
}
|
||||
if (Character.SelectedConstruction != null)
|
||||
if (Character.SelectedItem != null)
|
||||
{
|
||||
Character.SelectedConstruction.SecondaryUse(deltaTime, Character);
|
||||
Character.SelectedItem.SecondaryUse(deltaTime, Character);
|
||||
}
|
||||
}
|
||||
else if (AutoFaceMovement && Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater)
|
||||
@@ -2148,7 +2153,7 @@ namespace Barotrauma
|
||||
if (c.Removed) { continue; }
|
||||
if (c.TeamID != team) { continue; }
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
if (c.SelectedConstruction == target.Item)
|
||||
if (c.SelectedItem == target.Item)
|
||||
{
|
||||
operatingCharacter = c;
|
||||
return true;
|
||||
@@ -2185,7 +2190,7 @@ namespace Barotrauma
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (c.SelectedConstruction == target.Item)
|
||||
if (c.SelectedItem == target.Item)
|
||||
{
|
||||
// If the other character is player, don't try to operate
|
||||
other = c;
|
||||
|
||||
@@ -79,7 +79,8 @@ namespace Barotrauma
|
||||
{
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true)
|
||||
{
|
||||
GetNodePenalty = GetNodePenalty
|
||||
GetNodePenalty = GetNodePenalty,
|
||||
GetSingleNodePenalty = GetSingleNodePenalty
|
||||
};
|
||||
|
||||
this.canOpenDoors = canOpenDoors;
|
||||
@@ -360,7 +361,7 @@ namespace Barotrauma
|
||||
Ladder nextLadder = GetNextLadder();
|
||||
var ladders = currentLadder ?? nextLadder;
|
||||
bool useLadders = canClimb && ladders != null && steering.LengthSquared() > 0.1f && (!isDiving || steering.Y > 1);
|
||||
if (useLadders && character.SelectedConstruction != ladders.Item)
|
||||
if (useLadders && character.SelectedSecondaryItem != ladders.Item)
|
||||
{
|
||||
if (character.CanInteractWith(ladders.Item))
|
||||
{
|
||||
@@ -372,7 +373,7 @@ namespace Barotrauma
|
||||
// Try to select the previous ladder, unless it's already selected, unless the previous ladder is not adjacent to the current ladder.
|
||||
// The intention of this code is to prevent the bots from dropping from the "double ladders".
|
||||
var previousLadders = currentPath.PrevNode?.Ladders;
|
||||
if (previousLadders != null && previousLadders != ladders && character.SelectedConstruction != previousLadders.Item &&
|
||||
if (previousLadders != null && previousLadders != ladders && character.SelectedSecondaryItem != previousLadders.Item &&
|
||||
character.CanInteractWith(previousLadders.Item) && Math.Abs(previousLadders.Item.WorldPosition.X - ladders.Item.WorldPosition.X) < 5)
|
||||
{
|
||||
previousLadders.Item.TryInteract(character, forceSelectKey: true);
|
||||
@@ -382,8 +383,7 @@ namespace Barotrauma
|
||||
var collider = character.AnimController.Collider;
|
||||
if (character.IsClimbing && !useLadders)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
character.StopClimbing();
|
||||
}
|
||||
if (character.IsClimbing && useLadders)
|
||||
{
|
||||
@@ -402,15 +402,14 @@ namespace Barotrauma
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
bool isAboveFloor = heightFromFloor > -0.1f;
|
||||
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
||||
if (isAboveFloor && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||
if (isAboveFloor && !currentPath.IsAtEndNode && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
character.StopClimbing();
|
||||
}
|
||||
else if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
{
|
||||
// Try to change the ladder (hatches between two submarines)
|
||||
if (character.SelectedConstruction != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
|
||||
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
|
||||
{
|
||||
if (nextLadder.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
@@ -418,7 +417,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10)
|
||||
if (!currentPath.IsAtEndNode && (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10))
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
@@ -528,7 +527,7 @@ namespace Barotrauma
|
||||
{
|
||||
// We'll want this to run each time, because the delegate is used to find a valid button component.
|
||||
bool canAccessButtons = false;
|
||||
foreach (var button in door.Item.GetConnectedComponents<Controller>(true))
|
||||
foreach (var button in door.Item.GetConnectedComponents<Controller>(true, connectionFilter: c => c.Name == "toggle" || c.Name == "set_state"))
|
||||
{
|
||||
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
|
||||
{
|
||||
@@ -676,6 +675,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition);
|
||||
//heavily prefer buttons linked to the door, so sub builders can help the bots figure out which button to use by linking them
|
||||
if (door.Item.linkedTo.Contains(button.Item)) { distance *= 0.1f; }
|
||||
if (closestButton == null || distance < closestDist && character.CanSeeTarget(button.Item))
|
||||
{
|
||||
closestButton = button;
|
||||
@@ -756,42 +757,8 @@ namespace Barotrauma
|
||||
private float? GetNodePenalty(PathNode node, PathNode nextNode)
|
||||
{
|
||||
if (character == null) { return 0.0f; }
|
||||
if (nextNode.Waypoint.isObstructed) { return null; }
|
||||
float penalty = 0.0f;
|
||||
if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f)
|
||||
{
|
||||
var door = nextNode.Waypoint.ConnectedDoor;
|
||||
if (door == null)
|
||||
{
|
||||
penalty = 100.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CanAccessDoor(door, button =>
|
||||
{
|
||||
// Ignore buttons that are on the wrong side of the door
|
||||
if (door.IsHorizontal)
|
||||
{
|
||||
if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float? penalty = GetSingleNodePenalty(nextNode);
|
||||
if (penalty == null) { return null; }
|
||||
bool nextNodeAboveWaterLevel = nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y;
|
||||
//non-humanoids can't climb up ladders
|
||||
if (!(character.AnimController is HumanoidAnimController))
|
||||
@@ -839,6 +806,47 @@ namespace Barotrauma
|
||||
return penalty;
|
||||
}
|
||||
|
||||
private float? GetSingleNodePenalty(PathNode node)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { return null; }
|
||||
if (node.IsBlocked()) { return null; }
|
||||
float penalty = 0.0f;
|
||||
if (node.Waypoint.ConnectedGap != null && node.Waypoint.ConnectedGap.Open < 0.9f)
|
||||
{
|
||||
var door = node.Waypoint.ConnectedDoor;
|
||||
if (door == null)
|
||||
{
|
||||
penalty = 100.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CanAccessDoor(door, button =>
|
||||
{
|
||||
// Ignore buttons that are on the wrong side of the door
|
||||
if (door.IsHorizontal)
|
||||
{
|
||||
if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return penalty;
|
||||
}
|
||||
|
||||
public static float smallRoomSize = 500;
|
||||
public void Wander(float deltaTime, float wallAvoidDistance = 150, bool stayStillInTightSpace = true)
|
||||
{
|
||||
|
||||
@@ -14,13 +14,11 @@ namespace Barotrauma
|
||||
public readonly LanguageIdentifier Language;
|
||||
|
||||
public readonly List<NPCConversation> Conversations;
|
||||
public readonly Dictionary<Identifier, NPCPersonalityTrait> PersonalityTraits;
|
||||
|
||||
public NPCConversationCollection(NPCConversationsFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Language = element.GetAttributeIdentifier("language", "English").ToLanguageIdentifier();
|
||||
Conversations = new List<NPCConversation>();
|
||||
PersonalityTraits = new Dictionary<Identifier, NPCPersonalityTrait>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
Identifier elemName = new Identifier(subElement.Name.LocalName);
|
||||
@@ -28,11 +26,6 @@ namespace Barotrauma
|
||||
{
|
||||
Conversations.Add(new NPCConversation(subElement));
|
||||
}
|
||||
else if (elemName == "PersonalityTrait")
|
||||
{
|
||||
var personalityTrait = new NPCPersonalityTrait(subElement);
|
||||
PersonalityTraits.Add(personalityTrait.Name, personalityTrait);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,10 +354,13 @@ namespace Barotrauma
|
||||
|
||||
private static float GetConversationProbability(NPCConversation conversation)
|
||||
{
|
||||
int index = previousConversations.IndexOf(conversation);
|
||||
if (index < 0) return 10.0f;
|
||||
//prefer choosing conversations with more flags (= for more specific situations) when possible
|
||||
float baseProbability = MathF.Pow(conversation.Flags.Count + 1, 2);
|
||||
|
||||
return 1.0f - 1.0f / (index + 1);
|
||||
int index = previousConversations.IndexOf(conversation);
|
||||
if (index < 0) { return baseProbability * 10.0f; }
|
||||
|
||||
return baseProbability + 1.0f - 1.0f / (index + 1);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -529,6 +530,15 @@ namespace Barotrauma
|
||||
}
|
||||
return canEquip;
|
||||
}
|
||||
protected bool CheckItemIdentifiersOrTags(Item item, ImmutableHashSet<Identifier> identifiersOrTags)
|
||||
{
|
||||
if (identifiersOrTags.Contains(item.Prefab.Identifier)) { return true; }
|
||||
foreach (var identifier in identifiersOrTags)
|
||||
{
|
||||
if (item.HasTag(identifier)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool CanEquip(Item item) => CanEquip(character, item);
|
||||
}
|
||||
|
||||
+28
-7
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using static Barotrauma.AIObjectiveFindSafety;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -512,6 +513,23 @@ namespace Barotrauma
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = weapon.CombatPriority;
|
||||
if (weapon is RepairTool repairTool)
|
||||
{
|
||||
switch (repairTool.UsableIn)
|
||||
{
|
||||
case RepairTool.UseEnvironment.Air:
|
||||
if (character.InWater) { continue; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.Water:
|
||||
if (!character.InWater) { continue; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.None:
|
||||
continue;
|
||||
case RepairTool.UseEnvironment.Both:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prioritizeMelee)
|
||||
{
|
||||
if (weapon is MeleeWeapon)
|
||||
@@ -895,11 +913,14 @@ namespace Barotrauma
|
||||
|
||||
private void RemoveFollowTarget()
|
||||
{
|
||||
if (arrestingRegistered)
|
||||
if (followTargetObjective != null)
|
||||
{
|
||||
followTargetObjective.Completed -= OnArrestTargetReached;
|
||||
if (arrestingRegistered)
|
||||
{
|
||||
followTargetObjective.Completed -= OnArrestTargetReached;
|
||||
}
|
||||
RemoveSubObjective(ref followTargetObjective);
|
||||
}
|
||||
RemoveSubObjective(ref followTargetObjective);
|
||||
arrestingRegistered = false;
|
||||
}
|
||||
|
||||
@@ -950,7 +971,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Seeks for more ammunition. Creates a new subobjective.
|
||||
/// </summary>
|
||||
private void SeekAmmunition(Identifier[] ammunitionIdentifiers)
|
||||
private void SeekAmmunition(ImmutableHashSet<Identifier> ammunitionIdentifiers)
|
||||
{
|
||||
retreatTarget = null;
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
@@ -985,7 +1006,7 @@ namespace Barotrauma
|
||||
HumanAIController.UnequipEmptyItems(Weapon);
|
||||
RelatedItem item = null;
|
||||
Item ammunition = null;
|
||||
Identifier[] ammunitionIdentifiers = null;
|
||||
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
|
||||
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
{
|
||||
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
|
||||
@@ -1011,8 +1032,8 @@ namespace Barotrauma
|
||||
if (ammunitionIdentifiers != null)
|
||||
{
|
||||
// Try reload ammunition from inventory
|
||||
bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
||||
ammunition = character.Inventory.FindItem(i => ammunitionIdentifiers.Any(id => id == i.Prefab.Identifier || i.HasTag(id)) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
||||
ammunition = character.Inventory.FindItem(i => CheckItemIdentifiersOrTags(i, ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
||||
if (ammunition != null)
|
||||
{
|
||||
var container = Weapon.GetComponent<ItemContainer>();
|
||||
|
||||
+11
-6
@@ -1,6 +1,8 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -11,14 +13,14 @@ namespace Barotrauma
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
public Identifier[] ignoredContainerIdentifiers;
|
||||
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
|
||||
public bool checkInventory = true;
|
||||
|
||||
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs and in some cases also enemy NPCs, like pirates)
|
||||
private readonly bool spawnItemIfNotFound;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
public readonly Identifier[] itemIdentifiers;
|
||||
public readonly ImmutableHashSet<Identifier> itemIdentifiers;
|
||||
public readonly ItemContainer container;
|
||||
private readonly Item item;
|
||||
public Item ItemToContain { get; private set; }
|
||||
@@ -61,9 +63,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public AIObjectiveContainItem(Character character, Identifier itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: this(character, new Identifier[] { itemIdentifier }, container, objectiveManager, priorityModifier, spawnItemIfNotFound) { }
|
||||
: this(character, itemIdentifier.ToEnumerable().ToImmutableHashSet(), container, objectiveManager, priorityModifier, spawnItemIfNotFound) { }
|
||||
|
||||
public AIObjectiveContainItem(Character character, Identifier[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
public AIObjectiveContainItem(Character character, ImmutableHashSet<Identifier> itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
@@ -102,7 +104,10 @@ namespace Barotrauma
|
||||
return containedItemCount >= ItemCount;
|
||||
}
|
||||
|
||||
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && i.HasAccess(character);
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
return CheckItemIdentifiersOrTags(item, itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
|
||||
+5
-3
@@ -1,5 +1,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -13,7 +15,7 @@ namespace Barotrauma
|
||||
//can either be a tag or an identifier
|
||||
private readonly string[] itemIdentifiers;
|
||||
private readonly ItemContainer sourceContainer;
|
||||
private ItemContainer targetContainer;
|
||||
private readonly ItemContainer targetContainer;
|
||||
private readonly Item targetItem;
|
||||
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
@@ -127,7 +129,7 @@ namespace Barotrauma
|
||||
RemoveExistingPredicate = RemoveExistingPredicate,
|
||||
RemoveMax = RemoveExistingMax,
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = sourceContainer != null ? new Identifier[] { sourceContainer.Item.Prefab.Identifier } : null
|
||||
ignoredContainerIdentifiers = sourceContainer?.Item.Prefab.Identifier.ToEnumerable().ToImmutableHashSet()
|
||||
},
|
||||
onCompleted: () => IsCompleted = true,
|
||||
onAbandon: () => Abandon = true);
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ namespace Barotrauma
|
||||
TargetName = Leak.FlowTargetHull?.DisplayName,
|
||||
requiredCondition = () =>
|
||||
Leak.Submarine == character.Submarine &&
|
||||
Leak.linkedTo.Any(e => e is Hull h && character.CurrentHull == h),
|
||||
Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))),
|
||||
endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.CurrentHull),
|
||||
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
|
||||
SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
|
||||
|
||||
+8
-8
@@ -21,10 +21,10 @@ namespace Barotrauma
|
||||
public float TargetCondition { get; set; } = 1;
|
||||
public bool AllowDangerousPressure { get; set; }
|
||||
|
||||
public readonly ImmutableArray<Identifier> IdentifiersOrTags;
|
||||
public readonly ImmutableHashSet<Identifier> IdentifiersOrTags;
|
||||
|
||||
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
|
||||
private bool spawnItemIfNotFound = false;
|
||||
private readonly bool spawnItemIfNotFound = false;
|
||||
|
||||
private Item targetItem;
|
||||
private readonly Item originalTarget;
|
||||
@@ -32,8 +32,8 @@ namespace Barotrauma
|
||||
private bool isDoneSeeking;
|
||||
public Item TargetItem => targetItem;
|
||||
private int currSearchIndex;
|
||||
public Identifier[] ignoredContainerIdentifiers;
|
||||
public Identifier[] ignoredIdentifiersOrTags;
|
||||
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
|
||||
public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private readonly bool checkInventory;
|
||||
@@ -93,8 +93,8 @@ namespace Barotrauma
|
||||
Equip = equip;
|
||||
this.spawnItemIfNotFound = spawnItemIfNotFound;
|
||||
this.checkInventory = checkInventory;
|
||||
IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray();
|
||||
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray();
|
||||
IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableHashSet();
|
||||
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public static IEnumerable<Identifier> ParseGearTags(IEnumerable<Identifier> identifiersOrTags)
|
||||
@@ -558,11 +558,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (ignoredIdentifiersOrTags != null && ignoredIdentifiersOrTags.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { return false; }
|
||||
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; }
|
||||
return IdentifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && item.Prefab.VariantOf == id));
|
||||
return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
public bool RequireAllItems { get; set; }
|
||||
|
||||
private readonly ImmutableArray<Identifier> gearTags;
|
||||
private readonly Identifier[] ignoredTags;
|
||||
private readonly ImmutableHashSet<Identifier> ignoredTags;
|
||||
private bool subObjectivesCreated;
|
||||
|
||||
public readonly HashSet<Item> achievedItems = new HashSet<Item>();
|
||||
@@ -33,7 +33,7 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItems(Character character, AIObjectiveManager objectiveManager, IEnumerable<Identifier> identifiersOrTags, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTags = AIObjectiveGetItem.ParseGearTags(identifiersOrTags).ToImmutableArray();
|
||||
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToArray();
|
||||
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => subObjectivesCreated && subObjectives.None();
|
||||
|
||||
+9
-4
@@ -196,9 +196,10 @@ namespace Barotrauma
|
||||
character.AIController.SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
if (!character.IsClimbing)
|
||||
character.SelectedItem = null;
|
||||
if (character.SelectedSecondaryItem != null && !character.SelectedSecondaryItem.IsLadder)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
character.SelectedSecondaryItem = null;
|
||||
}
|
||||
if (Target is Entity e)
|
||||
{
|
||||
@@ -594,6 +595,10 @@ namespace Barotrauma
|
||||
{
|
||||
return c.CurrentHull;
|
||||
}
|
||||
else if (target is Structure structure)
|
||||
{
|
||||
return Hull.FindHull(structure.Position, useWorldCoordinates: false);
|
||||
}
|
||||
else if (target is Gap g)
|
||||
{
|
||||
return g.FlowTargetHull;
|
||||
@@ -647,7 +652,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder)
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder && !PathSteering.CurrentPath.IsAtEndNode)
|
||||
{
|
||||
if (Target.WorldPosition.Y > character.WorldPosition.Y)
|
||||
{
|
||||
@@ -694,7 +699,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Target is Item item)
|
||||
{
|
||||
if (!character.IsClimbing && character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
|
||||
if (character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
|
||||
}
|
||||
else if (Target is Character targetCharacter)
|
||||
{
|
||||
|
||||
+8
-8
@@ -161,10 +161,7 @@ namespace Barotrauma
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
character.SelectedItem = null;
|
||||
|
||||
CleanupItems(deltaTime);
|
||||
|
||||
@@ -262,7 +259,8 @@ namespace Barotrauma
|
||||
// Check that there is no unsafe hulls on the way to the target
|
||||
if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; }
|
||||
return true;
|
||||
}, endNodeFilter: node => !isCurrentHullAllowed | !IsForbidden(node.Waypoint.CurrentHull));
|
||||
//don't stop at ladders when idling
|
||||
}, endNodeFilter: node => node.Waypoint.Ladders == null && (!isCurrentHullAllowed || !IsForbidden(node.Waypoint.CurrentHull)));
|
||||
if (path.Unreachable)
|
||||
{
|
||||
//can't go to this room, remove it from the list and try another room
|
||||
@@ -293,7 +291,9 @@ namespace Barotrauma
|
||||
}
|
||||
else if (currentTarget != null)
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1,
|
||||
nodeFilter: node => node.Waypoint.CurrentHull != null,
|
||||
endNodeFilter: node => node.Waypoint.Ladders == null);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -310,7 +310,7 @@ namespace Barotrauma
|
||||
if (character.AnimController.GetHeightFromFloor() < 0.1f)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
character.SelectedSecondaryItem = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -375,7 +375,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
chairCheckTimer -= deltaTime;
|
||||
if (chairCheckTimer <= 0.0f && character.SelectedConstruction == null)
|
||||
if (chairCheckTimer <= 0.0f && character.SelectedSecondaryItem == null)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ namespace Barotrauma
|
||||
if (ValidContainableItemIdentifiers.None())
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ShowError($"No valid containable item identifiers found for the Load Item objective targeting {Container}");
|
||||
DebugConsole.LogError($"No valid containable item identifiers found for the Load Item objective targeting {Container}");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
@@ -250,7 +250,7 @@ namespace Barotrauma
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ShowError($"Unexpected target condition \"{TargetItemCondition}\" in local function GetConditionBasedProperty");
|
||||
DebugConsole.LogError($"Unexpected target condition \"{TargetItemCondition}\" in local function GetConditionBasedProperty");
|
||||
#endif
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ namespace Barotrauma
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ShowError($"Unexpected target condition \"{targetCondition}\" in AIObjectiveLoadItems.ItemMatchesTargetCondition");
|
||||
DebugConsole.LogError($"Unexpected target condition \"{targetCondition}\" in AIObjectiveLoadItems.ItemMatchesTargetCondition");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
+17
-4
@@ -37,6 +37,8 @@ namespace Barotrauma
|
||||
public Func<bool> completionCondition;
|
||||
private bool isDoneOperating;
|
||||
|
||||
public float? OverridePriority = null;
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
@@ -52,7 +54,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOrder)
|
||||
if (OverridePriority.HasValue)
|
||||
{
|
||||
Priority = OverridePriority.Value;
|
||||
}
|
||||
else if (isOrder)
|
||||
{
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
}
|
||||
@@ -135,7 +141,7 @@ namespace Barotrauma
|
||||
float value = CumulatedDevotion + (max * PriorityModifier);
|
||||
Priority = MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
else
|
||||
else if (!OverridePriority.HasValue)
|
||||
{
|
||||
float value = CumulatedDevotion + (AIObjectiveManager.LowestOrderPriority * PriorityModifier);
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
@@ -204,8 +210,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (!character.IsClimbing && character.CanInteractWith(target.Item, out _, checkLinked: false))
|
||||
{
|
||||
HumanAIController.FaceTarget(target.Item);
|
||||
if (character.SelectedConstruction != target.Item)
|
||||
if (target.Item.GetComponent<Controller>() is not Controller { ControlCharacterPose: true })
|
||||
{
|
||||
HumanAIController.FaceTarget(target.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
HumanAIController.SteeringManager.Reset();
|
||||
}
|
||||
if (character.SelectedItem != target.Item && character.SelectedSecondaryItem != target.Item)
|
||||
{
|
||||
target.Item.TryInteract(character, forceSelectKey: true);
|
||||
}
|
||||
|
||||
+4
-6
@@ -26,7 +26,7 @@ namespace Barotrauma
|
||||
private bool IsRepairing() => IsRepairing(character, Item);
|
||||
private readonly bool isPriority;
|
||||
|
||||
public static bool IsRepairing(Character character, Item item) => character.SelectedConstruction == item && item.Repairables.Any(r => r.CurrentFixer == character);
|
||||
public static bool IsRepairing(Character character, Item item) => character.SelectedItem == item && item.Repairables.Any(r => r.CurrentFixer == character);
|
||||
|
||||
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -165,7 +165,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!character.IsClimbing && character.CanInteractWith(Item, out _, checkLinked: false))
|
||||
if (character.CanInteractWith(Item, out _, checkLinked: false))
|
||||
{
|
||||
waitTimer += deltaTime;
|
||||
if (waitTimer < WaitTimeBeforeRepair) { return; }
|
||||
@@ -184,12 +184,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (!Abandon)
|
||||
{
|
||||
if (character.SelectedConstruction != Item)
|
||||
if (character.SelectedItem != Item)
|
||||
{
|
||||
if (Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) ||
|
||||
Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true))
|
||||
{
|
||||
character.SelectedConstruction = Item;
|
||||
character.SelectedItem = Item;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -232,8 +232,6 @@ namespace Barotrauma
|
||||
previousCondition = -1;
|
||||
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
|
||||
{
|
||||
// Don't stop in ladders, because we can't interact with other items while holding the ladders.
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null,
|
||||
TargetName = Item.Name
|
||||
};
|
||||
if (repairTool != null)
|
||||
|
||||
+2
-2
@@ -67,7 +67,7 @@ namespace Barotrauma
|
||||
if (!ViableForRepair(item, character, HumanAIController)) { return false; };
|
||||
if (!Objectives.ContainsKey(item))
|
||||
{
|
||||
if (item != character.SelectedConstruction)
|
||||
if (item != character.SelectedItem)
|
||||
{
|
||||
if (NearlyFullCondition(item)) { return false; }
|
||||
}
|
||||
@@ -96,7 +96,7 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
var selectedItem = character.SelectedConstruction;
|
||||
var selectedItem = character.SelectedItem;
|
||||
if (selectedItem != null && AIObjectiveRepairItem.IsRepairing(character, selectedItem) && selectedItem.ConditionPercentage < 100)
|
||||
{
|
||||
// Don't stop fixing until completely done
|
||||
|
||||
+1
-1
@@ -278,7 +278,7 @@ namespace Barotrauma
|
||||
float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f;
|
||||
|
||||
//find which treatments are the most suitable to treat the character's current condition
|
||||
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false, predictFutureDuration: 10.0f);
|
||||
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, user: character, normalize: false, predictFutureDuration: 10.0f);
|
||||
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
|
||||
|
||||
@@ -3,9 +3,8 @@ using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -151,7 +150,7 @@ namespace Barotrauma
|
||||
public OrderPrefab(ContentXElement orderElement, OrdersFile file) : base(file, orderElement.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Name = TextManager.Get($"OrderName.{Identifier}");
|
||||
ContextualName = TextManager.Get($"OrderNameContextual.{Identifier}");
|
||||
ContextualName = TextManager.Get($"OrderNameContextual.{Identifier}").Fallback(Name);
|
||||
|
||||
string targetItemType = orderElement.GetAttributeString("targetitemtype", "");
|
||||
if (!string.IsNullOrWhiteSpace(targetItemType))
|
||||
@@ -435,7 +434,7 @@ namespace Barotrauma
|
||||
}
|
||||
catch (NotImplementedException e)
|
||||
{
|
||||
DebugConsole.ShowError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}");
|
||||
DebugConsole.LogError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace Barotrauma
|
||||
{
|
||||
public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode);
|
||||
public GetNodePenaltyHandler GetNodePenalty;
|
||||
public delegate float? GetSingleNodePenaltyHandler(PathNode node);
|
||||
public GetSingleNodePenaltyHandler GetSingleNodePenalty;
|
||||
|
||||
private readonly List<PathNode> nodes;
|
||||
private readonly bool isCharacter;
|
||||
@@ -282,8 +284,6 @@ namespace Barotrauma
|
||||
}
|
||||
//avoid stopping at a doorway
|
||||
if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; }
|
||||
//avoid stopping at a ladder
|
||||
if (node.Waypoint.Ladders != null) { node.TempDistance *= 10.0f; }
|
||||
}
|
||||
//optimization: node extremely far (> 100m / 800 m) from the end position, don't try to use it as an end node
|
||||
if (node.TempDistance > (InsideSubmarine ? 100.0f * 100.0f : 800.0f * 800.0f))
|
||||
@@ -325,15 +325,24 @@ namespace Barotrauma
|
||||
#endif
|
||||
return new SteeringPath(true);
|
||||
}
|
||||
var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
|
||||
return path;
|
||||
return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
|
||||
|
||||
bool IsWaypointVisible(PathNode node, Vector2 rayStart, bool checkVisibility = true)
|
||||
bool IsValidStartNode(PathNode node) => IsValidNode(node, (isCharacter, start), startNodeFilter);
|
||||
|
||||
bool IsValidEndNode(PathNode node) => IsValidNode(node, (isCharacter && checkVisibility, end), endNodeFilter);
|
||||
|
||||
bool IsValidNode(PathNode node, (bool check, Vector2 start) visibilityCheck, Func<PathNode, bool> extraFilter)
|
||||
{
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (checkVisibility && isCharacter)
|
||||
if (nodeFilter != null && !nodeFilter(node)) { return false; }
|
||||
if (extraFilter != null && !extraFilter(node)) { return false; }
|
||||
if (GetSingleNodePenalty != null && GetSingleNodePenalty(node) == null) { return false; }
|
||||
if (node.Waypoint.ConnectedGap != null)
|
||||
{
|
||||
var body = Submarine.PickBody(rayStart, node.TempPosition,
|
||||
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; }
|
||||
}
|
||||
if (visibilityCheck.check)
|
||||
{
|
||||
var body = Submarine.PickBody(visibilityCheck.start, node.TempPosition,
|
||||
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
{
|
||||
@@ -344,36 +353,6 @@ namespace Barotrauma
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsValidStartNode(PathNode node)
|
||||
{
|
||||
if (nodeFilter != null && !nodeFilter(node)) { return false; }
|
||||
if (startNodeFilter != null && !startNodeFilter(node)) { return false; }
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
// Always check the visibility for the start node
|
||||
if (!IsWaypointVisible(node, start)) { return false; }
|
||||
if (node.IsBlocked()) { return false; }
|
||||
if (node.Waypoint.ConnectedGap != null)
|
||||
{
|
||||
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsValidEndNode(PathNode node)
|
||||
{
|
||||
if (nodeFilter != null && !nodeFilter(node)) { return false; }
|
||||
if (endNodeFilter != null && !endNodeFilter(node)) { return false; }
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
// Only check the visibility for the end node when allowed (fix leaks)
|
||||
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; }
|
||||
if (node.IsBlocked()) { return false; }
|
||||
if (node.Waypoint.ConnectedGap != null)
|
||||
{
|
||||
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "", float minGapSize = 0)
|
||||
@@ -402,15 +381,13 @@ namespace Barotrauma
|
||||
foreach (PathNode node in nodes)
|
||||
{
|
||||
if (node.state != 1 || node.F > dist) { continue; }
|
||||
if (isCharacter && node.Waypoint.isObstructed) { continue; }
|
||||
if (filter != null && !filter(node)) { continue; }
|
||||
if (node.IsBlocked()) { continue; }
|
||||
if (node.Waypoint.ConnectedGap != null)
|
||||
{
|
||||
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; }
|
||||
}
|
||||
}
|
||||
dist = node.F;
|
||||
currNode = node;
|
||||
currNode = node;
|
||||
}
|
||||
|
||||
if (currNode == null || currNode == end) { break; }
|
||||
@@ -515,7 +492,4 @@ namespace Barotrauma
|
||||
|
||||
private bool CanFitThroughGap(Gap gap, float minWidth) => gap.IsHorizontal ? gap.RectHeight > minWidth : gap.RectWidth > minWidth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -115,6 +115,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAtEndNode => currentIndex >= nodes.Count - 1;
|
||||
|
||||
public List<WayPoint> Nodes
|
||||
{
|
||||
get { return nodes; }
|
||||
|
||||
@@ -444,7 +444,7 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
public void ServerEventWrite(IWriteMessage msg, Client client, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(IsAlive);
|
||||
msg.WriteBoolean(IsAlive);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (InWater || !CanWalk)
|
||||
{
|
||||
return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed);
|
||||
return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed + 0.0001f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -134,9 +134,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR };
|
||||
public enum Animation { None, Climbing, UsingItem, Struggle, CPR, UsingItemWhileClimbing };
|
||||
public Animation Anim;
|
||||
|
||||
public bool IsUsingItem => Anim == Animation.UsingItem || Anim == Animation.UsingItemWhileClimbing;
|
||||
public bool IsClimbing => Anim == Animation.Climbing || Anim == Animation.UsingItemWhileClimbing;
|
||||
|
||||
public Vector2 AimSourceWorldPos
|
||||
{
|
||||
get
|
||||
@@ -280,7 +283,7 @@ namespace Barotrauma
|
||||
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
|
||||
{
|
||||
useItemTimer = 0.5f;
|
||||
Anim = Animation.UsingConstruction;
|
||||
StartUsingItem();
|
||||
|
||||
if (!allowMovement)
|
||||
{
|
||||
@@ -359,8 +362,13 @@ namespace Barotrauma
|
||||
|
||||
Vector2 itemPos = aim ? aimPos : holdPos;
|
||||
|
||||
var controller = character.SelectedConstruction?.GetComponent<Controller>();
|
||||
var controller = character.SelectedItem?.GetComponent<Controller>();
|
||||
bool usingController = controller != null && !controller.AllowAiming;
|
||||
if (!usingController)
|
||||
{
|
||||
controller = character.SelectedSecondaryItem?.GetComponent<Controller>();
|
||||
usingController = controller != null && !controller.AllowAiming;
|
||||
}
|
||||
bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f;
|
||||
float itemAngle;
|
||||
Holdable holdable = item.GetComponent<Holdable>();
|
||||
@@ -722,5 +730,45 @@ namespace Barotrauma
|
||||
CalculateArmLengths();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartAnimation(Animation animation)
|
||||
{
|
||||
if (animation == Animation.UsingItem)
|
||||
{
|
||||
Anim = IsClimbing ? Animation.UsingItemWhileClimbing : Animation.UsingItem;
|
||||
}
|
||||
else if (animation == Animation.Climbing)
|
||||
{
|
||||
Anim = IsUsingItem ? Animation.UsingItemWhileClimbing : Animation.Climbing;
|
||||
}
|
||||
else
|
||||
{
|
||||
Anim = animation;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopAnimation(Animation animation)
|
||||
{
|
||||
if (animation == Animation.UsingItem)
|
||||
{
|
||||
Anim = IsClimbing ? Animation.Climbing : Animation.None;
|
||||
}
|
||||
else if (animation == Animation.Climbing)
|
||||
{
|
||||
Anim = IsUsingItem ? Animation.UsingItem : Animation.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
Anim = Animation.None;
|
||||
}
|
||||
}
|
||||
|
||||
public void StartUsingItem() => StartAnimation(Animation.UsingItem);
|
||||
|
||||
public void StartClimbing() => StartAnimation(Animation.Climbing);
|
||||
|
||||
public void StopUsingItem() => StopAnimation(Animation.UsingItem);
|
||||
|
||||
public void StopClimbing() => StopAnimation(Animation.Climbing);
|
||||
}
|
||||
}
|
||||
|
||||
+99
-66
@@ -237,13 +237,14 @@ namespace Barotrauma
|
||||
|
||||
public override void UpdateAnim(float deltaTime)
|
||||
{
|
||||
if (Frozen) return;
|
||||
if (Frozen) { return; }
|
||||
if (MainLimb == null) { return; }
|
||||
|
||||
levitatingCollider = !IsHanging;
|
||||
ColliderIndex = Crouching && !swimming ? 1 : 0;
|
||||
if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false ||
|
||||
character.SelectedConstruction?.GetComponent<Ladder>() != null ||
|
||||
if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
|
||||
(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
|
||||
character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
|
||||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
|
||||
{
|
||||
Crouching = false;
|
||||
@@ -330,35 +331,41 @@ namespace Barotrauma
|
||||
Collider.SetTransform(Collider.SimPosition, Collider.Rotation + angleDiff);
|
||||
}
|
||||
}
|
||||
|
||||
if (character.LockHands)
|
||||
{
|
||||
var leftHand = GetLimb(LimbType.LeftHand);
|
||||
var rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
|
||||
|
||||
rightHand.Disabled = true;
|
||||
leftHand.Disabled = true;
|
||||
|
||||
Vector2 midPos = waist.SimPosition;
|
||||
Matrix torsoTransform = Matrix.CreateRotationZ(waist.Rotation);
|
||||
|
||||
midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
|
||||
|
||||
if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
|
||||
HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
|
||||
HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
|
||||
}
|
||||
else if (character.AnimController.AnimationTestPose)
|
||||
|
||||
if (character.AnimController.AnimationTestPose)
|
||||
{
|
||||
ApplyTestPose();
|
||||
}
|
||||
else
|
||||
else if (character.SelectedBy == null)
|
||||
{
|
||||
if (Anim != Animation.UsingConstruction)
|
||||
if (character.LockHands)
|
||||
{
|
||||
ResetPullJoints();
|
||||
var leftHand = GetLimb(LimbType.LeftHand);
|
||||
var rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
|
||||
|
||||
rightHand.Disabled = true;
|
||||
leftHand.Disabled = true;
|
||||
|
||||
Vector2 midPos = waist.SimPosition;
|
||||
Matrix torsoTransform = Matrix.CreateRotationZ(waist.Rotation);
|
||||
|
||||
midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
|
||||
if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
|
||||
HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
|
||||
HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
|
||||
}
|
||||
if (Anim != Animation.UsingItem)
|
||||
{
|
||||
if (Anim != Animation.UsingItemWhileClimbing)
|
||||
{
|
||||
ResetPullJoints();
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetPullJoints(l => l.IsLowerBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,49 +384,53 @@ namespace Barotrauma
|
||||
switch (Anim)
|
||||
{
|
||||
case Animation.Climbing:
|
||||
case Animation.UsingItemWhileClimbing:
|
||||
levitatingCollider = false;
|
||||
UpdateClimbing();
|
||||
UpdateUseItemTimer();
|
||||
break;
|
||||
case Animation.CPR:
|
||||
UpdateCPR(deltaTime);
|
||||
break;
|
||||
case Animation.UsingConstruction:
|
||||
case Animation.UsingItem:
|
||||
default:
|
||||
if (Anim == Animation.UsingConstruction)
|
||||
{
|
||||
useItemTimer -= deltaTime;
|
||||
if (useItemTimer <= 0.0f) Anim = Animation.None;
|
||||
}
|
||||
|
||||
UpdateUseItemTimer();
|
||||
swimmingStateLockTimer -= deltaTime;
|
||||
|
||||
if (forceStanding || character.AnimController.AnimationTestPose)
|
||||
{
|
||||
swimming = false;
|
||||
}
|
||||
else
|
||||
else if (swimming != inWater && swimmingStateLockTimer <= 0.0f)
|
||||
{
|
||||
//0.5 second delay for switching between swimming and walking
|
||||
//prevents rapid switches between swimming/walking if the water level is fluctuating around the minimum swimming depth
|
||||
if (swimming != inWater && swimmingStateLockTimer <= 0.0f)
|
||||
{
|
||||
swimming = inWater;
|
||||
swimmingStateLockTimer = 0.5f;
|
||||
}
|
||||
swimming = inWater;
|
||||
swimmingStateLockTimer = 0.5f;
|
||||
}
|
||||
|
||||
if (swimming)
|
||||
{
|
||||
UpdateSwimming();
|
||||
}
|
||||
else
|
||||
else if (character.SelectedItem == null || !(character.SelectedSecondaryItem?.GetComponent<Controller>() is { } controller) ||
|
||||
!controller.ControlCharacterPose || !controller.UserInCorrectPosition)
|
||||
{
|
||||
UpdateStanding();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
void UpdateUseItemTimer()
|
||||
{
|
||||
if (IsUsingItem)
|
||||
{
|
||||
useItemTimer -= deltaTime;
|
||||
if (useItemTimer <= 0.0f)
|
||||
{
|
||||
StopUsingItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Timing.TotalTime > LockFlippingUntil && TargetDir != dir && !IsStuck)
|
||||
{
|
||||
Flip();
|
||||
@@ -841,7 +852,9 @@ namespace Barotrauma
|
||||
float targetSpeed = TargetMovement.Length();
|
||||
if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !Aiming)
|
||||
{
|
||||
if (Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
|
||||
if (!IsUsingItem &&
|
||||
!(character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) &&
|
||||
!(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
|
||||
{
|
||||
if (rotation > 20 && rotation < 170)
|
||||
{
|
||||
@@ -1041,12 +1054,17 @@ namespace Barotrauma
|
||||
|
||||
void UpdateClimbing()
|
||||
{
|
||||
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
|
||||
if (ladder == null || character.IsIncapacitated)
|
||||
var ladder = character.SelectedSecondaryItem?.GetComponent<Ladder>();
|
||||
if (character.IsIncapacitated)
|
||||
{
|
||||
Anim = Animation.None;
|
||||
return;
|
||||
}
|
||||
else if (ladder == null)
|
||||
{
|
||||
StopClimbing();
|
||||
return;
|
||||
}
|
||||
|
||||
onGround = false;
|
||||
IgnorePlatforms = true;
|
||||
@@ -1209,15 +1227,21 @@ namespace Barotrauma
|
||||
{
|
||||
RotateHead(head);
|
||||
}
|
||||
else if (Anim == Animation.UsingItemWhileClimbing && character.SelectedItem is { } selectedItem)
|
||||
{
|
||||
Vector2 diff = (selectedItem.WorldPosition - head.WorldPosition) * Dir;
|
||||
float targetRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver4 * Dir);
|
||||
head.body.SmoothRotate(targetRotation, force: WalkParams.HeadTorque);
|
||||
}
|
||||
else
|
||||
{
|
||||
float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
|
||||
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque);
|
||||
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, force: WalkParams.HeadTorque);
|
||||
}
|
||||
|
||||
if (!ladder.Item.Prefab.Triggers.Any())
|
||||
if (ladder.Item.Prefab.Triggers.None())
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
character.SelectedSecondaryItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1247,8 +1271,7 @@ namespace Barotrauma
|
||||
|
||||
if (!isClimbing)
|
||||
{
|
||||
Anim = Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
character.StopClimbing();
|
||||
IgnorePlatforms = false;
|
||||
}
|
||||
|
||||
@@ -1474,17 +1497,17 @@ namespace Barotrauma
|
||||
|
||||
public override void DragCharacter(Character target, float deltaTime)
|
||||
{
|
||||
if (target == null) return;
|
||||
if (target == null) { return; }
|
||||
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftHand);
|
||||
Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftForearm);
|
||||
if (targetLeftHand == null) targetLeftHand = target.AnimController.GetLimb(LimbType.Torso);
|
||||
if (targetLeftHand == null) targetLeftHand = target.AnimController.MainLimb;
|
||||
|
||||
Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightHand);
|
||||
Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightForearm);
|
||||
if (targetRightHand == null) targetRightHand = target.AnimController.GetLimb(LimbType.Torso);
|
||||
if (targetRightHand == null) targetRightHand = target.AnimController.MainLimb;
|
||||
|
||||
@@ -1493,7 +1516,7 @@ namespace Barotrauma
|
||||
target.AnimController.ResetPullJoints();
|
||||
}
|
||||
|
||||
if (Anim == Animation.Climbing)
|
||||
if (IsClimbing)
|
||||
{
|
||||
//cannot drag up ladders if the character is conscious
|
||||
if (target.AllowInput && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient))
|
||||
@@ -1613,7 +1636,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//only pull with one hand when swimming
|
||||
if (i > 0 && inWater) continue;
|
||||
if (i > 0 && inWater) { continue; }
|
||||
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(targetLimb.WorldPosition - pullLimb.WorldPosition);
|
||||
|
||||
@@ -1665,14 +1688,15 @@ namespace Barotrauma
|
||||
targetForce = 5000.0f;
|
||||
}
|
||||
|
||||
if (!target.AllowInput)
|
||||
{
|
||||
targetLimb.PullJointEnabled = true;
|
||||
targetLimb.PullJointMaxForce = targetForce;
|
||||
targetLimb.PullJointWorldAnchorB = targetAnchor;
|
||||
}
|
||||
targetLimb.PullJointEnabled = true;
|
||||
targetLimb.PullJointMaxForce = targetForce;
|
||||
targetLimb.PullJointWorldAnchorB = targetAnchor;
|
||||
targetLimb.Disabled = true;
|
||||
|
||||
target.AnimController.movement = -diff;
|
||||
if (diff.LengthSquared() > 0.1f)
|
||||
{
|
||||
target.AnimController.movement = -diff;
|
||||
}
|
||||
}
|
||||
|
||||
float dist = ConvertUnits.ToSimUnits(Vector2.Distance(target.WorldPosition, WorldPosition));
|
||||
@@ -1698,8 +1722,17 @@ namespace Barotrauma
|
||||
}
|
||||
else if (target is AICharacter && target != Character.Controlled)
|
||||
{
|
||||
target.AnimController.TargetDir = WorldPosition.X > target.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
target.AnimController.TargetMovement = (character.SimPosition + Vector2.UnitX * Dir) - target.SimPosition;
|
||||
if (target.AnimController.Dir > 0 == WorldPosition.X > target.WorldPosition.X)
|
||||
{
|
||||
target.AnimController.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
target.AnimController.TargetDir = WorldPosition.X > target.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
}
|
||||
//make the target stand 0.5 meters away from this character, on the side they're currently at
|
||||
Vector2 movement = (character.SimPosition + Vector2.UnitX * 0.5f * Math.Sign(target.SimPosition.X - character.SimPosition.X)) - target.SimPosition;
|
||||
target.AnimController.TargetMovement = movement.LengthSquared() > 0.01f ? movement : Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,12 +930,13 @@ namespace Barotrauma
|
||||
{
|
||||
limb.MoveToPos(pos, amount, pullFromCenter);
|
||||
}
|
||||
|
||||
public void ResetPullJoints()
|
||||
|
||||
public void ResetPullJoints(Func<Limb, bool> condition = null)
|
||||
{
|
||||
for (int i = 0; i < Limbs.Length; i++)
|
||||
{
|
||||
if (Limbs[i] == null) { continue; }
|
||||
if (condition != null && !condition(Limbs[i])) { continue; }
|
||||
Limbs[i].PullJointEnabled = false;
|
||||
}
|
||||
}
|
||||
@@ -1241,7 +1242,7 @@ namespace Barotrauma
|
||||
{
|
||||
//find the room which the limb is in
|
||||
//the room where the ragdoll is in is used as the "guess", meaning that it's checked first
|
||||
Hull limbHull = currentHull == null ? null : Hull.FindHull(limb.WorldPosition, currentHull);
|
||||
Hull newHull = currentHull == null ? null : Hull.FindHull(limb.WorldPosition, currentHull);
|
||||
|
||||
bool prevInWater = limb.InWater;
|
||||
limb.InWater = false;
|
||||
@@ -1250,38 +1251,37 @@ namespace Barotrauma
|
||||
{
|
||||
limb.InWater = false;
|
||||
}
|
||||
else if (limbHull == null)
|
||||
else if (newHull == null)
|
||||
{
|
||||
//limb isn't in any room -> it's in the water
|
||||
limb.InWater = true;
|
||||
if (limb.type == LimbType.Head) headInWater = true;
|
||||
if (limb.type == LimbType.Head) { headInWater = true; }
|
||||
}
|
||||
else if (limbHull.WaterVolume > 0.0f && Submarine.RectContains(limbHull.Rect, limb.Position))
|
||||
else if (newHull.WaterVolume > 0.0f && Submarine.RectContains(newHull.Rect, limb.Position))
|
||||
{
|
||||
if (limb.Position.Y < limbHull.Surface)
|
||||
if (limb.Position.Y < newHull.Surface)
|
||||
{
|
||||
limb.InWater = true;
|
||||
surfaceY = limbHull.Surface;
|
||||
surfaceY = newHull.Surface;
|
||||
if (limb.type == LimbType.Head)
|
||||
{
|
||||
headInWater = true;
|
||||
}
|
||||
}
|
||||
//the limb has gone through the surface of the water
|
||||
if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.InWater != prevInWater)
|
||||
if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.InWater != prevInWater && newHull == limb.Hull)
|
||||
{
|
||||
Splash(limb, limbHull);
|
||||
|
||||
Splash(limb, newHull);
|
||||
//if the Character dropped into water, create a wave
|
||||
if (limb.LinearVelocity.Y < 0.0f)
|
||||
{
|
||||
Vector2 impulse = limb.LinearVelocity * limb.Mass;
|
||||
int n = (int)((limb.Position.X - limbHull.Rect.X) / Hull.WaveWidth);
|
||||
limbHull.WaveVel[n] += MathHelper.Clamp(impulse.Y, -5.0f, 5.0f);
|
||||
int n = (int)((limb.Position.X - newHull.Rect.X) / Hull.WaveWidth);
|
||||
newHull.WaveVel[n] += MathHelper.Clamp(impulse.Y, -5.0f, 5.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
limb.Hull = newHull;
|
||||
limb.Update(deltaTime);
|
||||
}
|
||||
|
||||
@@ -1499,12 +1499,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (flowForce.LengthSquared() > 0.001f)
|
||||
{
|
||||
Collider.ApplyForce(flowForce);
|
||||
{
|
||||
Collider.ApplyForce(flowForce * (Collider.Mass / Mass));
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
if (!limb.InWater) { continue; }
|
||||
limb.body.ApplyForce(flowForce);
|
||||
limb.body.ApplyForce(flowForce * (limb.Mass / Mass * limbs.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the AI try to turn around when aiming with this attack?"), Editable]
|
||||
public bool Reverse { get; private set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Should the rope attached to this limb snap upon choosing a new attack?"), Editable]
|
||||
public bool SnapRopeOnNewAttack { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the AI try to steer away from the target when aiming with this attack? Best combined with PassiveAggressive behavior."), Editable]
|
||||
public bool Retreat { get; private set; }
|
||||
|
||||
@@ -309,7 +312,7 @@ namespace Barotrauma
|
||||
List<Affliction> multipliedAfflictions = new List<Affliction>();
|
||||
foreach (Affliction affliction in Afflictions.Keys)
|
||||
{
|
||||
multipliedAfflictions.Add(affliction.CreateMultiplied(multiplier));
|
||||
multipliedAfflictions.Add(affliction.CreateMultiplied(multiplier, affliction.Probability));
|
||||
}
|
||||
return multipliedAfflictions;
|
||||
}
|
||||
@@ -399,9 +402,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
afflictionPrefab = AfflictionPrefab.Prefabs[afflictionIdentifier];
|
||||
if (afflictionPrefab == null)
|
||||
Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
|
||||
if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out afflictionPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.");
|
||||
continue;
|
||||
@@ -427,15 +429,13 @@ namespace Barotrauma
|
||||
Afflictions.Clear();
|
||||
foreach (var subElement in element.GetChildElements("affliction"))
|
||||
{
|
||||
AfflictionPrefab afflictionPrefab;
|
||||
Affliction affliction;
|
||||
Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
|
||||
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier))
|
||||
if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab afflictionPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in an Attack defined in \"{parentDebugName}\" - could not find an affliction with the identifier \"{afflictionIdentifier}\".");
|
||||
continue;
|
||||
}
|
||||
afflictionPrefab = AfflictionPrefab.Prefabs[afflictionIdentifier];
|
||||
affliction = afflictionPrefab.Instantiate(0.0f);
|
||||
affliction.Deserialize(subElement);
|
||||
//backwards compatibility
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Barotrauma
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == enabled) return;
|
||||
if (value == enabled) { return; }
|
||||
|
||||
if (Removed)
|
||||
{
|
||||
@@ -66,6 +66,32 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool disabledByEvent;
|
||||
/// <summary>
|
||||
/// MonsterEvents disable monsters (which includes removing them from the character list, so they essentially "don't exist") until they're ready to spawn
|
||||
/// </summary>
|
||||
public bool DisabledByEvent
|
||||
{
|
||||
get { return disabledByEvent; }
|
||||
set
|
||||
{
|
||||
if (value == disabledByEvent) { return; }
|
||||
disabledByEvent = value;
|
||||
if (disabledByEvent)
|
||||
{
|
||||
Enabled = false;
|
||||
CharacterList.Remove(this);
|
||||
if (AiTarget != null) { AITarget.List.Remove(AiTarget); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CharacterList.Contains(this)) { CharacterList.Add(this); }
|
||||
if (AiTarget != null && !AITarget.List.Contains(AiTarget)) { AITarget.List.Add(AiTarget); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Hull PreviousHull = null;
|
||||
public Hull CurrentHull = null;
|
||||
|
||||
@@ -526,6 +552,10 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
|
||||
if (value)
|
||||
{
|
||||
SelectedCharacter = null;
|
||||
}
|
||||
#if CLIENT
|
||||
HintManager.OnHandcuffed(this);
|
||||
#endif
|
||||
@@ -576,13 +606,10 @@ namespace Barotrauma
|
||||
get { return selectedCharacter; }
|
||||
set
|
||||
{
|
||||
if (value == selectedCharacter) return;
|
||||
if (selectedCharacter != null)
|
||||
selectedCharacter.selectedBy = null;
|
||||
if (value == selectedCharacter) { return; }
|
||||
if (selectedCharacter != null) { selectedCharacter.selectedBy = null; }
|
||||
selectedCharacter = value;
|
||||
if (selectedCharacter != null)
|
||||
selectedCharacter.selectedBy = this;
|
||||
|
||||
if (selectedCharacter != null) {selectedCharacter.selectedBy = this; }
|
||||
#if CLIENT
|
||||
CharacterHealth.SetHealthBarVisibility(value == null);
|
||||
#endif
|
||||
@@ -684,6 +711,14 @@ namespace Barotrauma
|
||||
get { return CurrentHull == null || CurrentHull.LethalPressure > 5.0f; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by status effects
|
||||
/// </summary>
|
||||
public AnimController.Animation Anim
|
||||
{
|
||||
get { return AnimController?.Anim ?? AnimController.Animation.None; }
|
||||
}
|
||||
|
||||
public const float KnockbackCooldown = 5.0f;
|
||||
public float KnockbackCooldownTimer;
|
||||
|
||||
@@ -816,43 +851,65 @@ namespace Barotrauma
|
||||
get { return AnimController?.Collider?.LinearVelocity.Length() ?? 0.0f; }
|
||||
}
|
||||
|
||||
private Item _selectedConstruction;
|
||||
public Item SelectedConstruction
|
||||
private Item _selectedItem;
|
||||
/// <summary>
|
||||
/// The primary selected item. It can be any device that character interacts with. This excludes items like ladders and chairs which are assigned to <see cref="SelectedSecondaryItem"/>.
|
||||
/// </summary>
|
||||
public Item SelectedItem
|
||||
{
|
||||
get => _selectedConstruction;
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
var prevSelectedConstruction = _selectedConstruction;
|
||||
_selectedConstruction = value;
|
||||
var prevSelectedItem = _selectedItem;
|
||||
_selectedItem = value;
|
||||
#if CLIENT
|
||||
HintManager.OnSetSelectedConstruction(this, prevSelectedConstruction, _selectedConstruction);
|
||||
HintManager.OnSetSelectedItem(this, prevSelectedItem, _selectedItem);
|
||||
if (Controlled == this)
|
||||
{
|
||||
if (_selectedConstruction == null)
|
||||
if (_selectedItem == null)
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.ResetCrewList();
|
||||
}
|
||||
else if (_selectedConstruction.GetComponent<Ladder>() == null)
|
||||
else if (!_selectedItem.IsLadder)
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.AutoHideCrewList();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (prevSelectedConstruction == null && _selectedConstruction != null)
|
||||
if (prevSelectedItem != null && (_selectedItem == null || _selectedItem != prevSelectedItem) && itemSelectedTime > 0)
|
||||
{
|
||||
double selectedDuration = Timing.TotalTime - itemSelectedTime;
|
||||
if (itemSelectedDurations.ContainsKey(prevSelectedItem.Prefab))
|
||||
{
|
||||
itemSelectedDurations[prevSelectedItem.Prefab] += selectedDuration;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemSelectedDurations.Add(prevSelectedItem.Prefab, selectedDuration);
|
||||
}
|
||||
itemSelectedTime = 0;
|
||||
}
|
||||
if (_selectedItem != null && (prevSelectedItem == null || prevSelectedItem != _selectedItem))
|
||||
{
|
||||
itemSelectedTime = Timing.TotalTime;
|
||||
}
|
||||
else if (prevSelectedConstruction != null && _selectedConstruction == null && itemSelectedTime > 0)
|
||||
{
|
||||
if (!itemSelectedDurations.ContainsKey(prevSelectedConstruction.Prefab))
|
||||
{
|
||||
itemSelectedDurations.Add(prevSelectedConstruction.Prefab, 0);
|
||||
}
|
||||
itemSelectedDurations[prevSelectedConstruction.Prefab] += Timing.TotalTime - itemSelectedTime;
|
||||
itemSelectedTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The secondary selected item. It's an item other than a device (see <see cref="SelectedItem"/>), e.g. a ladder or a chair.
|
||||
/// </summary>
|
||||
public Item SelectedSecondaryItem { get; set; }
|
||||
/// <summary>
|
||||
/// Has the characters selected a primary or a secondary item?
|
||||
/// </summary>
|
||||
public bool HasSelectedAnyItem => SelectedItem != null || SelectedSecondaryItem != null;
|
||||
/// <summary>
|
||||
/// Is the item either the primary or the secondary selected item?
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsAnySelectedItem(Item item) => item == SelectedItem || item == SelectedSecondaryItem;
|
||||
public bool HasSelectedAnotherSecondaryItem(Item item) => SelectedSecondaryItem != null && SelectedSecondaryItem != item;
|
||||
|
||||
public Item FocusedItem
|
||||
{
|
||||
@@ -941,7 +998,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
return SelectedConstruction == null || SelectedConstruction.GetComponent<Ladder>() != null || (SelectedConstruction.GetComponent<Controller>()?.AllowAiming ?? false);
|
||||
return SelectedItem == null || (SelectedItem.GetComponent<Controller>()?.AllowAiming ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1231,23 +1288,30 @@ namespace Barotrauma
|
||||
|
||||
if (Params.Husk && speciesName != "husk" && Prefab.VariantOf != "husk")
|
||||
{
|
||||
// Get the non husked name and find the ragdoll with it
|
||||
var matchingAffliction = AfflictionPrefab.List
|
||||
.Where(p => p is AfflictionPrefabHusk)
|
||||
.Select(p => p as AfflictionPrefabHusk)
|
||||
.FirstOrDefault(p => p.TargetSpecies.Any(t => t == AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p)));
|
||||
Identifier nonHuskedSpeciesName = Identifier.Empty;
|
||||
if (matchingAffliction == null)
|
||||
AfflictionPrefabHusk matchingAffliction = null;
|
||||
foreach (var huskPrefab in AfflictionPrefab.Prefabs.OfType<AfflictionPrefabHusk>())
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot find a husk infection that matches this species! Please add the speciesnames as 'targets' in the husk affliction prefab definition!");
|
||||
var nonHuskedName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, huskPrefab);
|
||||
if (huskPrefab.TargetSpecies.Contains(nonHuskedName))
|
||||
{
|
||||
var huskedSpeciesName = AfflictionHusk.GetHuskedSpeciesName(nonHuskedName, huskPrefab);
|
||||
if (huskedSpeciesName.Equals(speciesName))
|
||||
{
|
||||
nonHuskedSpeciesName = nonHuskedName;
|
||||
matchingAffliction = huskPrefab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matchingAffliction == null || nonHuskedSpeciesName.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a husk infection that matches {speciesName}! Please make sure that the speciesname is added as 'targets' in the husk affliction prefab definition!\n"
|
||||
+ "Note that all the infected speciesnames and files must stick the following pattern: [nonhuskedspeciesname][huskedspeciesname]. E.g. Humanhusk, Crawlerhusk, or Humancustomhusk, or Crawlerzombie. Not \"Customhumanhusk!\" or \"Zombiecrawler\"");
|
||||
// Crashes if we fail to create a ragdoll -> Let's just use some ragdoll so that the user sees the error msg.
|
||||
nonHuskedSpeciesName = IsHumanoid ? CharacterPrefab.HumanSpeciesName : "crawler".ToIdentifier();
|
||||
speciesName = nonHuskedSpeciesName;
|
||||
}
|
||||
else
|
||||
{
|
||||
nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
|
||||
}
|
||||
if (ragdollParams == null && prefab.VariantOf == null)
|
||||
{
|
||||
Identifier name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName;
|
||||
@@ -1487,9 +1551,20 @@ namespace Barotrauma
|
||||
|
||||
public void GiveJobItems(WayPoint spawnPoint = null)
|
||||
{
|
||||
if (info?.Job == null) { return; }
|
||||
info.Job.GiveJobItems(this, spawnPoint);
|
||||
|
||||
if (info == null) { return; }
|
||||
if (info.HumanPrefabIds != default)
|
||||
{
|
||||
var humanPrefab = NPCSet.Get(info.HumanPrefabIds.NpcSetIdentifier, info.HumanPrefabIds.NpcIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to give job items for the character \"{Name}\" - could not find human prefab with the id \"{info.HumanPrefabIds.NpcIdentifier}\" from \"{info.HumanPrefabIds.NpcSetIdentifier}\".");
|
||||
}
|
||||
else if (humanPrefab.GiveItems(this, Submarine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
info.Job?.GiveJobItems(this, spawnPoint);
|
||||
GameMain.LuaCs.Hook.Call("character.giveJobItems", this, spawnPoint);
|
||||
}
|
||||
|
||||
@@ -1532,10 +1607,17 @@ namespace Barotrauma
|
||||
if (item?.GetComponent<Wearable>() is Wearable wearable &&
|
||||
!Inventory.IsInLimbSlot(item, InvSlotType.Any))
|
||||
{
|
||||
if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue))
|
||||
foreach (var allowedSlot in wearable.AllowedSlots)
|
||||
{
|
||||
skillLevel += skillValue;
|
||||
if (allowedSlot == InvSlotType.Any) { continue; }
|
||||
if (!Inventory.IsInLimbSlot(item, allowedSlot)) { continue; }
|
||||
if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue))
|
||||
{
|
||||
skillLevel += skillValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1549,7 +1631,7 @@ namespace Barotrauma
|
||||
public Vector2? OverrideMovement { get; set; }
|
||||
public bool ForceRun { get; set; }
|
||||
|
||||
public bool IsClimbing => AnimController.Anim == AnimController.Animation.Climbing;
|
||||
public bool IsClimbing => AnimController.IsClimbing;
|
||||
|
||||
public Vector2 GetTargetMovement()
|
||||
{
|
||||
@@ -1779,6 +1861,11 @@ namespace Barotrauma
|
||||
return speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping.
|
||||
/// </summary>
|
||||
private const float cursorFollowMargin = 40;
|
||||
|
||||
public void Control(float deltaTime, Camera cam)
|
||||
{
|
||||
ViewTarget = null;
|
||||
@@ -1811,10 +1898,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (!aiControlled &&
|
||||
AnimController.Anim != AnimController.Animation.UsingConstruction &&
|
||||
!AnimController.IsUsingItem &&
|
||||
AnimController.Anim != AnimController.Animation.CPR &&
|
||||
(GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient || Controlled == this) &&
|
||||
AnimController.OnGround && !AnimController.InWater)
|
||||
(AnimController.OnGround || IsClimbing) && !AnimController.InWater)
|
||||
{
|
||||
if (dontFollowCursor)
|
||||
{
|
||||
@@ -1822,13 +1909,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping.
|
||||
float followMargin = 40;
|
||||
if (CursorPosition.X < AnimController.Collider.Position.X - followMargin)
|
||||
if (CursorPosition.X < AnimController.Collider.Position.X - cursorFollowMargin)
|
||||
{
|
||||
AnimController.TargetDir = Direction.Left;
|
||||
}
|
||||
else if (CursorPosition.X > AnimController.Collider.Position.X + followMargin)
|
||||
else if (CursorPosition.X > AnimController.Collider.Position.X + cursorFollowMargin)
|
||||
{
|
||||
AnimController.TargetDir = Direction.Right;
|
||||
}
|
||||
@@ -1975,7 +2060,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedConstruction == null || !SelectedConstruction.Prefab.DisableItemUsageWhenSelected)
|
||||
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
|
||||
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
|
||||
{
|
||||
foreach (Item item in HeldItems)
|
||||
{
|
||||
@@ -2006,24 +2092,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedConstruction != null)
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
if (IsKeyDown(InputType.Aim) || !SelectedConstruction.RequireAimToSecondaryUse)
|
||||
if (IsKeyDown(InputType.Aim) || !SelectedItem.RequireAimToSecondaryUse)
|
||||
{
|
||||
SelectedConstruction.SecondaryUse(deltaTime, this);
|
||||
SelectedItem.SecondaryUse(deltaTime, this);
|
||||
}
|
||||
if (IsKeyDown(InputType.Use) && SelectedConstruction != null && !SelectedConstruction.IsShootable)
|
||||
if (IsKeyDown(InputType.Use) && SelectedItem != null && !SelectedItem.IsShootable)
|
||||
{
|
||||
if (!SelectedConstruction.RequireAimToUse || IsKeyDown(InputType.Aim))
|
||||
if (!SelectedItem.RequireAimToUse || IsKeyDown(InputType.Aim))
|
||||
{
|
||||
SelectedConstruction.Use(deltaTime, this);
|
||||
SelectedItem.Use(deltaTime, this);
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(InputType.Shoot) && SelectedConstruction != null && SelectedConstruction.IsShootable)
|
||||
if (IsKeyDown(InputType.Shoot) && SelectedItem != null && SelectedItem.IsShootable)
|
||||
{
|
||||
if (!SelectedConstruction.RequireAimToUse || IsKeyDown(InputType.Aim))
|
||||
if (!SelectedItem.RequireAimToUse || IsKeyDown(InputType.Aim))
|
||||
{
|
||||
SelectedConstruction.Use(deltaTime, this);
|
||||
SelectedItem.Use(deltaTime, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2359,15 +2445,15 @@ namespace Barotrauma
|
||||
|
||||
//wires are interactable if the character has selected an item the wire is connected to,
|
||||
//and it's disconnected from the other end
|
||||
if (wire.Connections[0]?.Item != null && SelectedConstruction == wire.Connections[0].Item)
|
||||
if (wire.Connections[0]?.Item != null && SelectedItem == wire.Connections[0].Item)
|
||||
{
|
||||
return wire.Connections[1] == null;
|
||||
}
|
||||
if (wire.Connections[1]?.Item != null && SelectedConstruction == wire.Connections[1].Item)
|
||||
if (wire.Connections[1]?.Item != null && SelectedItem == wire.Connections[1].Item)
|
||||
{
|
||||
return wire.Connections[0] == null;
|
||||
}
|
||||
if (SelectedConstruction?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)
|
||||
if (SelectedItem?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)
|
||||
{
|
||||
return wire.Connections[0] == null && wire.Connections[1] == null;
|
||||
}
|
||||
@@ -2393,7 +2479,7 @@ namespace Barotrauma
|
||||
Pickable pickableComponent = item.GetComponent<Pickable>();
|
||||
if (pickableComponent != null && pickableComponent.Picker != this && pickableComponent.Picker != null && !pickableComponent.Picker.IsDead) { return false; }
|
||||
|
||||
if (SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
|
||||
if (SelectedItem?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
|
||||
//optimization: don't use HeldItems because it allocates memory and this method is executed very frequently
|
||||
var heldItem1 = Inventory?.GetItemInLimbSlot(InvSlotType.RightHand);
|
||||
if (heldItem1?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
|
||||
@@ -2436,27 +2522,43 @@ namespace Barotrauma
|
||||
distanceToItem = Vector2.Distance(rectIntersectionPoint, playerDistanceCheckPosition);
|
||||
}
|
||||
|
||||
if (distanceToItem > item.InteractDistance && item.InteractDistance > 0.0f) { return false; }
|
||||
float interactDistance = item.InteractDistance;
|
||||
if ((SelectedSecondaryItem != null || item.IsSecondaryItem) && AnimController is HumanoidAnimController c)
|
||||
{
|
||||
// Use a distance slightly shorter than the arms length to keep the character in a comfortable pose
|
||||
float armLength = 0.75f * ConvertUnits.ToDisplayUnits(c.ArmLength);
|
||||
interactDistance = Math.Min(interactDistance, armLength);
|
||||
}
|
||||
if (distanceToItem > interactDistance && item.InteractDistance > 0.0f) { return false; }
|
||||
|
||||
Vector2 itemPosition = item.SimPosition;
|
||||
if (Submarine == null && item.Submarine != null)
|
||||
{
|
||||
//character is outside, item inside
|
||||
itemPosition += item.Submarine.SimPosition;
|
||||
}
|
||||
else if (Submarine != null && item.Submarine == null)
|
||||
{
|
||||
//character is inside, item outside
|
||||
itemPosition -= Submarine.SimPosition;
|
||||
}
|
||||
else if (Submarine != item.Submarine)
|
||||
{
|
||||
//character and the item are inside different subs
|
||||
itemPosition += item.Submarine.SimPosition;
|
||||
itemPosition -= Submarine.SimPosition;
|
||||
}
|
||||
|
||||
if (SelectedSecondaryItem != null && !item.IsSecondaryItem)
|
||||
{
|
||||
if (item.GetComponent<Controller>() is { } controller && controller.Direction != 0 && controller.Direction != AnimController.Direction) { return false; }
|
||||
float threshold = ConvertUnits.ToSimUnits(cursorFollowMargin);
|
||||
if (AnimController.Direction == Direction.Left && SimPosition.X + threshold < itemPosition.X) { return false; }
|
||||
if (AnimController.Direction == Direction.Right && SimPosition.X - threshold > itemPosition.X) { return false; }
|
||||
}
|
||||
|
||||
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger)
|
||||
{
|
||||
Vector2 itemPosition = item.SimPosition;
|
||||
if (Submarine == null && item.Submarine != null)
|
||||
{
|
||||
//character is outside, item inside
|
||||
itemPosition += item.Submarine.SimPosition;
|
||||
}
|
||||
else if (Submarine != null && item.Submarine == null)
|
||||
{
|
||||
//character is inside, item outside
|
||||
itemPosition -= Submarine.SimPosition;
|
||||
}
|
||||
else if (Submarine != item.Submarine)
|
||||
{
|
||||
//character and the item are inside different subs
|
||||
itemPosition += item.Submarine.SimPosition;
|
||||
itemPosition -= Submarine.SimPosition;
|
||||
}
|
||||
var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true);
|
||||
if (body != null && body.UserData as Item != item && (body.UserData as ItemComponent)?.Item != item && Submarine.LastPickedFixture?.UserData as Item != item)
|
||||
{
|
||||
@@ -2529,12 +2631,12 @@ namespace Barotrauma
|
||||
|
||||
if (!CanInteract)
|
||||
{
|
||||
SelectedConstruction = null;
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
focusedItem = null;
|
||||
if (!AllowInput)
|
||||
{
|
||||
FocusedCharacter = null;
|
||||
if (SelectedCharacter != null) DeselectCharacter();
|
||||
if (SelectedCharacter != null) { DeselectCharacter(); }
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2578,8 +2680,8 @@ namespace Barotrauma
|
||||
AnimController.InWater :
|
||||
head.InWater;
|
||||
//climb ladders automatically when pressing up/down inside their trigger area
|
||||
Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>();
|
||||
if ((SelectedConstruction == null || currentLadder != null) &&
|
||||
Ladder currentLadder = SelectedSecondaryItem?.GetComponent<Ladder>();
|
||||
if ((SelectedSecondaryItem == null || currentLadder != null) &&
|
||||
!headInWater && Screen.Selected != GameMain.SubEditorScreen)
|
||||
{
|
||||
bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down);
|
||||
@@ -2621,7 +2723,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (nearbyLadder.Select(this))
|
||||
{
|
||||
SelectedConstruction = nearbyLadder.Item;
|
||||
SelectedSecondaryItem = nearbyLadder.Item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2665,16 +2767,23 @@ namespace Barotrauma
|
||||
{
|
||||
FocusedCharacter.onCustomInteract(FocusedCharacter, this);
|
||||
}
|
||||
else if (IsKeyHit(InputType.Deselect) && SelectedConstruction != null && SelectedConstruction.GetComponent<Ladder>() == null)
|
||||
else if (IsKeyHit(InputType.Deselect) && SelectedItem != null)
|
||||
{
|
||||
SelectedConstruction = null;
|
||||
SelectedItem = null;
|
||||
#if CLIENT
|
||||
CharacterHealth.OpenHealthWindow = null;
|
||||
#endif
|
||||
}
|
||||
else if (IsKeyHit(InputType.Health) && SelectedConstruction != null && SelectedConstruction.GetComponent<Ladder>() == null)
|
||||
else if (IsKeyHit(InputType.Deselect) && SelectedSecondaryItem != null)
|
||||
{
|
||||
SelectedConstruction = null;
|
||||
SelectedSecondaryItem = null;
|
||||
#if CLIENT
|
||||
CharacterHealth.OpenHealthWindow = null;
|
||||
#endif
|
||||
}
|
||||
else if (IsKeyHit(InputType.Health) && (SelectedItem != null || SelectedSecondaryItem != null))
|
||||
{
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
}
|
||||
else if (focusedItem != null)
|
||||
{
|
||||
@@ -2890,7 +2999,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) &&
|
||||
PressureProtection < (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f) &&
|
||||
WorldPosition.Y < CharacterHealth.CrushDepth)
|
||||
WorldPosition.Y < CharacterHealth.CrushDepth && !HasAbilityFlag(AbilityFlags.ImmuneToPressure))
|
||||
{
|
||||
//implode if below crush depth, and either outside or in a high-pressure hull
|
||||
if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)
|
||||
@@ -2923,7 +3032,7 @@ namespace Barotrauma
|
||||
{
|
||||
Stun = Math.Max(5.0f, Stun);
|
||||
AnimController.ResetPullJoints();
|
||||
SelectedConstruction = null;
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2980,7 +3089,7 @@ namespace Barotrauma
|
||||
humanAnimController.Crouching = false;
|
||||
}
|
||||
AnimController.ResetPullJoints();
|
||||
SelectedConstruction = null;
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2996,9 +3105,13 @@ namespace Barotrauma
|
||||
DoInteractionUpdate(deltaTime, mouseSimPos);
|
||||
}
|
||||
|
||||
if (SelectedConstruction != null && !CanInteractWith(SelectedConstruction))
|
||||
if (SelectedItem != null && !CanInteractWith(SelectedItem))
|
||||
{
|
||||
SelectedConstruction = null;
|
||||
SelectedItem = null;
|
||||
}
|
||||
if (SelectedSecondaryItem != null && !CanInteractWith(SelectedSecondaryItem))
|
||||
{
|
||||
SelectedSecondaryItem = null;
|
||||
}
|
||||
|
||||
if (!IsDead) { LockHands = false; }
|
||||
@@ -3553,7 +3666,7 @@ namespace Barotrauma
|
||||
string modifiedMessage = ChatMessage.ApplyDistanceEffect(message.Message, message.MessageType.Value, this, Controlled);
|
||||
if (!string.IsNullOrEmpty(modifiedMessage))
|
||||
{
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(info.Name, modifiedMessage, message.MessageType.Value, this);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(Name, modifiedMessage, message.MessageType.Value, this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -3936,7 +4049,8 @@ namespace Barotrauma
|
||||
CharacterHealth.Stun = newStun;
|
||||
if (newStun > 0.0f)
|
||||
{
|
||||
SelectedConstruction = null;
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
if (SelectedCharacter != null) { DeselectCharacter(); }
|
||||
}
|
||||
HealthUpdateInterval = 0.0f;
|
||||
}
|
||||
@@ -3952,77 +4066,79 @@ namespace Barotrauma
|
||||
CharacterHealth.ReduceAfflictionOnAllLimbs("damage".ToIdentifier(), eatingRegen * deltaTime);
|
||||
}
|
||||
}
|
||||
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
||||
foreach (StatusEffect statusEffect in statusEffectList)
|
||||
if (statusEffects.TryGetValue(actionType, out var statusEffectList))
|
||||
{
|
||||
if (statusEffect.type == ActionType.OnDamaged)
|
||||
foreach (StatusEffect statusEffect in statusEffectList)
|
||||
{
|
||||
if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
|
||||
if (statusEffect.OnlyPlayerTriggered)
|
||||
if (statusEffect.type == ActionType.OnDamaged)
|
||||
{
|
||||
if (LastAttacker == null || !LastAttacker.IsPlayer)
|
||||
if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
|
||||
if (statusEffect.OnlyPlayerTriggered)
|
||||
{
|
||||
continue;
|
||||
if (LastAttacker == null || !LastAttacker.IsPlayer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
|
||||
statusEffect.Apply(actionType, deltaTime, this, targets);
|
||||
}
|
||||
else if (statusEffect.targetLimbs != null)
|
||||
{
|
||||
foreach (var limbType in statusEffect.targetLimbs)
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
targets.Clear();
|
||||
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
|
||||
statusEffect.Apply(actionType, deltaTime, this, targets);
|
||||
}
|
||||
else if (statusEffect.targetLimbs != null)
|
||||
{
|
||||
foreach (var limbType in statusEffect.targetLimbs)
|
||||
{
|
||||
// Target all matching limbs
|
||||
foreach (var limb in AnimController.Limbs)
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == limbType)
|
||||
// Target all matching limbs
|
||||
foreach (var limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == limbType)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
// Target just the first matching limb
|
||||
Limb limb = AnimController.GetLimb(limbType);
|
||||
if (limb != null)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
|
||||
{
|
||||
// Target just the last matching limb
|
||||
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
|
||||
if (limb != null)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
// Target just the first matching limb
|
||||
Limb limb = AnimController.GetLimb(limbType);
|
||||
if (limb != null)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
|
||||
{
|
||||
// Target just the last matching limb
|
||||
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
|
||||
if (limb != null)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, this);
|
||||
}
|
||||
}
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, this);
|
||||
}
|
||||
}
|
||||
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
|
||||
{
|
||||
// OnDamaged is called only for the limb that is hit.
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
limb.ApplyStatusEffects(actionType, deltaTime);
|
||||
// OnDamaged is called only for the limb that is hit.
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
limb.ApplyStatusEffects(actionType, deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
//OnActive effects are handled by the afflictions themselves
|
||||
@@ -4097,10 +4213,12 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData(forceAfflictionData: true));
|
||||
}
|
||||
#endif
|
||||
|
||||
isDead = true;
|
||||
|
||||
@@ -4175,7 +4293,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
SelectedConstruction = null;
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
SelectedCharacter = null;
|
||||
|
||||
AnimController.ResetPullJoints();
|
||||
@@ -4689,7 +4807,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
|
||||
{
|
||||
if (talentOption.Talents.None(t => HasTalent(t.Identifier)))
|
||||
if (talentOption.TalentIdentifiers.None(t => HasTalent(t)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -4918,6 +5036,12 @@ namespace Barotrauma
|
||||
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
|
||||
/// </summary>
|
||||
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
|
||||
|
||||
public void StopClimbing()
|
||||
{
|
||||
AnimController.StopClimbing();
|
||||
SelectedSecondaryItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
class ActiveTeamChange
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -25,9 +26,10 @@ namespace Barotrauma
|
||||
UpdateSkills = 12,
|
||||
UpdateMoney = 13,
|
||||
UpdatePermanentStats = 14,
|
||||
RemoveFromCrew = 15,
|
||||
|
||||
MinValue = 0,
|
||||
MaxValue = 14
|
||||
MaxValue = 15
|
||||
}
|
||||
|
||||
private interface IEventData : NetEntityEvent.IData
|
||||
@@ -54,6 +56,15 @@ namespace Barotrauma
|
||||
public struct CharacterStatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
|
||||
#if SERVER
|
||||
public bool ForceAfflictionData;
|
||||
|
||||
public CharacterStatusEventData(bool forceAfflictionData)
|
||||
{
|
||||
ForceAfflictionData = forceAfflictionData;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public struct TreatmentEventData : IEventData
|
||||
@@ -124,18 +135,30 @@ namespace Barotrauma
|
||||
public EventType EventType => EventType.TeamChange;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct ItemTeamChange(CharacterTeamType TeamId, ImmutableArray<UInt16> ItemIds) : INetSerializableStruct;
|
||||
|
||||
|
||||
public struct AddToCrewEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.AddToCrew;
|
||||
public readonly CharacterTeamType TeamType;
|
||||
public readonly ImmutableArray<Item> InventoryItems;
|
||||
public readonly ItemTeamChange ItemTeamChange;
|
||||
|
||||
public AddToCrewEventData(CharacterTeamType teamType, IEnumerable<Item> inventoryItems)
|
||||
{
|
||||
TeamType = teamType;
|
||||
InventoryItems = inventoryItems.ToImmutableArray();
|
||||
ItemTeamChange = new ItemTeamChange(teamType, inventoryItems.Select(it => it.ID).ToImmutableArray());
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoveFromCrewEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.RemoveFromCrew;
|
||||
public readonly ItemTeamChange ItemTeamChange;
|
||||
|
||||
public RemoveFromCrewEventData(CharacterTeamType teamType, IEnumerable<Item> inventoryItems)
|
||||
{
|
||||
ItemTeamChange = new ItemTeamChange(teamType, inventoryItems.Select(it => it.ID).ToImmutableArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct UpdateExperienceEventData : IEventData
|
||||
|
||||
@@ -261,6 +261,10 @@ namespace Barotrauma
|
||||
|
||||
public string Name;
|
||||
|
||||
public LocalizedString Title;
|
||||
|
||||
public (Identifier NpcSetIdentifier, Identifier NpcIdentifier) HumanPrefabIds;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
@@ -649,15 +653,12 @@ namespace Barotrauma
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
else if (!npcIdentifier.IsEmpty && TextManager.Get("npctitle." + npcIdentifier) is { Loaded: true } npcTitle)
|
||||
{
|
||||
Name = npcTitle.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = GetRandomName(randSync);
|
||||
}
|
||||
|
||||
|
||||
TryLoadNameAndTitle(npcIdentifier);
|
||||
SetPersonalityTrait();
|
||||
|
||||
Salary = CalculateSalary();
|
||||
@@ -727,7 +728,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// Used for loading the data
|
||||
public CharacterInfo(XElement infoElement)
|
||||
public CharacterInfo(XElement infoElement, Identifier npcIdentifier = default)
|
||||
{
|
||||
ID = idCounter;
|
||||
idCounter++;
|
||||
@@ -774,6 +775,8 @@ namespace Barotrauma
|
||||
Head.FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White);
|
||||
CheckColors();
|
||||
|
||||
TryLoadNameAndTitle(npcIdentifier);
|
||||
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
{
|
||||
var nameElement = CharacterConfigElement.GetChildElement("names");
|
||||
@@ -794,9 +797,21 @@ namespace Barotrauma
|
||||
ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty);
|
||||
if (personalityName != Identifier.Empty)
|
||||
{
|
||||
PersonalityTrait = NPCPersonalityTrait.Get(GameSettings.CurrentConfig.Language, personalityName);
|
||||
if (NPCPersonalityTrait.Traits.TryGet(personalityName, out var trait) ||
|
||||
NPCPersonalityTrait.Traits.TryGet(personalityName.Replace(" ".ToIdentifier(), Identifier.Empty), out trait))
|
||||
{
|
||||
PersonalityTrait = trait;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in CharacterInfo \"{OriginalName}\": could not find a personality trait with the identifier \"{personalityName}\".");
|
||||
}
|
||||
}
|
||||
|
||||
HumanPrefabIds = (
|
||||
infoElement.GetAttributeIdentifier("npcsetid", Identifier.Empty),
|
||||
infoElement.GetAttributeIdentifier("npcid", Identifier.Empty));
|
||||
|
||||
MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0);
|
||||
|
||||
foreach (var subElement in infoElement.Elements())
|
||||
@@ -838,6 +853,19 @@ namespace Barotrauma
|
||||
LoadHeadAttachments();
|
||||
}
|
||||
|
||||
private void TryLoadNameAndTitle(Identifier npcIdentifier)
|
||||
{
|
||||
if (!npcIdentifier.IsEmpty)
|
||||
{
|
||||
Title = TextManager.Get("npctitle." + npcIdentifier);
|
||||
string nameTag = "charactername." + npcIdentifier;
|
||||
if (TextManager.ContainsTag(nameTag))
|
||||
{
|
||||
Name = TextManager.Get(nameTag).Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ContentXElement> hairs;
|
||||
public IReadOnlyList<ContentXElement> Hairs => hairs;
|
||||
private List<ContentXElement> beards;
|
||||
@@ -1259,6 +1287,11 @@ namespace Barotrauma
|
||||
if (splitTag[0] != "name") { continue; }
|
||||
if (splitTag[1] != Name) { continue; }
|
||||
item.ReplaceTag(tag, $"name:{newName}");
|
||||
var idCard = item.GetComponent<IdCard>();
|
||||
if (idCard != null)
|
||||
{
|
||||
idCard.OwnerName = newName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1292,9 +1325,16 @@ namespace Barotrauma
|
||||
new XAttribute("facialhaircolor", XMLExtensions.ColorToString(Head.FacialHairColor)),
|
||||
new XAttribute("startitemsgiven", StartItemsGiven),
|
||||
new XAttribute("ragdoll", ragdollFileName),
|
||||
new XAttribute("personality", PersonalityTrait?.Name.Value ?? ""));
|
||||
new XAttribute("personality", PersonalityTrait?.Identifier ?? Identifier.Empty));
|
||||
// TODO: animations?
|
||||
|
||||
if (HumanPrefabIds != default)
|
||||
{
|
||||
charElement.Add(
|
||||
new XAttribute("npcsetid", HumanPrefabIds.NpcSetIdentifier),
|
||||
new XAttribute("npcid", HumanPrefabIds.NpcIdentifier));
|
||||
}
|
||||
|
||||
charElement.Add(new XAttribute("missionscompletedsincedeath", MissionsCompletedSinceDeath));
|
||||
|
||||
if (Character != null)
|
||||
@@ -1323,11 +1363,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
charElement.Add(savedStatElement);
|
||||
|
||||
parentElement.Add(charElement);
|
||||
parentElement?.Add(charElement);
|
||||
return charElement;
|
||||
}
|
||||
|
||||
@@ -1597,9 +1634,9 @@ namespace Barotrauma
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void ApplyHealthData(Character character, XElement healthData)
|
||||
public static void ApplyHealthData(Character character, XElement healthData, Func<AfflictionPrefab, bool> afflictionPredicate = null)
|
||||
{
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData); }
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData, afflictionPredicate); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,26 +10,27 @@ namespace Barotrauma
|
||||
public readonly Direction Direction;
|
||||
|
||||
public readonly Character SelectedCharacter;
|
||||
public readonly Item SelectedItem;
|
||||
public readonly Item SelectedItem, SelectedSecondaryItem;
|
||||
|
||||
public readonly AnimController.Animation Animation;
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, selectedCharacter, selectedItem, animation)
|
||||
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, selectedCharacter, selectedItem, selectedSecondaryItem, animation)
|
||||
{
|
||||
}
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, selectedCharacter, selectedItem, animation)
|
||||
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, selectedCharacter, selectedItem, selectedSecondaryItem, animation)
|
||||
{
|
||||
}
|
||||
|
||||
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
|
||||
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: base(pos, rotation, velocity, angularVelocity, ID, time)
|
||||
{
|
||||
Direction = dir;
|
||||
SelectedCharacter = selectedCharacter;
|
||||
SelectedItem = selectedItem;
|
||||
SelectedSecondaryItem = selectedSecondaryItem;
|
||||
|
||||
Animation = animation;
|
||||
}
|
||||
@@ -81,10 +82,10 @@ namespace Barotrauma
|
||||
public UInt16 networkUpdateID;
|
||||
}
|
||||
|
||||
private List<NetInputMem> memInput = new List<NetInputMem>();
|
||||
private readonly List<NetInputMem> memInput = new List<NetInputMem>();
|
||||
|
||||
private List<CharacterStateInfo> memState = new List<CharacterStateInfo>();
|
||||
private List<CharacterStateInfo> memLocalState = new List<CharacterStateInfo>();
|
||||
private readonly List<CharacterStateInfo> memState = new List<CharacterStateInfo>();
|
||||
private readonly List<CharacterStateInfo> memLocalState = new List<CharacterStateInfo>();
|
||||
|
||||
public float healthUpdateTimer;
|
||||
|
||||
|
||||
@@ -12,12 +12,8 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly static PrefabCollection<CharacterPrefab> Prefabs = new PrefabCollection<CharacterPrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
Character.RemoveByPrefab(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,13 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<CorpsePrefab> Prefabs = new PrefabCollection<CorpsePrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
|
||||
public static CorpsePrefab Get(Identifier identifier)
|
||||
{
|
||||
@@ -46,7 +40,7 @@ namespace Barotrauma
|
||||
[Serialize(0, IsPropertySaveable.No)]
|
||||
public int MaxMoney { get; private set; }
|
||||
|
||||
public CorpsePrefab(ContentXElement element, CorpsesFile file) : base(element, file) { }
|
||||
public CorpsePrefab(ContentXElement element, CorpsesFile file) : base(element, file, npcSetIdentifier: Identifier.Empty) { }
|
||||
|
||||
public static CorpsePrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(sync);
|
||||
}
|
||||
|
||||
+7
-2
@@ -50,6 +50,9 @@ namespace Barotrauma
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "The probability for the affliction to be applied."), Editable(minValue: 0f, maxValue: 1f)]
|
||||
public float Probability { get; set; } = 1.0f;
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Explosion damage is applied per each affected limb. Should this affliction damage be divided by the count of affected limbs (1-15) or applied in full? Default: true. Only affects explosions."), Editable]
|
||||
public bool DivideByLimbCount { get; set; }
|
||||
|
||||
public float DamagePerSecond;
|
||||
public float DamagePerSecondTimer;
|
||||
public float PreviousVitalityDecrease;
|
||||
@@ -96,9 +99,11 @@ namespace Barotrauma
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public Affliction CreateMultiplied(float multiplier)
|
||||
public Affliction CreateMultiplied(float multiplier, float probability)
|
||||
{
|
||||
return Prefab.Instantiate(NonClampedStrength * multiplier, Source);
|
||||
var instance = Prefab.Instantiate(NonClampedStrength * multiplier, Source);
|
||||
instance.Probability = probability;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public override string ToString() => Prefab == null ? "Affliction (Invalid)" : $"Affliction ({Prefab.Name})";
|
||||
|
||||
+2
-3
@@ -451,13 +451,12 @@ namespace Barotrauma
|
||||
|
||||
public static Identifier GetHuskedSpeciesName(Identifier speciesName, AfflictionPrefabHusk prefab)
|
||||
{
|
||||
return prefab.HuskedSpeciesName.Replace(AfflictionPrefabHusk.Tag, speciesName);
|
||||
return new Identifier(speciesName.Value + prefab.HuskedSpeciesName.Value);
|
||||
}
|
||||
|
||||
public static Identifier GetNonHuskedSpeciesName(Identifier huskedSpeciesName, AfflictionPrefabHusk prefab)
|
||||
{
|
||||
Identifier nonTag = prefab.HuskedSpeciesName.Remove(AfflictionPrefabHusk.Tag);
|
||||
return huskedSpeciesName.Remove(nonTag);
|
||||
return huskedSpeciesName.Remove(prefab.HuskedSpeciesName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-4
@@ -63,8 +63,10 @@ namespace Barotrauma
|
||||
if (HuskedSpeciesName.IsEmpty)
|
||||
{
|
||||
DebugConsole.NewMessage($"No 'huskedspeciesname' defined for the husk affliction ({Identifier}) in {element}", Color.Orange);
|
||||
HuskedSpeciesName = "[speciesname]husk".ToIdentifier();
|
||||
HuskedSpeciesName = "husk".ToIdentifier();
|
||||
}
|
||||
// Remove "[speciesname]" for backward support (we don't use it anymore)
|
||||
HuskedSpeciesName = HuskedSpeciesName.Remove("[speciesname]").ToIdentifier();
|
||||
TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty<Identifier>(), trim: true);
|
||||
if (TargetSpecies.Length == 0)
|
||||
{
|
||||
@@ -108,7 +110,6 @@ namespace Barotrauma
|
||||
|
||||
public readonly Identifier HuskedSpeciesName;
|
||||
public readonly Identifier[] TargetSpecies;
|
||||
public static readonly Identifier Tag = "[speciesname]".ToIdentifier();
|
||||
|
||||
public readonly bool TransferBuffs;
|
||||
public readonly bool SendMessages;
|
||||
@@ -404,8 +405,18 @@ namespace Barotrauma
|
||||
|
||||
AfflictionType = element.GetAttributeIdentifier("type", "");
|
||||
TranslationIdentifier = element.GetAttributeIdentifier("translationoverride", Identifier);
|
||||
Name = TextManager.Get($"AfflictionName.{TranslationIdentifier}").Fallback(element.GetAttributeString("name", ""));
|
||||
Description = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}").Fallback(element.GetAttributeString("description", ""));
|
||||
Name = TextManager.Get($"AfflictionName.{TranslationIdentifier}");
|
||||
string fallbackName = element.GetAttributeString("name", "");
|
||||
if (!string.IsNullOrEmpty(fallbackName))
|
||||
{
|
||||
Name = Name.Fallback(fallbackName);
|
||||
}
|
||||
Description = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
|
||||
string fallbackDescription = element.GetAttributeString("description", "");
|
||||
if (!string.IsNullOrEmpty(fallbackDescription))
|
||||
{
|
||||
Description = Description.Fallback(fallbackDescription);
|
||||
}
|
||||
IsBuff = element.GetAttributeBool("isbuff", false);
|
||||
|
||||
HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
|
||||
|
||||
@@ -60,13 +60,27 @@ namespace Barotrauma
|
||||
if (vitalityMultipliers != null)
|
||||
{
|
||||
float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
vitalityMultipliers.ForEach(i => VitalityMultipliers.Add(i, multiplier));
|
||||
foreach (var vitalityMultiplier in vitalityMultipliers)
|
||||
{
|
||||
VitalityMultipliers.Add(vitalityMultiplier, multiplier);
|
||||
if (AfflictionPrefab.Prefabs.None(p => p.Identifier == vitalityMultiplier))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?");
|
||||
}
|
||||
}
|
||||
}
|
||||
var vitalityTypeMultipliers = subElement.GetAttributeIdentifierArray("type", null) ?? subElement.GetAttributeIdentifierArray("types", null);
|
||||
if (vitalityTypeMultipliers != null)
|
||||
{
|
||||
float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
vitalityTypeMultipliers.ForEach(i => VitalityTypeMultipliers.Add(i, multiplier));
|
||||
foreach (var vitalityTypeMultiplier in vitalityTypeMultipliers)
|
||||
{
|
||||
VitalityTypeMultipliers.Add(vitalityTypeMultiplier, multiplier);
|
||||
if (AfflictionPrefab.Prefabs.None(p => p.AfflictionType == vitalityTypeMultiplier))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vitalityMultipliers == null && VitalityTypeMultipliers == null)
|
||||
{
|
||||
@@ -911,19 +925,11 @@ namespace Barotrauma
|
||||
float vitalityDecrease = affliction.GetVitalityDecrease(this);
|
||||
if (limbHealth != null)
|
||||
{
|
||||
if (limbHealth.VitalityMultipliers.ContainsKey(affliction.Prefab.Identifier))
|
||||
{
|
||||
vitalityDecrease *= limbHealth.VitalityMultipliers[affliction.Prefab.Identifier];
|
||||
}
|
||||
if (limbHealth.VitalityTypeMultipliers.ContainsKey(affliction.Prefab.AfflictionType))
|
||||
{
|
||||
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[affliction.Prefab.AfflictionType];
|
||||
}
|
||||
vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth);
|
||||
}
|
||||
Vitality -= vitalityDecrease;
|
||||
affliction.CalculateDamagePerSecond(vitalityDecrease);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (IsUnconscious)
|
||||
{
|
||||
@@ -932,6 +938,33 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
private float GetVitalityMultiplier(Affliction affliction, LimbHealth limbHealth)
|
||||
{
|
||||
float multiplier = 1.0f;
|
||||
if (limbHealth.VitalityMultipliers.TryGetValue(affliction.Prefab.Identifier, out float vitalityMultiplier))
|
||||
{
|
||||
multiplier *= vitalityMultiplier;
|
||||
}
|
||||
if (limbHealth.VitalityTypeMultipliers.TryGetValue(affliction.Prefab.AfflictionType, out float vitalityTypeMultiplier))
|
||||
{
|
||||
multiplier *= vitalityTypeMultiplier;
|
||||
}
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How much vitality the affliction reduces, taking into account the effects of vitality modifiers on the limb the affliction is on (if limb-based)
|
||||
/// </summary>
|
||||
private float GetVitalityDecreaseWithVitalityMultipliers(Affliction affliction)
|
||||
{
|
||||
float vitalityDecrease = affliction.GetVitalityDecrease(this);
|
||||
if (afflictions.TryGetValue(affliction, out LimbHealth limbHealth) && limbHealth != null)
|
||||
{
|
||||
vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth);
|
||||
}
|
||||
return vitalityDecrease;
|
||||
}
|
||||
|
||||
private void Kill()
|
||||
{
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
@@ -1021,7 +1054,7 @@ namespace Barotrauma
|
||||
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
|
||||
/// <param name="normalize">If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.</param>
|
||||
/// <param name="predictFutureDuration">If above 0, the method will take into account how much currently active status effects while affect the afflictions in the next x seconds.</param>
|
||||
public void GetSuitableTreatments(Dictionary<Identifier, float> treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float predictFutureDuration = 0.0f)
|
||||
public void GetSuitableTreatments(Dictionary<Identifier, float> treatmentSuitability, bool normalize, Character user, Limb limb = null, bool ignoreHiddenAfflictions = false, float predictFutureDuration = 0.0f)
|
||||
{
|
||||
//key = item identifier
|
||||
//float = suitability
|
||||
@@ -1045,7 +1078,18 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (strength <= affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
if (ignoreHiddenAfflictions && strength < affliction.Prefab.ShowIconThreshold) { continue; }
|
||||
|
||||
if (ignoreHiddenAfflictions)
|
||||
{
|
||||
if (user == Character)
|
||||
{
|
||||
if (strength < affliction.Prefab.ShowIconThreshold) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strength < affliction.Prefab.ShowIconToOthersThreshold) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Identifier, float> treatment in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
@@ -1129,17 +1173,17 @@ namespace Barotrauma
|
||||
activeAfflictions.Add(affliction);
|
||||
}
|
||||
}
|
||||
msg.Write((byte)activeAfflictions.Count);
|
||||
msg.WriteByte((byte)activeAfflictions.Count);
|
||||
foreach (Affliction affliction in activeAfflictions)
|
||||
{
|
||||
msg.Write(affliction.Prefab.UintIdentifier);
|
||||
msg.WriteUInt32(affliction.Prefab.UintIdentifier);
|
||||
msg.WriteRangedSingle(
|
||||
MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
|
||||
0.0f, affliction.Prefab.MaxStrength, 8);
|
||||
msg.Write((byte)affliction.Prefab.PeriodicEffects.Count());
|
||||
msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count);
|
||||
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects)
|
||||
{
|
||||
msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
|
||||
msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], 0, periodicEffect.MaxInterval, 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,15 +1197,15 @@ namespace Barotrauma
|
||||
limbAfflictions.Add((limbHealth, limbAffliction));
|
||||
}
|
||||
|
||||
msg.Write((byte)limbAfflictions.Count);
|
||||
msg.WriteByte((byte)limbAfflictions.Count);
|
||||
foreach (var (limbHealth, affliction) in limbAfflictions)
|
||||
{
|
||||
msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1);
|
||||
msg.Write(affliction.Prefab.UintIdentifier);
|
||||
msg.WriteUInt32(affliction.Prefab.UintIdentifier);
|
||||
msg.WriteRangedSingle(
|
||||
MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
|
||||
0.0f, affliction.Prefab.MaxStrength, 8);
|
||||
msg.Write((byte)affliction.Prefab.PeriodicEffects.Count());
|
||||
msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count);
|
||||
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects)
|
||||
{
|
||||
msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
|
||||
@@ -1210,7 +1254,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(XElement element)
|
||||
public void Load(XElement element, Func<AfflictionPrefab, bool> afflictionPredicate = null)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
@@ -1243,6 +1287,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error while loading character health: affliction \"{id}\" not found.");
|
||||
return;
|
||||
}
|
||||
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
||||
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
||||
var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
||||
if (irremovableAffliction != null)
|
||||
|
||||
@@ -86,6 +86,28 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
|
||||
}
|
||||
foreach (var afflictionType in parsedAfflictionTypes)
|
||||
{
|
||||
if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType))
|
||||
{
|
||||
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?");
|
||||
}
|
||||
}
|
||||
foreach (var afflictionIdentifier in parsedAfflictionIdentifiers)
|
||||
{
|
||||
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier))
|
||||
{
|
||||
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?");
|
||||
}
|
||||
}
|
||||
static void createWarningOrError(string msg)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(msg);
|
||||
#else
|
||||
DebugConsole.AddWarning(msg);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseAfflictionTypes()
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
class HumanPrefab : PrefabWithUintIdentifier
|
||||
{
|
||||
[Serialize("any", IsPropertySaveable.No)]
|
||||
public string Job { get; protected set; }
|
||||
public Identifier Job { get; protected set; }
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.No)]
|
||||
public float Commonness { get; protected set; }
|
||||
@@ -82,17 +82,19 @@ namespace Barotrauma
|
||||
public XElement Element { get; protected set; }
|
||||
|
||||
|
||||
public readonly Dictionary<XElement, float> ItemSets = new Dictionary<XElement, float>();
|
||||
public readonly Dictionary<XElement, float> CustomNPCSets = new Dictionary<XElement, float>();
|
||||
public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>();
|
||||
public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>();
|
||||
|
||||
public HumanPrefab(ContentXElement element, ContentFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
public readonly Identifier NpcSetIdentifier;
|
||||
|
||||
public HumanPrefab(ContentXElement element, ContentFile file, Identifier npcSetIdentifier) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
Job = Job.ToLowerInvariant();
|
||||
Element = element;
|
||||
element.GetChildElements("itemset").ForEach(e => ItemSets.Add(e, e.GetAttributeFloat("commonness", 1)));
|
||||
element.GetChildElements("character").ForEach(e => CustomNPCSets.Add(e, e.GetAttributeFloat("commonness", 1)));
|
||||
element.GetChildElements("itemset").ForEach(e => ItemSets.Add((e, e.GetAttributeFloat("commonness", 1))));
|
||||
element.GetChildElements("character").ForEach(e => CustomCharacterInfos.Add((e, e.GetAttributeFloat("commonness", 1))));
|
||||
PreferredOutpostModuleTypes = element.GetAttributeIdentifierArray("preferredoutpostmoduletypes", Array.Empty<Identifier>());
|
||||
this.NpcSetIdentifier = npcSetIdentifier;
|
||||
}
|
||||
|
||||
public IEnumerable<Identifier> GetModuleFlags()
|
||||
@@ -107,7 +109,7 @@ namespace Barotrauma
|
||||
|
||||
public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced, Func<JobPrefab, bool> predicate = null)
|
||||
{
|
||||
return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate);
|
||||
return !Job.IsEmpty && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate);
|
||||
}
|
||||
|
||||
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
|
||||
@@ -146,23 +148,43 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true)
|
||||
public bool GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true)
|
||||
{
|
||||
if (ItemSets == null || !ItemSets.Any()) { return; }
|
||||
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), randSync);
|
||||
if (ItemSets == null || !ItemSets.Any()) { return false; }
|
||||
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element;
|
||||
if (spawnItems != null)
|
||||
{
|
||||
foreach (XElement itemElement in spawnItems.GetChildElements("item"))
|
||||
{
|
||||
InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents);
|
||||
int amount = itemElement.GetAttributeInt("amount", 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public CharacterInfo GetCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
/// <summary>
|
||||
/// Creates a character info from the human prefab. If there are custom character infos defined, those are used, otherwise a randomized info is generated.
|
||||
/// </summary>
|
||||
/// <param name="randSync"></param>
|
||||
/// <returns></returns>
|
||||
public CharacterInfo CreateCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
{
|
||||
var characterElement = ToolBox.SelectWeightedRandom(CustomNPCSets.Keys.ToList(), CustomNPCSets.Values.ToList(), randSync);
|
||||
return characterElement != null ? new CharacterInfo(characterElement) : null;
|
||||
var characterElement = ToolBox.SelectWeightedRandom(CustomCharacterInfos, info => info.commonness, randSync).element;
|
||||
CharacterInfo characterInfo;
|
||||
if (characterElement == null)
|
||||
{
|
||||
characterInfo= new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: GetJobPrefab(randSync), npcIdentifier: Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterInfo = new CharacterInfo(characterElement, Identifier);
|
||||
}
|
||||
characterInfo.HumanPrefabIds = (NpcSetIdentifier, Identifier);
|
||||
return characterInfo;
|
||||
}
|
||||
|
||||
public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, Item parentItem = null, bool createNetworkEvents = true)
|
||||
@@ -229,7 +251,11 @@ namespace Barotrauma
|
||||
parentItem?.Combine(item, user: null);
|
||||
foreach (XElement childItemElement in itemElement.Elements())
|
||||
{
|
||||
InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents);
|
||||
int amount = childItemElement.GetAttributeInt("amount", 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Barotrauma
|
||||
|
||||
public int Variant;
|
||||
|
||||
public Skill PrimarySkill { get; }
|
||||
public Skill PrimarySkill { get; private set; }
|
||||
|
||||
public Job(JobPrefab jobPrefab) : this(jobPrefab, randSync: Rand.RandSync.Unsynced, variant: 0) { }
|
||||
|
||||
@@ -102,9 +102,14 @@ namespace Barotrauma
|
||||
public void OverrideSkills(Dictionary<Identifier, float> newSkills)
|
||||
{
|
||||
skills.Clear();
|
||||
foreach (var newSkill in newSkills)
|
||||
foreach (var newSkillInfo in newSkills)
|
||||
{
|
||||
skills.Add(newSkill.Key, new Skill(newSkill.Key, newSkill.Value));
|
||||
var newSkill = new Skill(newSkillInfo.Key, newSkillInfo.Value);
|
||||
if (PrimarySkill != null && newSkill.Identifier == PrimarySkill.Identifier)
|
||||
{
|
||||
PrimarySkill = newSkill;
|
||||
}
|
||||
skills.Add(newSkillInfo.Key, newSkill);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,13 +65,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<JobPrefab> Prefabs = new PrefabCollection<JobPrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
|
||||
private static readonly Dictionary<Identifier, float> _itemRepairPriorities = new Dictionary<Identifier, float>();
|
||||
/// <summary>
|
||||
@@ -79,7 +73,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static IReadOnlyDictionary<Identifier, float> ItemRepairPriorities => _itemRepairPriorities;
|
||||
|
||||
public static JobPrefab Get(string identifier)
|
||||
public static JobPrefab Get(Identifier identifier)
|
||||
{
|
||||
if (Prefabs.ContainsKey(identifier))
|
||||
{
|
||||
|
||||
@@ -218,6 +218,8 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale;
|
||||
|
||||
public Hull Hull;
|
||||
|
||||
public bool InWater { get; set; }
|
||||
|
||||
private FixedMouseJoint pullJoint;
|
||||
@@ -303,6 +305,17 @@ namespace Barotrauma
|
||||
public Vector2 DebugTargetPos;
|
||||
public Vector2 DebugRefPos;
|
||||
|
||||
public bool IsLowerBody =>
|
||||
type == LimbType.LeftLeg ||
|
||||
type == LimbType.RightLeg ||
|
||||
type == LimbType.LeftFoot ||
|
||||
type == LimbType.RightFoot ||
|
||||
type == LimbType.Tail ||
|
||||
type == LimbType.Legs ||
|
||||
type == LimbType.RightThigh ||
|
||||
type == LimbType.LeftThigh ||
|
||||
type == LimbType.Waist;
|
||||
|
||||
public bool IsSevered
|
||||
{
|
||||
get { return isSevered; }
|
||||
@@ -709,11 +722,12 @@ namespace Barotrauma
|
||||
tempModifiers.Clear();
|
||||
var newAffliction = affliction;
|
||||
float random = Rand.Value(Rand.RandSync.Unsynced);
|
||||
if (random > affliction.Probability) { continue; }
|
||||
bool foundMatchingModifier = false;
|
||||
bool applyAffliction = true;
|
||||
foreach (DamageModifier damageModifier in DamageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(affliction)) { continue; }
|
||||
foundMatchingModifier = true;
|
||||
if (random > affliction.Probability * damageModifier.ProbabilityMultiplier)
|
||||
{
|
||||
applyAffliction = false;
|
||||
@@ -729,6 +743,7 @@ namespace Barotrauma
|
||||
foreach (DamageModifier damageModifier in wearable.WearableComponent.DamageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(affliction)) { continue; }
|
||||
foundMatchingModifier = true;
|
||||
if (random > affliction.Probability * damageModifier.ProbabilityMultiplier)
|
||||
{
|
||||
applyAffliction = false;
|
||||
@@ -740,6 +755,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundMatchingModifier && random > affliction.Probability) { continue; }
|
||||
float finalDamageModifier = damageMultiplier;
|
||||
foreach (DamageModifier damageModifier in tempModifiers)
|
||||
{
|
||||
@@ -752,7 +768,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!MathUtils.NearlyEqual(finalDamageModifier, 1.0f))
|
||||
{
|
||||
newAffliction = affliction.CreateMultiplied(finalDamageModifier);
|
||||
newAffliction = affliction.CreateMultiplied(finalDamageModifier, affliction.Probability);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,9 +5,11 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class NPCPersonalityTrait
|
||||
class NPCPersonalityTrait : PrefabWithUintIdentifier
|
||||
{
|
||||
public readonly Identifier Name;
|
||||
public readonly static PrefabCollection<NPCPersonalityTrait> Traits = new PrefabCollection<NPCPersonalityTrait>();
|
||||
|
||||
public readonly LocalizedString DisplayName;
|
||||
|
||||
public readonly List<string> AllowedDialogTags;
|
||||
|
||||
@@ -17,43 +19,29 @@ namespace Barotrauma
|
||||
get { return commonness; }
|
||||
}
|
||||
|
||||
public static IEnumerable<NPCPersonalityTrait> GetAll(LanguageIdentifier language)
|
||||
public NPCPersonalityTrait(XElement element, NPCPersonalityTraitsFile file)
|
||||
: base(file, element.GetAttributeIdentifier("identifier", element.GetAttributeIdentifier("name", Identifier.Empty)))
|
||||
{
|
||||
if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language))
|
||||
string name = element.GetAttributeString("name", null);
|
||||
if (name == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead..");
|
||||
language = TextManager.DefaultLanguage;
|
||||
DisplayName = TextManager.Get("personalitytrait." + Identifier)
|
||||
.Fallback(Identifier.ToString());
|
||||
}
|
||||
return NPCConversationCollection.Collections[language]
|
||||
.SelectMany(cc => cc.PersonalityTraits.Values);
|
||||
}
|
||||
|
||||
public static NPCPersonalityTrait Get(LanguageIdentifier language, Identifier traitName)
|
||||
{
|
||||
if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language))
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead..");
|
||||
language = TextManager.DefaultLanguage;
|
||||
DisplayName = name;
|
||||
}
|
||||
return NPCConversationCollection.Collections[language]
|
||||
.FirstOrDefault(cc => cc.PersonalityTraits.ContainsKey(traitName))
|
||||
.PersonalityTraits[traitName];
|
||||
}
|
||||
|
||||
public NPCPersonalityTrait(XElement element)
|
||||
{
|
||||
Name = element.GetAttributeIdentifier("name", "");
|
||||
AllowedDialogTags = new List<string>(element.GetAttributeStringArray("alloweddialogtags", Array.Empty<string>()));
|
||||
commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
}
|
||||
|
||||
public static NPCPersonalityTrait GetRandom(string seed)
|
||||
{
|
||||
#warning TODO: implement NPCPersonality content type and revise this for determinism
|
||||
var rand = new MTRandom(ToolBox.StringToInt(seed));
|
||||
var list = GetAll(GameSettings.CurrentConfig.Language);
|
||||
return ToolBox.SelectWeightedRandom(list, t => t.commonness, rand);
|
||||
return ToolBox.SelectWeightedRandom(Traits.OrderBy(t => t.UintIdentifier), t => t.commonness, rand);
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAboveVitality : AbilityConditionDataless
|
||||
{
|
||||
@@ -13,7 +11,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.HealthPercentage / 100f > vitalityPercentage;
|
||||
return character.Vitality / character.MaxVitality > vitalityPercentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,11 +1,10 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAlliesAboveVitality : AbilityConditionDataless
|
||||
{
|
||||
float vitalityPercentage;
|
||||
readonly float vitalityPercentage;
|
||||
|
||||
public AbilityConditionAlliesAboveVitality(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionCoauthor : AbilityConditionDataless
|
||||
{
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionCrouched : AbilityConditionDataless
|
||||
{
|
||||
|
||||
+5
-11
@@ -1,28 +1,22 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasAffliction : AbilityConditionDataless
|
||||
{
|
||||
private string afflictionIdentifier;
|
||||
private float minimumPercentage;
|
||||
|
||||
private readonly Identifier afflictionIdentifier;
|
||||
private readonly float minimumPercentage;
|
||||
|
||||
public AbilityConditionHasAffliction(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
afflictionIdentifier = conditionElement.GetAttributeString("afflictionidentifier", "");
|
||||
afflictionIdentifier = conditionElement.GetAttributeIdentifier("afflictionidentifier", Identifier.Empty);
|
||||
minimumPercentage = conditionElement.GetAttributeFloat("minimumpercentage", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(afflictionIdentifier))
|
||||
if (!afflictionIdentifier.IsEmpty)
|
||||
{
|
||||
var affliction = character.CharacterHealth.GetAffliction(afflictionIdentifier);
|
||||
|
||||
if (affliction == null) { return false; }
|
||||
|
||||
return minimumPercentage <= affliction.Strength / affliction.Prefab.MaxStrength;
|
||||
}
|
||||
return false;
|
||||
|
||||
+15
-27
@@ -3,55 +3,43 @@ using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasItem : AbilityConditionDataless
|
||||
{
|
||||
// not used for anything atm, will be used for clown subclass
|
||||
private readonly string[] tags;
|
||||
private InvSlotType? invSlotType;
|
||||
bool requireAll;
|
||||
|
||||
private List<Item> items = new List<Item>();
|
||||
readonly bool requireAll;
|
||||
|
||||
public AbilityConditionHasItem(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>());
|
||||
requireAll = conditionElement.GetAttributeBool("requireall", false);
|
||||
//this.invSlotType = invSlotType;
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
items.Clear();
|
||||
if (tags.Any())
|
||||
if (tags.None())
|
||||
{
|
||||
foreach (string tag in tags)
|
||||
{
|
||||
// there is a better method, should use that instead
|
||||
if (character.GetEquippedItem(tag, invSlotType) is Item foundItem)
|
||||
{
|
||||
items.Add(foundItem);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.GetEquippedItem(null, invSlotType) is Item foundItem)
|
||||
{
|
||||
items.Add(foundItem);
|
||||
}
|
||||
return character.GetEquippedItem(null) is Item;
|
||||
}
|
||||
|
||||
if (requireAll)
|
||||
{
|
||||
return (items.Count >= tags.Count());
|
||||
foreach (string tag in tags)
|
||||
{
|
||||
if (character.GetEquippedItem(tag) == null) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return items.Any();
|
||||
foreach (string tag in tags)
|
||||
{
|
||||
if (character.GetEquippedItem(tag) != null) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasVelocity : AbilityConditionDataless
|
||||
{
|
||||
|
||||
+9
-6
@@ -1,7 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionShipFlooded : AbilityConditionDataless
|
||||
{
|
||||
@@ -14,8 +11,14 @@ namespace Barotrauma.Abilities
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (!character.IsInFriendlySub) { return false; }
|
||||
float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
|
||||
return currentFloodPercentage / 100 > floodPercentage;
|
||||
float waterVolume = 0.0f, totalVolume = 0.0f;
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != character.Submarine) { continue; }
|
||||
waterVolume += hull.WaterVolume;
|
||||
totalVolume += hull.Volume;
|
||||
}
|
||||
return (waterVolume / totalVolume) > floodPercentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -1,7 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyStatToFlooding : CharacterAbility
|
||||
{
|
||||
@@ -22,8 +19,14 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (conditionsMatched && Character.IsInFriendlySub)
|
||||
{
|
||||
float currentFloodPercentage = Character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
|
||||
lastValue = currentFloodPercentage / 100f * maxValue;
|
||||
float waterVolume = 0.0f, totalVolume = 0.0f;
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != Character.Submarine) { continue; }
|
||||
waterVolume += hull.WaterVolume;
|
||||
totalVolume += hull.Volume;
|
||||
}
|
||||
lastValue = (totalVolume == 0.0f ? 1.0f : waterVolume / totalVolume) * maxValue;
|
||||
Character.ChangeStat(statType, lastValue);
|
||||
}
|
||||
else
|
||||
|
||||
+7
-14
@@ -1,8 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityUnlockTree : CharacterAbility
|
||||
{
|
||||
@@ -14,22 +10,19 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (!TalentTree.JobTalentTrees.TryGet(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
|
||||
|
||||
var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab)));
|
||||
var subTree = talentTree.TalentSubTrees.Find(t => t.AllTalentIdentifiers.Contains(CharacterTalent.Prefab.Identifier));
|
||||
if (subTree == null) { return; }
|
||||
|
||||
subTree.ForceUnlock = true;
|
||||
if (!addingFirstTime) { return; }
|
||||
|
||||
foreach (var talentOption in subTree.TalentOptionStages)
|
||||
foreach (var talentId in subTree.AllTalentIdentifiers)
|
||||
{
|
||||
foreach (var talent in talentOption.Talents)
|
||||
if (talentId == CharacterTalent.Prefab.Identifier) { continue; }
|
||||
if (Character.GiveTalent(talentId))
|
||||
{
|
||||
if (talent == CharacterTalent.Prefab) { continue; }
|
||||
if (Character.GiveTalent(talent))
|
||||
{
|
||||
Character.Info.AdditionalTalentPoints++;
|
||||
}
|
||||
}
|
||||
Character.Info.AdditionalTalentPoints++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-9
@@ -1,10 +1,4 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityInsurancePolicy : CharacterAbility
|
||||
{
|
||||
@@ -19,10 +13,10 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (Character?.Info is CharacterInfo info)
|
||||
if (Character?.Info is CharacterInfo info && GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
int totalAmount = moneyPerMission * info.MissionsCompletedSinceDeath;
|
||||
Character.GiveMoney(totalAmount);
|
||||
campaign.Bank.Give(totalAmount);
|
||||
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier.Value);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,8 +1,4 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
@@ -17,7 +13,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
|
||||
if (!SelectedItemHasTag(Character)) { return; }
|
||||
|
||||
Character closestCharacter = null;
|
||||
float closestDistance = squaredMaxDistance;
|
||||
@@ -31,13 +27,17 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
}
|
||||
|
||||
if (closestCharacter?.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; }
|
||||
if (closestCharacter == null || !SelectedItemHasTag(closestCharacter)) { return; }
|
||||
|
||||
if (closestDistance < squaredMaxDistance)
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
|
||||
bool SelectedItemHasTag(Character character) =>
|
||||
(character.SelectedItem != null && character.SelectedItem.HasTag(tag)) ||
|
||||
(character.SelectedSecondaryItem != null && character.SelectedSecondaryItem.HasTag(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
public readonly AbilityEffectType AbilityEffectType;
|
||||
|
||||
protected int maxTriggerCount { get; }
|
||||
protected readonly int maxTriggerCount;
|
||||
protected int timesTriggered = 0;
|
||||
|
||||
|
||||
@@ -88,8 +88,6 @@ namespace Barotrauma.Abilities
|
||||
// XML
|
||||
private AbilityCondition ConstructCondition(CharacterTalent characterTalent, ContentXElement conditionElement, bool errorMessages = true)
|
||||
{
|
||||
AbilityCondition newCondition = null;
|
||||
|
||||
Type conditionType;
|
||||
string type = conditionElement.Name.ToString().ToLowerInvariant();
|
||||
try
|
||||
@@ -109,6 +107,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
object[] args = { characterTalent, conditionElement };
|
||||
|
||||
AbilityCondition newCondition;
|
||||
try
|
||||
{
|
||||
newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args);
|
||||
@@ -210,8 +209,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
|
||||
{
|
||||
AbilityFlags flagType = AbilityFlags.None;
|
||||
if (!Enum.TryParse(flagTypeString, true, out flagType))
|
||||
if (!Enum.TryParse(flagTypeString, true, out AbilityFlags flagType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
|
||||
}
|
||||
|
||||
@@ -56,11 +56,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,7 +18,12 @@ namespace Barotrauma
|
||||
|
||||
public static readonly PrefabCollection<TalentTree> JobTalentTrees = new PrefabCollection<TalentTree>();
|
||||
|
||||
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
|
||||
public readonly ImmutableArray<TalentSubTree> TalentSubTrees;
|
||||
|
||||
/// <summary>
|
||||
/// Talent identifiers of all the talents in this tree
|
||||
/// </summary>
|
||||
public readonly ImmutableHashSet<Identifier> AllTalentIdentifiers;
|
||||
|
||||
public ContentXElement ConfigElement
|
||||
{
|
||||
@@ -36,16 +40,19 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<TalentSubTree> subTrees = new List<TalentSubTree>();
|
||||
foreach (var subTreeElement in element.GetChildElements("subtree"))
|
||||
{
|
||||
TalentSubTrees.Add(new TalentSubTree(subTreeElement));
|
||||
subTrees.Add(new TalentSubTree(subTreeElement));
|
||||
}
|
||||
TalentSubTrees = subTrees.ToImmutableArray();
|
||||
AllTalentIdentifiers = TalentSubTrees.SelectMany(t => t.AllTalentIdentifiers).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public bool TalentIsInTree(Identifier talentIdentifier)
|
||||
{
|
||||
return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier);
|
||||
return AllTalentIdentifiers.Contains(talentIdentifier);
|
||||
}
|
||||
|
||||
public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier)
|
||||
@@ -54,6 +61,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// i hate this function - markus
|
||||
// me too - joonas
|
||||
public static TalentTreeStageState GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, List<Identifier> selectedTalents)
|
||||
{
|
||||
if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; }
|
||||
@@ -66,12 +74,12 @@ namespace Barotrauma
|
||||
|
||||
TalentOption targetTalentOption = subTree.TalentOptionStages[index];
|
||||
|
||||
if (targetTalentOption.Talents.Any(t => character.HasTalent(t.Identifier)))
|
||||
if (targetTalentOption.TalentIdentifiers.Any(t => character.HasTalent(t)))
|
||||
{
|
||||
return TalentTreeStageState.Unlocked;
|
||||
}
|
||||
|
||||
if (targetTalentOption.Talents.Any(t => selectedTalents.Contains(t.Identifier)))
|
||||
if (targetTalentOption.TalentIdentifiers.Any(t => selectedTalents.Contains(t)))
|
||||
{
|
||||
return TalentTreeStageState.Highlighted;
|
||||
}
|
||||
@@ -83,8 +91,8 @@ namespace Barotrauma
|
||||
if (lastindex >= 0)
|
||||
{
|
||||
TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex];
|
||||
hasTalentInLastTier = lastLatentOption.Talents.Any(HasTalent);
|
||||
isLastTalentPurchased = lastLatentOption.Talents.Any(t => character.HasTalent(t.Identifier));
|
||||
hasTalentInLastTier = lastLatentOption.TalentIdentifiers.Any(HasTalent);
|
||||
isLastTalentPurchased = lastLatentOption.TalentIdentifiers.Any(t => character.HasTalent(t));
|
||||
}
|
||||
|
||||
if (!hasTalentInLastTier)
|
||||
@@ -101,9 +109,9 @@ namespace Barotrauma
|
||||
|
||||
return TalentTreeStageState.Locked;
|
||||
|
||||
bool HasTalent(TalentPrefab t)
|
||||
bool HasTalent(Identifier talentId)
|
||||
{
|
||||
return selectedTalents.Contains(t.Identifier);
|
||||
return selectedTalents.Contains(talentId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,14 +125,14 @@ namespace Barotrauma
|
||||
|
||||
foreach (var subTree in talentTree.TalentSubTrees)
|
||||
{
|
||||
if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.Talents.Any(t => t.Identifier == talentIdentifier))) { return true; }
|
||||
if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.TalentIdentifiers.Contains(talentIdentifier))) { return true; }
|
||||
|
||||
foreach (var talentOptionStage in subTree.TalentOptionStages)
|
||||
{
|
||||
bool hasTalentInThisTier = talentOptionStage.Talents.Any(t => selectedTalents.Contains(t.Identifier));
|
||||
bool hasTalentInThisTier = talentOptionStage.TalentIdentifiers.Any(t => selectedTalents.Contains(t));
|
||||
if (!hasTalentInThisTier)
|
||||
{
|
||||
if (talentOptionStage.Talents.Any(t => t.Identifier == talentIdentifier))
|
||||
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -170,18 +178,21 @@ namespace Barotrauma
|
||||
|
||||
public bool ForceUnlock;
|
||||
|
||||
public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
|
||||
public readonly ImmutableArray<TalentOption> TalentOptionStages;
|
||||
|
||||
public readonly ImmutableHashSet<Identifier> AllTalentIdentifiers;
|
||||
|
||||
public TalentSubTree(ContentXElement subTreeElement)
|
||||
{
|
||||
Identifier = subTreeElement.GetAttributeIdentifier("identifier", "");
|
||||
|
||||
DisplayName = TextManager.Get("talenttree." + Identifier).Fallback(Identifier.Value);
|
||||
|
||||
List<TalentOption> talentOptionStages = new List<TalentOption>();
|
||||
foreach (var talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
|
||||
{
|
||||
TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
|
||||
talentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
|
||||
}
|
||||
TalentOptionStages = talentOptionStages.ToImmutableArray();
|
||||
AllTalentIdentifiers = TalentOptionStages.SelectMany(t => t.TalentIdentifiers).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -190,8 +201,12 @@ namespace Barotrauma
|
||||
{
|
||||
private readonly ImmutableHashSet<Identifier> talentIdentifiers;
|
||||
|
||||
public IEnumerable<TalentPrefab> Talents
|
||||
=> talentIdentifiers.Select(id => TalentPrefab.TalentPrefabs[id]);
|
||||
public IEnumerable<Identifier> TalentIdentifiers => talentIdentifiers;
|
||||
|
||||
public bool HasTalent(Identifier talentIdentifier)
|
||||
{
|
||||
return talentIdentifiers.Contains(talentIdentifier);
|
||||
}
|
||||
|
||||
public TalentOption(ContentXElement talentOptionsElement, Identifier debugIdentifier)
|
||||
{
|
||||
|
||||
+3
-1
@@ -40,7 +40,9 @@ namespace Barotrauma
|
||||
}
|
||||
catch
|
||||
{
|
||||
prefab.Dispose(); //clean up before rethrowing, since some prefab types might lock resources
|
||||
//clean up before rethrowing, since some prefab types might lock resources
|
||||
prefab.Dispose();
|
||||
Prefabs.Remove(prefab);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class NPCPersonalityTraitsFile : GenericPrefabFile<NPCPersonalityTrait>
|
||||
{
|
||||
public NPCPersonalityTraitsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "personalitytrait";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "personalitytraits";
|
||||
protected override PrefabCollection<NPCPersonalityTrait> Prefabs => NPCPersonalityTrait.Traits;
|
||||
protected override NPCPersonalityTrait CreatePrefab(ContentXElement element) => new NPCPersonalityTrait(element, this);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using Barotrauma;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class TutorialsFile : GenericPrefabFile<TutorialPrefab>
|
||||
{
|
||||
protected override PrefabCollection<TutorialPrefab> Prefabs => TutorialPrefab.Prefabs;
|
||||
|
||||
public TutorialsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "Tutorial";
|
||||
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "Tutorials";
|
||||
|
||||
protected override TutorialPrefab CreatePrefab(ContentXElement element) => new TutorialPrefab(this, element);
|
||||
}
|
||||
}
|
||||
+19
-18
@@ -29,25 +29,25 @@ namespace Barotrauma
|
||||
public readonly ImmutableArray<string> AltNames;
|
||||
public readonly string Path;
|
||||
public string Dir => Barotrauma.IO.Path.GetDirectoryName(Path) ?? "";
|
||||
public readonly UInt64 SteamWorkshopId;
|
||||
public readonly Option<ContentPackageId> UgcId;
|
||||
|
||||
public readonly Version GameVersion;
|
||||
public readonly string ModVersion;
|
||||
public Md5Hash Hash { get; private set; }
|
||||
public readonly DateTime? InstallTime;
|
||||
public readonly Option<DateTime> InstallTime;
|
||||
|
||||
public ImmutableArray<ContentFile> Files { get; private set; }
|
||||
public ImmutableArray<ContentFile.LoadError> Errors { get; private set; }
|
||||
|
||||
public async Task<bool> IsUpToDate()
|
||||
{
|
||||
if (SteamWorkshopId != 0 && InstallTime.HasValue)
|
||||
{
|
||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(SteamWorkshopId);
|
||||
if (item is null) { return true; }
|
||||
return item.Value.LatestUpdateTime <= InstallTime;
|
||||
}
|
||||
return true;
|
||||
if (!UgcId.TryUnwrap(out var ugcId)) { return true; }
|
||||
if (!(ugcId is SteamWorkshopId steamWorkshopId)) { return true; }
|
||||
if (!InstallTime.TryUnwrap(out var installTime)) { return true; }
|
||||
|
||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(steamWorkshopId.Value);
|
||||
if (item is null) { return true; }
|
||||
return item.Value.LatestUpdateTime <= installTime;
|
||||
}
|
||||
|
||||
public int Index => ContentPackageManager.EnabledPackages.IndexOf(this);
|
||||
@@ -66,18 +66,19 @@ namespace Barotrauma
|
||||
AltNames = rootElement.GetAttributeStringArray("altnames", Array.Empty<string>())
|
||||
.Select(n => n.Trim()).ToImmutableArray();
|
||||
AssertCondition(!string.IsNullOrEmpty(Name), "Name is null or empty");
|
||||
SteamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||
|
||||
UInt64 steamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||
|
||||
UgcId = steamWorkshopId != 0
|
||||
? Option<ContentPackageId>.Some(new SteamWorkshopId(steamWorkshopId))
|
||||
: Option<ContentPackageId>.None();
|
||||
|
||||
GameVersion = rootElement.GetAttributeVersion("gameversion", GameMain.Version);
|
||||
ModVersion = rootElement.GetAttributeString("modversion", DefaultModVersion);
|
||||
if (rootElement.Attribute("installtime") != null)
|
||||
{
|
||||
InstallTime = ToolBox.Epoch.ToDateTime(rootElement.GetAttributeUInt("installtime", 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallTime = null;
|
||||
}
|
||||
UInt64 installTimeUnix = rootElement.GetAttributeUInt64("installtime", 0);
|
||||
InstallTime = installTimeUnix != 0
|
||||
? Option<DateTime>.Some(ToolBox.Epoch.ToDateTime(installTimeUnix))
|
||||
: Option<DateTime>.None();
|
||||
|
||||
var fileResults = rootElement.Elements()
|
||||
.Select(e => ContentFile.CreateFromXElement(this, e))
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class ContentPackageId
|
||||
{
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public override string ToString()
|
||||
=> StringRepresentation;
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public static Option<ContentPackageId> Parse(string s)
|
||||
=> ReflectionUtils.ParseDerived<ContentPackageId, string>(s);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamWorkshopId : ContentPackageId
|
||||
{
|
||||
public readonly UInt64 Value;
|
||||
|
||||
public SteamWorkshopId(UInt64 value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
private const string Prefix = "STEAM_WORKSHOP_";
|
||||
|
||||
public override string StringRepresentation => Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is SteamWorkshopId otherWorkshopId && otherWorkshopId.Value == Value;
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
|
||||
public new static Option<SteamWorkshopId> Parse(string s)
|
||||
{
|
||||
if (s.StartsWith(Prefix)) { s = s[Prefix.Length..]; }
|
||||
if (!UInt64.TryParse(s, out var id) || id == 0) { return Option<SteamWorkshopId>.None(); }
|
||||
return Option<SteamWorkshopId>.Some(new SteamWorkshopId(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Steam;
|
||||
@@ -182,7 +181,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Core != null && !ContentPackageManager.CorePackages.Contains(Core))
|
||||
{
|
||||
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.SteamWorkshopId == Core.SteamWorkshopId) ??
|
||||
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.UgcId == Core.UgcId) ??
|
||||
ContentPackageManager.CorePackages.First());
|
||||
}
|
||||
|
||||
@@ -194,7 +193,7 @@ namespace Barotrauma
|
||||
newRegular.Add(p);
|
||||
}
|
||||
else if (ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2
|
||||
=> p2.SteamWorkshopId == p.SteamWorkshopId) is { } newP)
|
||||
=> p2.UgcId == p.UgcId) is { } newP)
|
||||
{
|
||||
newRegular.Add(newP);
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ namespace Barotrauma
|
||||
cachedValue = cachedValue
|
||||
.Replace(ModDirStr, modPath, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace(string.Format(OtherModDirFmt, ContentPackage.Name), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
if (ContentPackage.SteamWorkshopId != 0)
|
||||
if (ContentPackage.UgcId.TryUnwrap(out var ugcId))
|
||||
{
|
||||
cachedValue = cachedValue
|
||||
.Replace(string.Format(OtherModDirFmt, ContentPackage.SteamWorkshopId.ToString(CultureInfo.InvariantCulture)), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
.Replace(string.Format(OtherModDirFmt, ugcId.StringRepresentation), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
var allPackages = ContentPackageManager.AllPackages;
|
||||
@@ -55,9 +55,9 @@ namespace Barotrauma
|
||||
#endif
|
||||
foreach (Identifier otherModName in otherMods)
|
||||
{
|
||||
if (!UInt64.TryParse(otherModName.Value, out UInt64 workshopId)) { workshopId = 0; }
|
||||
Option<ContentPackageId> ugcId = ContentPackageId.Parse(otherModName.Value);
|
||||
ContentPackage? otherMod =
|
||||
allPackages.FirstOrDefault(p => workshopId != 0 && p.SteamWorkshopId != 0 && workshopId == p.SteamWorkshopId)
|
||||
allPackages.FirstOrDefault(p => ugcId == p.UgcId)
|
||||
?? allPackages.FirstOrDefault(p => p.Name == otherModName)
|
||||
?? allPackages.FirstOrDefault(p => p.NameMatches(otherModName))
|
||||
?? throw new MissingContentPackageException(ContentPackage, otherModName.Value);
|
||||
|
||||
@@ -84,7 +84,9 @@ namespace Barotrauma
|
||||
{
|
||||
static readonly List<CoroutineHandle> Coroutines = new List<CoroutineHandle>();
|
||||
|
||||
public static float UnscaledDeltaTime, DeltaTime;
|
||||
public static float DeltaTime { get; private set; }
|
||||
|
||||
public static bool Paused { get; private set; }
|
||||
|
||||
public static CoroutineHandle StartCoroutine(IEnumerable<CoroutineStatus> func, string name = "", bool useSeparateThread = false)
|
||||
{
|
||||
@@ -191,7 +193,7 @@ namespace Barotrauma
|
||||
if (current != null)
|
||||
{
|
||||
if (current.EndsCoroutine(handle) || handle.AbortRequested) { return true; }
|
||||
if (!current.CheckFinished(UnscaledDeltaTime)) { return false; }
|
||||
if (!current.CheckFinished(DeltaTime)) { return false; }
|
||||
}
|
||||
if (!handle.Coroutine.MoveNext()) { return true; }
|
||||
return false;
|
||||
@@ -204,7 +206,7 @@ namespace Barotrauma
|
||||
while (!handle.AbortRequested)
|
||||
{
|
||||
if (PerformCoroutineStep(handle)) { return; }
|
||||
Thread.Sleep((int)(UnscaledDeltaTime * 1000));
|
||||
Thread.Sleep((int)(DeltaTime * 1000));
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
@@ -232,7 +234,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (handle.Thread.ThreadState.HasFlag(ThreadState.Stopped))
|
||||
{
|
||||
if (handle.Exception!=null || handle.Coroutine.Current == CoroutineStatus.Failure)
|
||||
if (handle.Exception != null || handle.Coroutine.Current == CoroutineStatus.Failure)
|
||||
{
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
}
|
||||
@@ -254,9 +256,9 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
// Updating just means stepping through all the coroutines
|
||||
public static void Update(float unscaledDeltaTime, float deltaTime)
|
||||
public static void Update(bool paused, float deltaTime)
|
||||
{
|
||||
UnscaledDeltaTime = unscaledDeltaTime;
|
||||
Paused = paused;
|
||||
DeltaTime = deltaTime;
|
||||
|
||||
List<CoroutineHandle> coroutineList;
|
||||
@@ -276,14 +278,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ListCoroutines()
|
||||
{
|
||||
lock (Coroutines)
|
||||
{
|
||||
DebugConsole.NewMessage("***********");
|
||||
DebugConsole.NewMessage($"{Coroutines.Count} coroutine(s)");
|
||||
foreach (var c in Coroutines)
|
||||
{
|
||||
DebugConsole.NewMessage($"- {c.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WaitForSeconds : CoroutineStatus
|
||||
{
|
||||
public readonly float TotalTime;
|
||||
|
||||
float timer;
|
||||
bool ignorePause;
|
||||
private float timer;
|
||||
private readonly bool ignorePause;
|
||||
|
||||
public WaitForSeconds(float time, bool ignorePause = true)
|
||||
{
|
||||
@@ -295,7 +310,7 @@ namespace Barotrauma
|
||||
public override bool CheckFinished(float deltaTime)
|
||||
{
|
||||
#if !SERVER
|
||||
if (ignorePause || !GUI.PauseMenuOpen)
|
||||
if (ignorePause || !CoroutineManager.Paused)
|
||||
{
|
||||
timer -= deltaTime;
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember == null || args.Length == 0) return;
|
||||
|
||||
int.TryParse(args[0], out int id);
|
||||
var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == id);
|
||||
var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == id);
|
||||
if (client == null)
|
||||
{
|
||||
ThrowError("Client id \"" + id + "\" not found.");
|
||||
@@ -467,7 +467,7 @@ namespace Barotrauma
|
||||
banDuration = parsedBanDuration;
|
||||
}
|
||||
|
||||
GameMain.NetworkMember.BanPlayer(clientName, reason, false, banDuration);
|
||||
GameMain.NetworkMember.BanPlayer(clientName, reason, banDuration);
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -486,7 +486,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember == null || args.Length == 0) return;
|
||||
|
||||
int.TryParse(args[0], out int id);
|
||||
var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == id);
|
||||
var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == id);
|
||||
if (client == null)
|
||||
{
|
||||
ThrowError("Client id \"" + id + "\" not found.");
|
||||
@@ -510,12 +510,12 @@ namespace Barotrauma
|
||||
banDuration = parsedBanDuration;
|
||||
}
|
||||
|
||||
GameMain.NetworkMember.BanPlayer(client.Name, reason, false, banDuration);
|
||||
GameMain.NetworkMember.BanPlayer(client.Name, reason, banDuration);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
commands.Add(new Command("banendpoint|banip", "banendpoint [endpoint]: Ban the IP address/SteamID from the server.", null));
|
||||
commands.Add(new Command("banaddress|banip", "banaddress [endpoint]: Ban the IP address/SteamID from the server.", null));
|
||||
|
||||
commands.Add(new Command("teleportcharacter|teleport", "teleport [character name]: Teleport the specified character to the position of the cursor. If the name parameter is omitted, the controlled character will be teleported.", null,
|
||||
() =>
|
||||
@@ -799,7 +799,55 @@ namespace Barotrauma
|
||||
eventPrefabs.Select(prefab => prefab.Identifier).Distinct().Select(id => id.Value).ToArray()
|
||||
};
|
||||
}));
|
||||
|
||||
|
||||
commands.Add(new Command("unlockmission", "unlockmission [identifier/tag]: Unlocks a mission in a random adjacent level.", (string[] args) =>
|
||||
{
|
||||
if (!(GameMain.GameSession?.GameMode is CampaignMode campaign))
|
||||
{
|
||||
ThrowError("The unlockmission command is only usable in the campaign mode.");
|
||||
return;
|
||||
}
|
||||
if (args.Length == 0)
|
||||
{
|
||||
ThrowError("Please enter the identifier or a tag of the mission you want to unlock.");
|
||||
return;
|
||||
}
|
||||
var currentLocation = campaign.Map.CurrentLocation;
|
||||
if (MissionPrefab.Prefabs.Any(p => p.Identifier == args[0]))
|
||||
{
|
||||
currentLocation.UnlockMissionByIdentifier(args[0].ToIdentifier());
|
||||
}
|
||||
else
|
||||
{
|
||||
currentLocation.UnlockMissionByTag(args[0].ToIdentifier());
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
|
||||
}
|
||||
}, isCheat: true, getValidArgs: () =>
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
MissionPrefab.Prefabs.Select(p => p.Identifier.ToString()).ToArray()
|
||||
};
|
||||
}));
|
||||
|
||||
commands.Add(new Command("setcampaignmetadata", "setcampaignmetadata [identifier] [value]: Sets the specified campaign metadata value.", (string[] args) =>
|
||||
{
|
||||
if (!(GameMain.GameSession?.GameMode is CampaignMode campaign))
|
||||
{
|
||||
ThrowError("The setcampaignmetadata command is only usable in the campaign mode.");
|
||||
return;
|
||||
}
|
||||
if (args.Length < 2)
|
||||
{
|
||||
ThrowError("Please specify an identifier and a value.");
|
||||
return;
|
||||
}
|
||||
SetDataAction.PerformOperation(campaign.CampaignMetadata, args[0].ToIdentifier(), args[1], SetDataAction.OperationType.Set);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("setskill", "setskill [all/identifier] [max/level] [character]: Set your skill level.", (string[] args) =>
|
||||
{
|
||||
if (args.Length < 2)
|
||||
@@ -918,16 +966,10 @@ namespace Barotrauma
|
||||
|
||||
foreach (var talentTree in talentTrees)
|
||||
{
|
||||
foreach (var subTree in talentTree.TalentSubTrees)
|
||||
foreach (var talentId in talentTree.AllTalentIdentifiers)
|
||||
{
|
||||
foreach (var option in subTree.TalentOptionStages)
|
||||
{
|
||||
foreach (var talent in option.Talents)
|
||||
{
|
||||
character.GiveTalent(talent);
|
||||
NewMessage($"Unlocked talent \"{talent.DisplayName}\".");
|
||||
}
|
||||
}
|
||||
character.GiveTalent(talentId);
|
||||
NewMessage($"Unlocked talent \"{talentId}\".");
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1226,7 +1268,11 @@ namespace Barotrauma
|
||||
}
|
||||
}, () =>
|
||||
{
|
||||
return new[] { FactionPrefab.Prefabs.Select(f => f.Identifier.Value).ToArray() };
|
||||
return new[]
|
||||
{
|
||||
FactionPrefab.Prefabs.Select(f => f.Identifier.Value).ToArray(),
|
||||
GameMain.GameSession?.Campaign.Factions.Select(f => f.Prefab.Identifier.ToString()).ToArray() ?? Array.Empty<string>()
|
||||
};
|
||||
}, true));
|
||||
|
||||
commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) =>
|
||||
@@ -1257,102 +1303,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}, null, true));
|
||||
|
||||
commands.Add(new Command("upgradeitem", "upgradeitem [upgrade] [level] [items]: Adds an upgrade to the current targeted item.", args =>
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
int level;
|
||||
if (args.Length > 1)
|
||||
{
|
||||
if (int.TryParse(args[1], out int result))
|
||||
{
|
||||
level = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowError($"\"{args[1]}\" is not a valid level.");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowError("Parameter \"level\" is required.");
|
||||
return;
|
||||
}
|
||||
|
||||
var upgradePrefab = UpgradePrefab.Find(args[0].ToIdentifier());
|
||||
|
||||
if (upgradePrefab == null)
|
||||
{
|
||||
ThrowError($"Unknown upgrade: {args[0]}.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<MapEntity> targetItems = new List<MapEntity>();
|
||||
|
||||
if (upgradePrefab.IsWallUpgrade)
|
||||
{
|
||||
targetItems.AddRange(Submarine.MainSub.GetWalls(true).Cast<MapEntity>());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (args.Length > 2)
|
||||
{
|
||||
targetItems.AddRange(Item.ItemList.Where(item => item.Submarine == Submarine.MainSub).Where(item => item.HasTag(args[2])).Cast<MapEntity>());
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowError("Argument \"tag\" is required.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetItems.Any())
|
||||
{
|
||||
ThrowError("No valid items found.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (MapEntity targetItem in targetItems)
|
||||
{
|
||||
Upgrade existingUpgrade = targetItem.GetUpgrade(args[0].ToIdentifier());
|
||||
|
||||
if (!(targetItem is ISerializableEntity sEntity)) { continue; }
|
||||
|
||||
var upgrade = new Upgrade(sEntity, upgradePrefab, level);
|
||||
if (targetItem.AddUpgrade(upgrade, true))
|
||||
{
|
||||
if (existingUpgrade == null)
|
||||
{
|
||||
NewMessage($"Added {upgradePrefab.Identifier}:{level} to {sEntity.Name}.", Color.Green);
|
||||
upgrade.ApplyUpgrade();
|
||||
}
|
||||
else
|
||||
{
|
||||
NewMessage($"Set {sEntity.Name}'s {upgradePrefab.Identifier} upgrade to level {existingUpgrade.Level}.", Color.Cyan);
|
||||
existingUpgrade.ApplyUpgrade();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowError($"{upgrade.Prefab.Identifier} cannot be applied to {sEntity.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowError("Parameter \"upgrade\" is required.");
|
||||
}
|
||||
}, () =>
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().Select(i => i.Value).ToArray()
|
||||
};
|
||||
}, true));
|
||||
|
||||
commands.Add(new Command("maxupgrades", "maxupgrades [category] [prefab]: Maxes out all upgrades or only specific one if given arguments.", args =>
|
||||
{
|
||||
UpgradeManager upgradeManager = GameMain.GameSession?.Campaign?.UpgradeManager;
|
||||
@@ -1705,6 +1656,8 @@ namespace Barotrauma
|
||||
}, isCheat: false));
|
||||
|
||||
commands.Add(new Command("listtasks", "listtasks: Lists all asynchronous tasks currently in the task pool.", (string[] args) => { TaskPool.ListTasks(); }));
|
||||
|
||||
commands.Add(new Command("listcoroutines", "listcoroutines: Lists all coroutines currently running.", (string[] args) => { CoroutineManager.ListCoroutines(); }));
|
||||
|
||||
commands.Add(new Command("calculatehashes", "calculatehashes [content package name]: Show the MD5 hashes of the files in the selected content package. If the name parameter is omitted, the first content package is selected.", (string[] args) =>
|
||||
{
|
||||
@@ -2287,7 +2240,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void ShowError(string msg, Color? color = null)
|
||||
public static void LogError(string msg, Color? color = null)
|
||||
{
|
||||
color ??= Color.Red;
|
||||
NewMessage(msg, color.Value, isCommand: false, isError: true);
|
||||
@@ -2315,7 +2268,7 @@ namespace Barotrauma
|
||||
{
|
||||
NewMessage(msg, color.Value, isCommand: false, isError: false);
|
||||
}
|
||||
#if DEBUG
|
||||
#if DEBUG && CLIENT
|
||||
Console.WriteLine(msg);
|
||||
#endif
|
||||
}
|
||||
@@ -2459,7 +2412,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
ShowError(error);
|
||||
LogError(error);
|
||||
}
|
||||
|
||||
public static void AddWarning(string warning)
|
||||
@@ -2500,7 +2453,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
|
||||
fileName += DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString();
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var invalidChars = Path.GetInvalidFileNameCharsCrossPlatform();
|
||||
foreach (char invalidChar in invalidChars)
|
||||
{
|
||||
fileName = fileName.Replace(invalidChar.ToString(), "");
|
||||
@@ -2533,9 +2486,12 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
GameMain.DebugDraw = false;
|
||||
GameMain.LightManager.LightingEnabled = true;
|
||||
Character.DebugDrawInteract = false;
|
||||
#endif
|
||||
Hull.EditWater = false;
|
||||
Hull.EditFire = false;
|
||||
EnemyAIController.DisableEnemyAI = false;
|
||||
HumanAIController.DisableCrewAI = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
+11
-7
@@ -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,17 @@ 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;
|
||||
if (affliction.Prefab.LimbSpecific)
|
||||
{
|
||||
LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type;
|
||||
if (limbType == null || limbType != TargetLimb) { return false; }
|
||||
}
|
||||
return affliction.Strength >= MinStrength;
|
||||
});
|
||||
|
||||
if (afflictions.Any(a => a.Identifier == Identifier)) { return true; }
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
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.LogError($"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.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed.");
|
||||
}
|
||||
|
||||
static bool IsTargetTagAttribute(XAttribute attribute) => attribute.NameAsIdentifier() == "targettag";
|
||||
}
|
||||
|
||||
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.LogError($"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 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class CheckConnectionAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ConnectionName { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ConnectedItemTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OtherConnectionName { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int MinAmount { get; set; }
|
||||
|
||||
public CheckConnectionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
int amount = 0;
|
||||
var connectTargets = !ConnectedItemTag.IsEmpty ? ParentEvent.GetTargets(ConnectedItemTag) : Enumerable.Empty<Entity>();
|
||||
foreach (var target in ParentEvent.GetTargets(ItemTag))
|
||||
{
|
||||
if (target is not Item targetItem) { continue; }
|
||||
if (targetItem.GetComponent<ConnectionPanel>() is not ConnectionPanel panel) { continue; }
|
||||
if (panel.Connections == null || panel.Connections.None()) { continue; }
|
||||
foreach (var connection in panel.Connections)
|
||||
{
|
||||
if (!IsCorrectConnection(connection, ConnectionName)) { continue; }
|
||||
if (ConnectedItemTag.IsEmpty && OtherConnectionName.IsEmpty)
|
||||
{
|
||||
amount += connection.Wires.Count();
|
||||
if (amount >= MinAmount) { return true; }
|
||||
continue;
|
||||
}
|
||||
foreach (var wire in connection.Wires)
|
||||
{
|
||||
if (wire.OtherConnection(connection) is not Connection otherConnection) { continue; }
|
||||
if (!ConnectedItemTag.IsEmpty && !IsCorrectConnection(otherConnection, OtherConnectionName)) { continue; }
|
||||
if (!ConnectedItemTag.IsEmpty && !IsCorrectItem()) { continue; }
|
||||
amount++;
|
||||
if (amount >= MinAmount) { return true; }
|
||||
bool IsCorrectItem() => connectTargets.Contains(otherConnection.Item);
|
||||
}
|
||||
|
||||
bool IsCorrectConnection(Connection connection, Identifier id) => connection.Name.ToIdentifier() == id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -36,15 +35,32 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public CheckDataAction(ContentXElement element, string parentDebugString) : base(null, element)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Condition))
|
||||
{
|
||||
Condition = element.GetAttributeString("value", string.Empty)!;
|
||||
if (string.IsNullOrEmpty(Condition))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in scripted event \"{parentDebugString}\". CheckDataAction with no condition set ({element}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetSuccess()
|
||||
{
|
||||
return DetermineSuccess() ?? false;
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
if (!(GameMain.GameSession?.GameMode is CampaignMode campaignMode)) { return false; }
|
||||
if (GameMain.GameSession?.GameMode is not CampaignMode campaignMode) { return false; }
|
||||
|
||||
string[] splitString = Condition.Split(' ');
|
||||
string value = Condition;
|
||||
string value;
|
||||
if (splitString.Length > 0)
|
||||
{
|
||||
#warning Is this correct?
|
||||
//the first part of the string is the operator, skip it
|
||||
value = string.Join(" ", splitString.Skip(1));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -14,6 +15,20 @@ namespace Barotrauma
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ItemTags { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the first target when the check succeeds.")]
|
||||
public Identifier ApplyTagToTarget { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RequireEquipped { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int ItemContainerIndex { get; set; }
|
||||
|
||||
private readonly IReadOnlyList<PropertyConditional> conditionals;
|
||||
|
||||
private readonly Identifier[] itemIdentifierSplit;
|
||||
private readonly Identifier[] itemTags;
|
||||
@@ -22,6 +37,19 @@ namespace Barotrauma
|
||||
{
|
||||
itemIdentifierSplit = ItemIdentifiers.Split(',').ToIdentifiers();
|
||||
itemTags = ItemTags.Split(",").ToIdentifiers();
|
||||
var conditionalList = new List<PropertyConditional>();
|
||||
foreach (ContentXElement subElement in element.GetChildElements("conditional"))
|
||||
{
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute))
|
||||
{
|
||||
conditionalList.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
conditionals = conditionalList;
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
@@ -30,23 +58,72 @@ namespace Barotrauma
|
||||
if (!targets.Any()) { return null; }
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (!(target is Character chr)) { continue; }
|
||||
if (chr.Inventory == null) { continue; }
|
||||
|
||||
if (itemTags.Any(tag => chr.Inventory.FindItemByTag(tag, recursive: true) != null)) { return true; }
|
||||
|
||||
foreach (var identifier in itemIdentifierSplit)
|
||||
if (target is Character character)
|
||||
{
|
||||
if (chr.Inventory.FindItemByIdentifier(identifier, recursive: true) != null)
|
||||
Inventory inventory = character.Inventory;
|
||||
if (CheckInventory(character.Inventory, character))
|
||||
{
|
||||
if (!ApplyTagToTarget.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToTarget, target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (target is Item item)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var itemContainer in item.GetComponents<ItemContainer>())
|
||||
{
|
||||
if (ItemContainerIndex == -1 || i == ItemContainerIndex)
|
||||
{
|
||||
if (CheckInventory(itemContainer.Inventory, character: null))
|
||||
{
|
||||
if (!ApplyTagToTarget.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToTarget, target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckInventory(Inventory inventory, Character character)
|
||||
{
|
||||
if (inventory == null) { return false; }
|
||||
int count = 0;
|
||||
foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier)))
|
||||
{
|
||||
if (!ConditionalsMatch(item, character)) { continue; }
|
||||
count++;
|
||||
if (count >= Amount) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ConditionalsMatch(Item item, Character character = null)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
foreach (PropertyConditional conditional in conditionals)
|
||||
{
|
||||
if (!conditional.Matches(item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (RequireEquipped)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
return character.HasEquippedItem(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CheckItemAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
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; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderTargetTag { get; set; }
|
||||
|
||||
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Character targetCharacter = null;
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (t is Character c)
|
||||
{
|
||||
targetCharacter = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target character was found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
var currentOrderInfo = targetCharacter.GetCurrentOrderWithTopPriority();
|
||||
if (currentOrderInfo?.Identifier == OrderIdentifier)
|
||||
{
|
||||
if (!OrderTargetTag.IsEmpty)
|
||||
{
|
||||
if (currentOrderInfo.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)) { return false; }
|
||||
}
|
||||
return OrderOption.IsEmpty || currentOrderInfo?.Option == OrderOption;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
{
|
||||
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckSelectedItemAction : BinaryOptionAction
|
||||
{
|
||||
public enum SelectedItemType { Primary, Secondary, Any };
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CharacterTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)]
|
||||
public SelectedItemType ItemType { get; set; }
|
||||
|
||||
public CheckSelectedItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Character character = null;
|
||||
if (!CharacterTag.IsEmpty)
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(CharacterTag))
|
||||
{
|
||||
if (t is Character c)
|
||||
{
|
||||
character = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (character == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
IEnumerable<Entity> targets = ParentEvent.GetTargets(TargetTag);
|
||||
if (targets.None())
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target is not Item targetItem)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (IsSelected(targetItem))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
bool IsSelected(Item item)
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => character.IsAnySelectedItem(item),
|
||||
SelectedItemType.Primary => character.SelectedItem == item,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == item,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => !character.HasSelectedAnyItem,
|
||||
SelectedItemType.Primary => character.SelectedItem == null,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == null,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
{
|
||||
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckSelectedAction : BinaryOptionAction
|
||||
{
|
||||
public enum SelectedItemType { Primary, Secondary, Any };
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CharacterTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)]
|
||||
public SelectedItemType ItemType { get; set; }
|
||||
|
||||
public CheckSelectedAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Character character = null;
|
||||
if (!CharacterTag.IsEmpty)
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(CharacterTag))
|
||||
{
|
||||
if (t is Character c)
|
||||
{
|
||||
character = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (character == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
IEnumerable<Entity> targets = ParentEvent.GetTargets(TargetTag);
|
||||
if (targets.None())
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
if (ItemType == SelectedItemType.Any && character.SelectedCharacter == targetCharacter) { return true; }
|
||||
continue;
|
||||
}
|
||||
if (target is not Item targetItem)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (IsSelected(targetItem))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
bool IsSelected(Item item)
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => character.IsAnySelectedItem(item),
|
||||
SelectedItemType.Primary => character.SelectedItem == item,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == item,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => !character.HasSelectedAnyItem,
|
||||
SelectedItemType.Primary => character.SelectedItem == null,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == null,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
{
|
||||
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal sealed class CheckTalentAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TalentIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
public CheckTalentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
if (TargetTag.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Character? matchingCharacter = null;
|
||||
|
||||
foreach (Entity entity in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (entity is Character character)
|
||||
{
|
||||
matchingCharacter = character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matchingCharacter is not null && matchingCharacter.HasTalent(TalentIdentifier);
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string subActionStr = "";
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}";
|
||||
}
|
||||
|
||||
return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(CheckTalentAction)} -> (Talent: {TalentIdentifier.ColorizeObject()}" +
|
||||
$" Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" +
|
||||
subActionStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,11 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool ContinueConversation { get; set; }
|
||||
|
||||
private Character speaker;
|
||||
public Character speaker
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private AIObjective prevIdleObjective, prevGotoObjective;
|
||||
|
||||
@@ -120,7 +124,7 @@ namespace Barotrauma
|
||||
#else
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.InGame && c.Character != null) { ServerWrite(speaker, c); }
|
||||
if (c.InGame && c.Character != null) { ServerWrite(speaker, c, interrupt); }
|
||||
}
|
||||
#endif
|
||||
ResetSpeaker();
|
||||
@@ -331,9 +335,11 @@ namespace Barotrauma
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
|
||||
if (!targets.Any() || IsBlockedByAnotherConversation(targets)) { return; }
|
||||
if (!targets.Any() || IsBlockedByAnotherConversation(targets, BlockOtherConversationsDuration)) { return; }
|
||||
}
|
||||
|
||||
if (targetCharacter != null && IsBlockedByAnotherConversation(targetCharacter.ToEnumerable(), 0.1f)) { return; }
|
||||
|
||||
if (speaker?.AIController is HumanAIController humanAI)
|
||||
{
|
||||
prevIdleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Barotrauma
|
||||
|
||||
public static EventAction Instantiate(ScriptedEvent scriptedEvent, ContentXElement element)
|
||||
{
|
||||
Type actionType = null;
|
||||
Type actionType;
|
||||
try
|
||||
{
|
||||
actionType = Type.GetType("Barotrauma." + element.Name, true, true);
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GodModeAction : EventAction
|
||||
@@ -10,6 +5,9 @@ namespace Barotrauma
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the character's active afflictions be updated (e.g. applying visual effects of the afflictions)")]
|
||||
public bool UpdateAfflictions { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
@@ -35,9 +33,16 @@ namespace Barotrauma
|
||||
{
|
||||
if (target != null && target is Character character)
|
||||
{
|
||||
character.GodMode = Enabled;
|
||||
if (UpdateAfflictions)
|
||||
{
|
||||
character.CharacterHealth.Unkillable = Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.GodMode = Enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class InventoryHighlightAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemIdentifier { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int ItemContainerIndex { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool Recursive { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public InventoryHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MessageBoxAction : EventAction
|
||||
{
|
||||
public enum ActionType { Create, ConnectObjective, Close, Clear }
|
||||
|
||||
[Serialize(ActionType.Create, IsPropertySaveable.Yes)]
|
||||
public ActionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Identifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string Tag { get; set; }
|
||||
|
||||
[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 CloseOnSelectTag { 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("", IsPropertySaveable.Yes)]
|
||||
public Identifier CloseOnInRoomName { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTag { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (Identifier.IsEmpty)
|
||||
{
|
||||
Identifier = element.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
|
||||
public override string ToDebugString() => $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MessageBoxAction)}";
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.GameSession.GameMode is CampaignMode campaign)
|
||||
{
|
||||
MissionPrefab prefab = null;
|
||||
Mission unlockedMission = null;
|
||||
var unlockLocation = FindUnlockLocation();
|
||||
if (unlockLocation == null && CreateLocationIfNotFound)
|
||||
{
|
||||
@@ -72,27 +72,34 @@ namespace Barotrauma
|
||||
{
|
||||
if (!MissionIdentifier.IsEmpty)
|
||||
{
|
||||
prefab = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier);
|
||||
unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier);
|
||||
}
|
||||
else if (!MissionTag.IsEmpty)
|
||||
{
|
||||
prefab = unlockLocation.UnlockMissionByTag(MissionTag);
|
||||
unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag);
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
|
||||
}
|
||||
if (prefab != null)
|
||||
if (unlockedMission != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Unlocked mission \"{prefab.Name}\" in the location \"{unlockLocation.Name}\".");
|
||||
#if CLIENT
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name),
|
||||
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
if (unlockedMission.Locations[0] == unlockedMission.Locations[1] || unlockedMission.Locations[1] ==null)
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
DebugConsole.NewMessage($"Unlocked mission \"{unlockedMission.Name}\" in the location \"{unlockLocation.Name}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Unlocked mission \"{unlockedMission.Name}\" in the connection from \"{unlockedMission.Locations[0].Name}\" to \"{unlockedMission.Locations[1].Name}\".");
|
||||
}
|
||||
#if CLIENT
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", unlockedMission.Name),
|
||||
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: unlockedMission.Prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
{
|
||||
IconColor = unlockedMission.Prefab.IconColor
|
||||
};
|
||||
#else
|
||||
NotifyMissionUnlock(prefab);
|
||||
#else
|
||||
NotifyMissionUnlock(unlockedMission);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -138,16 +145,17 @@ namespace Barotrauma
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionAction)} -> ({(MissionIdentifier.IsEmpty ? MissionTag : MissionIdentifier)})";
|
||||
}
|
||||
|
||||
|
||||
#if SERVER
|
||||
private void NotifyMissionUnlock(MissionPrefab prefab)
|
||||
private void NotifyMissionUnlock(Mission mission)
|
||||
{
|
||||
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(mission.Prefab.Identifier);
|
||||
outmsg.WriteString(mission.Name.Value);
|
||||
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-13
@@ -16,6 +16,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AddToCrew { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RemoveFromCrew { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public NPCChangeTeamAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
@@ -35,34 +38,47 @@ namespace Barotrauma
|
||||
if (AddToCrew && (TeamTag == CharacterTeamType.Team1 || TeamTag == CharacterTeamType.Team2))
|
||||
{
|
||||
npc.Info.StartItemsGiven = true;
|
||||
|
||||
GameMain.GameSession.CrewManager.AddCharacter(npc);
|
||||
ChangeItemTeam(Submarine.MainSub, true);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamTag, npc.Inventory.AllItems));
|
||||
}
|
||||
}
|
||||
else if (RemoveFromCrew && (npc.TeamID == CharacterTeamType.Team1 || npc.TeamID == CharacterTeamType.Team2))
|
||||
{
|
||||
npc.Info.StartItemsGiven = true;
|
||||
GameMain.GameSession.CrewManager.RemoveCharacter(npc, removeInfo: true);
|
||||
var sub = Submarine.Loaded.FirstOrDefault(s => s.TeamID == TeamTag);
|
||||
ChangeItemTeam(sub, false);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.RemoveFromCrewEventData(TeamTag, npc.Inventory.AllItems));
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeItemTeam(Submarine sub, bool allowStealing)
|
||||
{
|
||||
foreach (Item item in npc.Inventory.AllItems)
|
||||
{
|
||||
item.AllowStealing = true;
|
||||
var wifiComponent = item.GetComponent<Items.Components.WifiComponent>();
|
||||
if (wifiComponent != null)
|
||||
item.AllowStealing = allowStealing;
|
||||
if (item.GetComponent<Items.Components.WifiComponent>() is { } wifiComponent)
|
||||
{
|
||||
wifiComponent.TeamID = TeamTag;
|
||||
}
|
||||
var idCard = item.GetComponent<Items.Components.IdCard>();
|
||||
if (idCard != null)
|
||||
if (item.GetComponent<Items.Components.IdCard>() is { } idCard)
|
||||
{
|
||||
idCard.TeamID = TeamTag;
|
||||
idCard.SubmarineSpecificID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
WayPoint subWaypoint =
|
||||
WayPoint.WayPointList.Find(wp => wp.Submarine == Submarine.MainSub && wp.SpawnType == SpawnType.Human && wp.AssignedJob == npc.Info.Job?.Prefab) ??
|
||||
WayPoint.WayPointList.Find(wp => wp.Submarine == Submarine.MainSub && wp.SpawnType == SpawnType.Human);
|
||||
WayPoint subWaypoint =
|
||||
WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human && wp.AssignedJob == npc.Info.Job?.Prefab) ??
|
||||
WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human);
|
||||
if (subWaypoint != null)
|
||||
{
|
||||
npc.GiveIdCardTags(subWaypoint, createNetworkEvent: true);
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamTag, npc.Inventory.AllItems));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -17,6 +14,12 @@ namespace Barotrauma
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool Follow { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int MaxTargets { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool AbandonOnReset { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public NPCFollowAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
@@ -32,6 +35,7 @@ namespace Barotrauma
|
||||
target = ParentEvent.GetTargets(TargetTag).FirstOrDefault();
|
||||
if (target == null) { return; }
|
||||
|
||||
int targetCount = 0;
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
@@ -56,6 +60,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
targetCount++;
|
||||
if (MaxTargets > -1 && targetCount >= MaxTargets)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
@@ -67,11 +76,11 @@ namespace Barotrauma
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
if (affectedNpcs != null && target != null)
|
||||
if (affectedNpcs != null && target != null && AbandonOnReset)
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
if (npc.Removed || npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveGoTo>())
|
||||
{
|
||||
if (goToObjective.Target == target)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class NPCOperateItemAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier NPCTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemComponentName { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderOption { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RequireEquip { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool Operate { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int MaxTargets { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool AbandonOnReset { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public NPCOperateItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
private Item target = null;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
target = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Item;
|
||||
if (target == null) { return; }
|
||||
|
||||
int targetCount = 0;
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
|
||||
if (Operate)
|
||||
{
|
||||
ItemComponentName = "Controller".ToIdentifier();
|
||||
var itemComponent = target.Components.FirstOrDefault(ic => ItemComponentName == ic.Name);
|
||||
if (itemComponent == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error in NPCOperateItemAction: could not find the component \"{ItemComponentName}\" in item \"{target.Name}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
var newObjective = new AIObjectiveOperateItem(itemComponent, npc, humanAiController.ObjectiveManager, OrderOption, RequireEquip)
|
||||
{
|
||||
OverridePriority = 100.0f
|
||||
};
|
||||
humanAiController.ObjectiveManager.AddObjective(newObjective);
|
||||
humanAiController.ObjectiveManager.WaitTimer = 0.0f;
|
||||
humanAiController.ObjectiveManager.Objectives.RemoveAll(o => o is AIObjectiveGoTo gotoOjective);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var objective in humanAiController.ObjectiveManager.Objectives)
|
||||
{
|
||||
if (objective is AIObjectiveOperateItem operateItemObjective && operateItemObjective.OperateTarget == target)
|
||||
{
|
||||
objective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
targetCount++;
|
||||
if (MaxTargets > -1 && targetCount >= MaxTargets)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
if (affectedNpcs != null && target != null && AbandonOnReset)
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
foreach (var operateItemObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveOperateItem>())
|
||||
{
|
||||
if (operateItemObjective.OperateTarget == target)
|
||||
{
|
||||
operateItemObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
target = null;
|
||||
affectedNpcs = null;
|
||||
}
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(AIObjectiveOperateItem)} -> (NPCTag: {NPCTag.ColorizeObject()}, TargetTag: {TargetTag.ColorizeObject()}, Operate: {Operate.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (!(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
if (npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
|
||||
if (Wait)
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController)) { continue; }
|
||||
if (npc.Removed || npc.AIController is not HumanAIController) { continue; }
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
gotoObjective.Abandon = true;
|
||||
|
||||
@@ -57,6 +57,12 @@ 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(100.0f, IsPropertySaveable.Yes)]
|
||||
public float Offset { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")]
|
||||
public string TargetModuleTags
|
||||
{
|
||||
@@ -115,10 +121,16 @@ 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)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Offset), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
@@ -145,10 +157,15 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!SpeciesName.IsEmpty)
|
||||
{
|
||||
if (!AllowDuplicates && Character.CharacterList.Any(c => c.SpeciesName == SpeciesName))
|
||||
{
|
||||
spawned = true;
|
||||
return;
|
||||
}
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawn: newCharacter =>
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Offset), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
@@ -194,7 +211,7 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawned: onSpawned);
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Offset), onSpawned: onSpawned);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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,48 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
class TeleportAction : EventAction
|
||||
{
|
||||
public enum TeleportPosition { MainSub, Outpost }
|
||||
|
||||
[Serialize(TeleportPosition.MainSub, IsPropertySaveable.Yes)]
|
||||
public TeleportPosition Position { get; set; }
|
||||
|
||||
[Serialize(SpawnType.Human, IsPropertySaveable.Yes)]
|
||||
public SpawnType SpawnType { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string SpawnPointTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TeleportAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
Submarine sub = Position switch
|
||||
{
|
||||
TeleportPosition.MainSub => Submarine.MainSub,
|
||||
TeleportPosition.Outpost => GameMain.GameSession?.Level?.StartOutpost,
|
||||
_ => null
|
||||
};
|
||||
if (WayPoint.GetRandom(spawnType: SpawnType, sub: sub, spawnPointTag: SpawnPointTag) is WayPoint wp)
|
||||
{
|
||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (target is Character c)
|
||||
{
|
||||
c.TeleportTo(wp.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
}
|
||||
@@ -157,8 +157,9 @@ namespace Barotrauma
|
||||
npcsOrItems.Add(item);
|
||||
}
|
||||
item.CampaignInteractionType = CampaignMode.InteractionType.Examine;
|
||||
if (player.SelectedConstruction == item ||
|
||||
player.Inventory != null && player.Inventory.Contains(item) ||
|
||||
if (player.SelectedItem == item ||
|
||||
player.SelectedSecondaryItem == item ||
|
||||
(player.Inventory != null && player.Inventory.Contains(item)) ||
|
||||
(player.FocusedItem == item && player.IsKeyHit(InputType.Use)))
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
|
||||
+32
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class TutorialHighlightAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool State { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class TutorialIconAction : EventAction
|
||||
{
|
||||
public enum ActionType { Add, Remove, RemoveTarget, RemoveIcon, Clear };
|
||||
|
||||
[Serialize(ActionType.Add, IsPropertySaveable.Yes)]
|
||||
public ActionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier IconStyle { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialIconAction(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)
|
||||
{
|
||||
if (ParentEvent.GetTargets(TargetTag).FirstOrDefault() is Entity target)
|
||||
{
|
||||
if (Type == ActionType.Add)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.Add((target, IconStyle));
|
||||
}
|
||||
else if(Type == ActionType.Remove)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.RemoveAll(i => i.entity == target && i.iconStyle == IconStyle);
|
||||
}
|
||||
else if (Type == ActionType.RemoveTarget)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.RemoveAll(i => i.entity == target);
|
||||
}
|
||||
else if (Type == ActionType.RemoveIcon)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.RemoveAll(i => i.iconStyle == IconStyle);
|
||||
}
|
||||
else if (Type == ActionType.Clear)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goToLabel)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TutorialSegmentAction : EventAction
|
||||
{
|
||||
public enum SegmentActionType { Trigger, Add, Complete, CompleteAndRemove, Remove };
|
||||
|
||||
[Serialize(SegmentActionType.Trigger, IsPropertySaveable.Yes)]
|
||||
public SegmentActionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Identifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTag { 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; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (Identifier.IsEmpty)
|
||||
{
|
||||
Identifier = element.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class UIHighlightAction : EventAction
|
||||
{
|
||||
public enum ElementId
|
||||
{
|
||||
None,
|
||||
RepairButton,
|
||||
PumpSpeedSlider,
|
||||
PassiveSonarIndicator,
|
||||
ActiveSonarIndicator,
|
||||
SonarModeSwitch,
|
||||
DirectionalSonarFrame,
|
||||
SteeringModeSwitch,
|
||||
MaintainPosTickBox,
|
||||
AutoTempSwitch,
|
||||
PowerButton,
|
||||
FissionRateSlider,
|
||||
TurbineOutputSlider,
|
||||
DeconstructButton,
|
||||
RechargeSpeedSlider,
|
||||
CPRButton
|
||||
}
|
||||
|
||||
[Serialize(ElementId.None, IsPropertySaveable.Yes)]
|
||||
public ElementId Id { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier EntityIdentifier { get; set; }
|
||||
|
||||
[Serialize(OrderCategory.Emergency, IsPropertySaveable.Yes)]
|
||||
public OrderCategory OrderCategory { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderOption { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderTargetTag { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool Bounce { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public UIHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<Event> activeEvents = new List<Event>();
|
||||
|
||||
private readonly HashSet<Event> finishedEvents = new HashSet<Event>();
|
||||
private readonly HashSet<EventPrefab> nonRepeatableEvents = new HashSet<EventPrefab>();
|
||||
|
||||
|
||||
#if DEBUG && SERVER
|
||||
private DateTime nextIntensityLogTime;
|
||||
#endif
|
||||
@@ -169,54 +173,48 @@ namespace Barotrauma
|
||||
CreateEvents(additiveSet);
|
||||
}
|
||||
|
||||
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
|
||||
if (level?.LevelData != null)
|
||||
{
|
||||
//if the outpost is connected to a locked connection, create an event to unlock it
|
||||
if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false)
|
||||
if (level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
var unlockPathPrefabs = EventPrefab.Prefabs.Where(e => e.UnlockPathEvent);
|
||||
var unlockPathPrefabsForBiome = unlockPathPrefabs.Where(e =>
|
||||
e.BiomeIdentifier.IsEmpty ||
|
||||
e.BiomeIdentifier == level.LevelData.Biome.Identifier);
|
||||
//if the outpost is connected to a locked connection, create an event to unlock it
|
||||
if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false)
|
||||
{
|
||||
var unlockPathPrefabs = EventPrefab.Prefabs.Where(e => e.UnlockPathEvent);
|
||||
var unlockPathPrefabsForBiome = unlockPathPrefabs.Where(e =>
|
||||
e.BiomeIdentifier.IsEmpty ||
|
||||
e.BiomeIdentifier == level.LevelData.Biome.Identifier);
|
||||
|
||||
var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ?
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, b => b.Commonness, rand) :
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabs, b => b.Commonness, rand);
|
||||
if (unlockPathEventPrefab != null)
|
||||
{
|
||||
var newEvent = unlockPathEventPrefab.CreateInstance();
|
||||
newEvent.Init();
|
||||
ActiveEvents.Add(newEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if no event that unlocks the path can be found, unlock it automatically
|
||||
level.StartLocation.Connections.ForEach(c => c.Locked = false);
|
||||
}
|
||||
}
|
||||
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
{
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
}
|
||||
AddChildEvents(initialEventSet);
|
||||
void AddChildEvents(EventSet eventSet)
|
||||
{
|
||||
if (eventSet == null) { return; }
|
||||
if (eventSet.OncePerOutpost)
|
||||
{
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
|
||||
var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ?
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, b => b.Commonness, rand) :
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabs, b => b.Commonness, rand);
|
||||
if (unlockPathEventPrefab != null)
|
||||
{
|
||||
if (!level.LevelData.NonRepeatableEvents.Contains(ep))
|
||||
{
|
||||
level.LevelData.NonRepeatableEvents.Add(ep);
|
||||
}
|
||||
var newEvent = unlockPathEventPrefab.CreateInstance();
|
||||
ActiveEvents.Add(newEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if no event that unlocks the path can be found, unlock it automatically
|
||||
level.StartLocation.Connections.ForEach(c => c.Locked = false);
|
||||
}
|
||||
}
|
||||
foreach (EventSet childSet in eventSet.ChildSets)
|
||||
|
||||
AddChildEvents(initialEventSet);
|
||||
void AddChildEvents(EventSet eventSet)
|
||||
{
|
||||
AddChildEvents(childSet);
|
||||
if (eventSet == null) { return; }
|
||||
if (eventSet.OncePerOutpost)
|
||||
{
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
|
||||
{
|
||||
nonRepeatableEvents.Add(ep);
|
||||
}
|
||||
}
|
||||
foreach (EventSet childSet in eventSet.ChildSets)
|
||||
{
|
||||
AddChildEvents(childSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,6 +222,7 @@ namespace Barotrauma
|
||||
PreloadContent(GetFilesToPreload());
|
||||
|
||||
roundDuration = 0.0f;
|
||||
eventsInitialized = false;
|
||||
isCrewAway = false;
|
||||
crewAwayDuration = 0.0f;
|
||||
crewAwayResetTimer = 0.0f;
|
||||
@@ -350,13 +349,33 @@ namespace Barotrauma
|
||||
selectedEvents.Clear();
|
||||
activeEvents.Clear();
|
||||
QueuedEvents.Clear();
|
||||
finishedEvents.Clear();
|
||||
nonRepeatableEvents.Clear();
|
||||
|
||||
preloadedSprites.ForEach(s => s.Remove());
|
||||
preloadedSprites.Clear();
|
||||
|
||||
|
||||
pathFinder = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the exhaustible events in the level as exhausted, and adds the current events to the event history
|
||||
/// </summary>
|
||||
public void RegisterEventHistory()
|
||||
{
|
||||
level.LevelData.EventsExhausted = true;
|
||||
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
{
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
}
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
|
||||
}
|
||||
}
|
||||
|
||||
public void SkipEventCooldown()
|
||||
{
|
||||
eventCoolDown = 0.0f;
|
||||
@@ -375,6 +394,8 @@ namespace Barotrauma
|
||||
selectedEvents.Remove(eventSet);
|
||||
if (level == null) { return; }
|
||||
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
|
||||
if (eventSet.Exhaustible && level.LevelData.EventsExhausted) { return; }
|
||||
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true);
|
||||
|
||||
int applyCount = 1;
|
||||
@@ -427,7 +448,8 @@ namespace Barotrauma
|
||||
if (suitablePrefabSubsets.Any())
|
||||
{
|
||||
var unusedEvents = suitablePrefabSubsets.ToList();
|
||||
for (int j = 0; j < eventSet.EventCount; j++)
|
||||
int eventCount = eventSet.GetEventCount(level);
|
||||
for (int j = 0; j < eventCount; j++)
|
||||
{
|
||||
if (unusedEvents.All(e => e.EventPrefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; }
|
||||
EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, e => e.EventPrefabs.Max(p => CalculateCommonness(p, e.Commonness)), rand);
|
||||
@@ -438,7 +460,6 @@ namespace Barotrauma
|
||||
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(eventSet);
|
||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
@@ -468,8 +489,6 @@ namespace Barotrauma
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, rand);
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(eventSet);
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<Event>());
|
||||
@@ -592,12 +611,25 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool eventsInitialized;
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (!Enabled || level == null) { return; }
|
||||
if (GameMain.GameSession.Campaign?.DisableEvents ?? false) { return; }
|
||||
|
||||
if (!eventsInitialized)
|
||||
{
|
||||
foreach (var eventSet in selectedEvents.Keys)
|
||||
{
|
||||
foreach (var ev in selectedEvents[eventSet])
|
||||
{
|
||||
ev.Init(eventSet);
|
||||
}
|
||||
}
|
||||
eventsInitialized = true;
|
||||
}
|
||||
|
||||
//clients only calculate the intensity but don't create any events
|
||||
//(the intensity is used for controlling the background music)
|
||||
CalculateCurrentIntensity(deltaTime);
|
||||
@@ -706,7 +738,18 @@ namespace Barotrauma
|
||||
|
||||
foreach (Event ev in activeEvents)
|
||||
{
|
||||
if (!ev.IsFinished) { ev.Update(deltaTime); }
|
||||
if (!ev.IsFinished)
|
||||
{
|
||||
ev.Update(deltaTime);
|
||||
}
|
||||
else if (!finishedEvents.Contains(ev))
|
||||
{
|
||||
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
if (!level.LevelData.EventHistory.Contains(ev.Prefab)) { level.LevelData.EventHistory.Add(ev.Prefab); }
|
||||
}
|
||||
finishedEvents.Add(ev);
|
||||
}
|
||||
}
|
||||
|
||||
if (QueuedEvents.Count > 0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user