Unstable 0.17.1.0
This commit is contained in:
@@ -388,7 +388,7 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetingTag == null)
|
||||
if (targetingTag.IsNullOrEmpty())
|
||||
{
|
||||
if (targetItem.GetComponent<Sonar>() != null)
|
||||
{
|
||||
@@ -2100,15 +2100,11 @@ namespace Barotrauma
|
||||
if (!ActiveAttack.IsRunning)
|
||||
{
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
|
||||
{
|
||||
Networking.NetEntityEvent.Type.SetAttackTarget,
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
|
||||
attackingLimb,
|
||||
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
|
||||
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
|
||||
SimPosition.X,
|
||||
SimPosition.Y
|
||||
});
|
||||
damageTarget,
|
||||
targetLimb,
|
||||
SimPosition));
|
||||
#else
|
||||
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
|
||||
#endif
|
||||
@@ -2696,7 +2692,7 @@ namespace Barotrauma
|
||||
float target = targetParams.Threshold;
|
||||
if (targetParams.ThresholdMin > 0 && targetParams.ThresholdMax > 0)
|
||||
{
|
||||
target = selectedTargetingParams == targetParams ? targetParams.ThresholdMax : targetParams.ThresholdMin;
|
||||
target = selectedTargetingParams == targetParams && State == AIState.FleeTo ? targetParams.ThresholdMax : targetParams.ThresholdMin;
|
||||
}
|
||||
if (Character.HealthPercentage > target)
|
||||
{
|
||||
|
||||
@@ -569,7 +569,8 @@ namespace Barotrauma
|
||||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
|
||||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOnAlsoWhenInactive) ||
|
||||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
|
||||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10;
|
||||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10 ||
|
||||
Character.CurrentHull.IsWetRoom;
|
||||
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
|
||||
bool removeDivingSuit = !shouldKeepTheGearOn && !IsOrderedToWait();
|
||||
if (oxygenLow && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
|
||||
@@ -1265,15 +1266,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsFriendly(attacker))
|
||||
{
|
||||
if (Character.Submarine == null)
|
||||
if (c.Submarine == null)
|
||||
{
|
||||
// Outside
|
||||
return attacker.Submarine == null ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
if (!Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
|
||||
if (!c.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
|
||||
{
|
||||
// Attacked from an unconnected submarine.
|
||||
return Character.SelectedConstruction?.GetComponent<Turret>() != null ? AIObjectiveCombat.CombatMode.None : AIObjectiveCombat.CombatMode.Retreat;
|
||||
return c.SelectedConstruction?.GetComponent<Turret>() != null ? AIObjectiveCombat.CombatMode.None : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
return c.AIController is HumanAIController humanAI &&
|
||||
(humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders))
|
||||
@@ -1285,18 +1286,22 @@ namespace Barotrauma
|
||||
{
|
||||
cumulativeDamage = 100;
|
||||
}
|
||||
if (GameMain.IsSingleplayer && attacker.IsPlayer && Character.TeamID == attacker.TeamID)
|
||||
if (attacker.IsPlayer && c.TeamID == attacker.TeamID)
|
||||
{
|
||||
// Bots in the player team never act aggressively in single player when attacked by the player
|
||||
return cumulativeDamage > minorDamageThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
|
||||
if (GameMain.IsSingleplayer || Character.TeamID != attacker.TeamID)
|
||||
{
|
||||
// Bots in the player team never act aggressively in single player when attacked by the player
|
||||
// In multiplayer, they react only to players attacking them or other crew members
|
||||
return Character == c && cumulativeDamage > minorDamageThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
}
|
||||
if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
|
||||
if (c.Submarine == null || !c.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
|
||||
{
|
||||
// Outside or attacked from an unconnected submarine -> don't react.
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
// If there are any enemies around, just ignore the friendly fire
|
||||
if (Character.CharacterList.Any(ch => ch.Submarine == Character.Submarine && !ch.Removed && !ch.IsIncapacitated && !IsFriendly(ch) && VisibleHulls.Contains(ch.CurrentHull)))
|
||||
if (Character.CharacterList.Any(ch => ch.Submarine == c.Submarine && !ch.Removed && !ch.IsIncapacitated && !IsFriendly(ch) && VisibleHulls.Contains(ch.CurrentHull)))
|
||||
{
|
||||
isAttackerFightingEnemy = true;
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
@@ -1352,18 +1357,19 @@ namespace Barotrauma
|
||||
|
||||
Character FindInstigator()
|
||||
{
|
||||
if (Character.IsInstigator)
|
||||
if (attacker.IsInstigator)
|
||||
{
|
||||
return Character;
|
||||
return attacker;
|
||||
}
|
||||
else if (c.AIController is HumanAIController humanAi)
|
||||
if (c.IsInstigator)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
if (c.AIController is HumanAIController humanAi)
|
||||
{
|
||||
return Character.CharacterList.FirstOrDefault(ch => ch.Submarine == c.Submarine && !ch.Removed && !ch.IsIncapacitated && ch.IsInstigator && humanAi.VisibleHulls.Contains(ch.CurrentHull));
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1497,7 +1503,7 @@ namespace Barotrauma
|
||||
if (hull == null ||
|
||||
hull.WaterPercentage > 90 ||
|
||||
hull.LethalPressure > 0 ||
|
||||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.5f))
|
||||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
|
||||
{
|
||||
needsSuit = !Character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
return true;
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ namespace Barotrauma
|
||||
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
|
||||
}
|
||||
if (item.ConditionPercentage <= 0) { return false; }
|
||||
if (item.IsClaimedByBallastFlora) { return false; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
if (IsReady(battery)) { return false; }
|
||||
return true;
|
||||
|
||||
+3
-1
@@ -83,7 +83,8 @@ namespace Barotrauma
|
||||
container.HasTag("allowcleanup") &&
|
||||
container.ParentInventory == null && container.OwnInventory != null && container.OwnInventory.AllItems.Any() &&
|
||||
container.GetComponent<ItemContainer>() != null &&
|
||||
IsItemInsideValidSubmarine(container, character);
|
||||
IsItemInsideValidSubmarine(container, character) &&
|
||||
!container.IsClaimedByBallastFlora;
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
|
||||
{
|
||||
@@ -100,6 +101,7 @@ namespace Barotrauma
|
||||
if (!IsValidContainer(item.Container, character, allowUnloading)) { return false; }
|
||||
}
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
if (pickable is Holdable h && h.Attachable && h.Attached) { return false; }
|
||||
|
||||
+1
-1
@@ -777,7 +777,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (retreatTarget != null && character.CurrentHull != retreatTarget)
|
||||
{
|
||||
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true)
|
||||
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager)
|
||||
{
|
||||
UsePathingOutside = false
|
||||
},
|
||||
|
||||
-1
@@ -162,7 +162,6 @@ namespace Barotrauma
|
||||
CloseEnough = reach,
|
||||
DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak".ToIdentifier() : Identifier.Empty,
|
||||
TargetName = Leak.FlowTargetHull?.DisplayName,
|
||||
CheckVisibility = false,
|
||||
requiredCondition = () => Leak.Submarine == character.Submarine,
|
||||
// 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()
|
||||
|
||||
+6
-13
@@ -1,7 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
@@ -11,6 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "go to".ToIdentifier();
|
||||
|
||||
public override bool KeepDivingGearOn => GetTargetHull() == null;
|
||||
|
||||
private AIObjectiveFindDivingGear findDivingGear;
|
||||
private readonly bool repeat;
|
||||
//how long until the path to the target is declared unreachable
|
||||
@@ -74,14 +75,6 @@ namespace Barotrauma
|
||||
_closeEnough = Math.Max(minDistance, value);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Currently we never check the visibility (to the end node), which is actually unintentional.
|
||||
// I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better.
|
||||
// However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true.
|
||||
|
||||
// NOTE: This seemes to have caused an issue now Regalis11/Barotrauma#8067: namely, the bot was trying to use a waypoint that was obstructed by a shuttle
|
||||
// because obstruction was only checked when checking visibility in PathFinder. Changed that so that obstructed nodes are no longer used.
|
||||
public bool CheckVisibility { get; set; }
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
|
||||
@@ -268,15 +261,15 @@ namespace Barotrauma
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
bool needsDivingGear = (needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit)) && character.NeedsAir;
|
||||
bool needsDivingGear = (needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit));
|
||||
if (Mimic)
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget) && character.NeedsAir)
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
{
|
||||
needsDivingGear = true;
|
||||
needsDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.NeedsAir)
|
||||
else if (HumanAIController.HasDivingMask(followTarget))
|
||||
{
|
||||
needsDivingGear = true;
|
||||
}
|
||||
@@ -505,7 +498,7 @@ namespace Barotrauma
|
||||
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
|
||||
endNodeFilter: endNodeFilter,
|
||||
nodeFilter: nodeFilter,
|
||||
checkVisiblity: CheckVisibility);
|
||||
checkVisiblity: Target is Item || Target is Character);
|
||||
}
|
||||
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
|
||||
{
|
||||
|
||||
+1
@@ -60,6 +60,7 @@ namespace Barotrauma
|
||||
if (targetCondition.HasValue && container.Inventory.IsFull() && container.Inventory.AllItems.None(i => ItemMatchesTargetCondition(i, targetCondition.Value))) { return false; }
|
||||
if (!AIObjectiveCleanupItems.IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.GetRootInventoryOwner() is Character owner && owner != character) { return false; }
|
||||
if (item.IsClaimedByBallastFlora) { return false; }
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
// Ignore items that require power but don't have it
|
||||
if (item.GetComponent<Powered>() is Powered powered && powered.PowerConsumption > 0 && powered.Voltage < powered.MinVoltage) { return false; }
|
||||
|
||||
+22
-20
@@ -10,6 +10,16 @@ namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveManager
|
||||
{
|
||||
public enum ObjectiveType
|
||||
{
|
||||
None = 0,
|
||||
Order = 1,
|
||||
Objective = 2,
|
||||
|
||||
MinValue = 0,
|
||||
MaxValue = 2
|
||||
}
|
||||
|
||||
public const float HighestOrderPriority = 70;
|
||||
public const float LowestOrderPriority = 60;
|
||||
public const float RunPriority = 50;
|
||||
@@ -184,28 +194,20 @@ namespace Barotrauma
|
||||
{
|
||||
var previousObjective = CurrentObjective;
|
||||
var firstObjective = Objectives.FirstOrDefault();
|
||||
|
||||
bool currentObjectiveIsOrder = CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority;
|
||||
if (currentObjectiveIsOrder)
|
||||
|
||||
CurrentObjective = currentObjectiveIsOrder ? CurrentOrder : firstObjective;
|
||||
|
||||
if (previousObjective == CurrentObjective) { return CurrentObjective; }
|
||||
|
||||
previousObjective?.OnDeselected();
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>().CalculatePriority(Math.Max(CurrentObjective.Priority - 10, 0));
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
CurrentObjective = CurrentOrder;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentObjective = firstObjective;
|
||||
}
|
||||
if (previousObjective != CurrentObjective)
|
||||
{
|
||||
previousObjective?.OnDeselected();
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>().CalculatePriority(Math.Max(CurrentObjective.Priority - 10, 0));
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ObjectiveManagerState,
|
||||
currentObjectiveIsOrder ? "order" : "objective"
|
||||
});
|
||||
}
|
||||
GameMain.NetworkMember.CreateEntityEvent(character,
|
||||
new Character.ObjectiveManagerStateEventData(currentObjectiveIsOrder ? ObjectiveType.Order : ObjectiveType.Objective));
|
||||
}
|
||||
return CurrentObjective;
|
||||
}
|
||||
|
||||
+5
@@ -67,6 +67,11 @@ namespace Barotrauma
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
else if (targetItem.IsClaimedByBallastFlora)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
var reactor = component?.Item.GetComponent<Reactor>();
|
||||
if (reactor != null)
|
||||
{
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ namespace Barotrauma
|
||||
if (!character.Submarine.IsConnectedTo(pump.Item.Submarine)) { return false; }
|
||||
}
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
if (pump.Item.IsClaimedByBallastFlora) { return false; }
|
||||
if (IsReady(pump)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
+5
-1
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
@@ -12,6 +11,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "repair item".ToIdentifier();
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool KeepDivingGearOn => Item?.CurrentHull == null;
|
||||
|
||||
public Item Item { get; private set; }
|
||||
|
||||
@@ -52,6 +52,10 @@ namespace Barotrauma
|
||||
Priority = 0;
|
||||
IsCompleted = true;
|
||||
}
|
||||
else if (Item.IsClaimedByBallastFlora)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float distanceFactor = 1;
|
||||
|
||||
+1
@@ -151,6 +151,7 @@ namespace Barotrauma
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.Submarine == null || character.Submarine == null) { return false; }
|
||||
if (item.IsClaimedByBallastFlora) { return false; }
|
||||
//player crew ignores items in outposts
|
||||
if (character.IsOnPlayerTeam && item.Submarine.Info.IsOutpost) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return false; }
|
||||
|
||||
+12
-9
@@ -30,6 +30,7 @@ namespace Barotrauma
|
||||
private float findHullTimer;
|
||||
private bool ignoreOxygen;
|
||||
private readonly float findHullInterval = 1.0f;
|
||||
private bool performedCpr;
|
||||
|
||||
public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -220,12 +221,12 @@ namespace Barotrauma
|
||||
DialogueIdentifier = "dialogcannotreachpatient".ToIdentifier(),
|
||||
TargetName = targetCharacter.DisplayName
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () =>
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
Abandon = true;
|
||||
});
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () =>
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
Abandon = true;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -401,6 +402,7 @@ namespace Barotrauma
|
||||
{
|
||||
character.SelectCharacter(targetCharacter);
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
performedCpr = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -436,9 +438,10 @@ namespace Barotrauma
|
||||
{
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name).Value,
|
||||
null, 1.0f, $"targethealed{targetCharacter.Name}".ToIdentifier(), 60.0f);
|
||||
{
|
||||
string textTag = performedCpr ? "DialogTargetResuscitated" : "DialogTargetHealed";
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", targetCharacter.Name)?.Value;
|
||||
character.Speak(message, delay: 1.0f, identifier: $"targethealed{targetCharacter.Name}".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
return isCompleted;
|
||||
}
|
||||
|
||||
@@ -392,7 +392,6 @@ namespace Barotrauma
|
||||
return option;
|
||||
}
|
||||
|
||||
|
||||
public ImmutableArray<Identifier> GetTargetItems(Identifier option = default)
|
||||
{
|
||||
if (option.IsEmpty || !OptionTargetItems.TryGetValue(option, out ImmutableArray<Identifier> optionTargetItems))
|
||||
@@ -418,6 +417,28 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create an Order instance with a null target
|
||||
/// </summary>
|
||||
public Order CreateInstance(OrderTargetType targetType, Character orderGiver = null, bool isAutonomous = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
return targetType switch
|
||||
{
|
||||
OrderTargetType.Entity => new Order(this, targetEntity: null, targetItem: null, orderGiver, isAutonomous),
|
||||
OrderTargetType.Position => new Order(this, target: null, orderGiver),
|
||||
OrderTargetType.WallSection => new Order(this, wall: null, sectionIndex: null, orderGiver),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
catch (NotImplementedException e)
|
||||
{
|
||||
DebugConsole.ShowError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Order
|
||||
@@ -510,28 +531,45 @@ namespace Barotrauma
|
||||
public readonly bool UseController;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for order instances
|
||||
/// Constructor for orders with the target type OrderTargetType.Entity
|
||||
/// </summary>
|
||||
public Order(OrderPrefab prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
: this(prefab, Identifier.Empty, 0, OrderType.Current, null, targetEntity, targetItem, orderGiver, isAutonomous) { }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.Entity
|
||||
/// </summary>
|
||||
public Order(OrderPrefab prefab, Identifier option, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
: this(prefab, option, 0, OrderType.Current, null, targetEntity, targetItem, orderGiver, isAutonomous) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.Position
|
||||
/// </summary>
|
||||
public Order(OrderPrefab prefab, OrderTarget target, Character orderGiver = null)
|
||||
: this(prefab, prefab.Options.FirstOrDefault(), 0, OrderType.Current, null, target, orderGiver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.Position
|
||||
/// </summary>
|
||||
public Order(OrderPrefab prefab, Identifier option, OrderTarget target, Character orderGiver = null)
|
||||
: this(prefab, option, 0, OrderType.Current, null, target, orderGiver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.WallSection
|
||||
/// </summary>
|
||||
public Order(OrderPrefab prefab, Structure wall, int? sectionIndex, Character orderGiver = null)
|
||||
: this(prefab, Identifier.Empty, 0, OrderType.Current, null, wall, sectionIndex, orderGiver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.WallSection
|
||||
/// </summary>
|
||||
public Order(OrderPrefab prefab, Identifier option, Structure wall, int? sectionIndex, Character orderGiver = null)
|
||||
: this(prefab, option, 0, OrderType.Current, null, wall, sectionIndex, orderGiver) { }
|
||||
|
||||
public Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.Entity
|
||||
/// </summary>
|
||||
private Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Option = option;
|
||||
@@ -561,14 +599,20 @@ namespace Barotrauma
|
||||
TargetType = OrderTargetType.Entity;
|
||||
}
|
||||
|
||||
public Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, OrderTarget target, Character orderGiver = null)
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.Position
|
||||
/// </summary>
|
||||
private Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, OrderTarget target, Character orderGiver = null)
|
||||
: this(prefab, option, manualPriority, orderType, aiObjective, targetEntity: null, targetItem: null, orderGiver)
|
||||
{
|
||||
TargetPosition = target;
|
||||
TargetType = OrderTargetType.Position;
|
||||
}
|
||||
|
||||
public Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, Structure wall, int? sectionIndex, Character orderGiver = null)
|
||||
/// <summary>
|
||||
/// Constructor for orders with the target type OrderTargetType.WallSection
|
||||
/// </summary>
|
||||
private Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, Structure wall, int? sectionIndex, Character orderGiver = null)
|
||||
: this(prefab, option, manualPriority, orderType, aiObjective, targetEntity: wall, null, orderGiver: orderGiver)
|
||||
{
|
||||
WallSectionIndex = sectionIndex;
|
||||
@@ -633,7 +677,7 @@ namespace Barotrauma
|
||||
|
||||
public Order WithTargetEntity(Entity entity)
|
||||
{
|
||||
return new Order(this, targetEntity: entity);
|
||||
return new Order(this, targetEntity: entity, targetType: OrderTargetType.Entity);
|
||||
}
|
||||
|
||||
public Order WithTargetSpatialEntity(ISpatialEntity spatialEntity)
|
||||
@@ -673,7 +717,7 @@ namespace Barotrauma
|
||||
|
||||
public Order WithTargetPosition(OrderTarget targetPosition)
|
||||
{
|
||||
return new Order(this, targetPosition: targetPosition);
|
||||
return new Order(this, targetPosition: targetPosition, targetType: OrderTargetType.Position);
|
||||
}
|
||||
|
||||
public Order Clone()
|
||||
|
||||
@@ -313,8 +313,7 @@ namespace Barotrauma
|
||||
ShipCommandLog("Dismissing " + shipIssueWorker + " for character " + shipIssueWorker.OrderedCharacter);
|
||||
#endif
|
||||
var order = new Order(OrderPrefab.Dismissal, null).WithManualPriority(3).WithOrderGiver(character);
|
||||
//character.Speak(orderPrefab.GetChatMessage(shipIssueWorker.OrderedCharacter.Name, "", givingOrderToSelf: false));
|
||||
shipIssueWorker.OrderedCharacter.SetOrder(order);
|
||||
shipIssueWorker.OrderedCharacter.SetOrder(order, isNewOrder: true);
|
||||
shipIssueWorker.RemoveOrder();
|
||||
break;
|
||||
}
|
||||
@@ -368,7 +367,7 @@ namespace Barotrauma
|
||||
ShipGlobalIssueFixLeaks shipGlobalIssueFixLeaks = new ShipGlobalIssueFixLeaks(this);
|
||||
for (int i = 0; i < crewSizeModifier; i++)
|
||||
{
|
||||
var order = new Order(OrderPrefab.Prefabs["fixleaks"], null);
|
||||
var order = OrderPrefab.Prefabs["fixleaks"].CreateInstance(OrderPrefab.OrderTargetType.Entity);
|
||||
ShipIssueWorkers.Add(new ShipIssueWorkerFixLeaks(this, order, shipGlobalIssueFixLeaks));
|
||||
}
|
||||
shipGlobalIssues.Add(shipGlobalIssueFixLeaks);
|
||||
@@ -376,7 +375,7 @@ namespace Barotrauma
|
||||
ShipGlobalIssueRepairSystems shipGlobalIssueRepairSystems = new ShipGlobalIssueRepairSystems(this);
|
||||
for (int i = 0; i < crewSizeModifier; i++)
|
||||
{
|
||||
var order = new Order(OrderPrefab.Prefabs["repairsystems"], null);
|
||||
var order = OrderPrefab.Prefabs["repairsystems"].CreateInstance(OrderPrefab.OrderTargetType.Entity);
|
||||
ShipIssueWorkers.Add(new ShipIssueWorkerRepairSystems(this, order, shipGlobalIssueRepairSystems));
|
||||
}
|
||||
shipGlobalIssues.Add(shipGlobalIssueRepairSystems);
|
||||
|
||||
@@ -442,7 +442,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
public void ServerWrite(IWriteMessage msg, Client client, object[] extraData = null)
|
||||
public void ServerEventWrite(IWriteMessage msg, Client client, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(IsAlive);
|
||||
}
|
||||
|
||||
+17
-12
@@ -1017,24 +1017,29 @@ namespace Barotrauma
|
||||
{
|
||||
if (l.IsSevered) { continue; }
|
||||
|
||||
float rotation = l.body.Rotation;
|
||||
if (l.DoesFlip)
|
||||
{
|
||||
if (RagdollParams.IsSpritesheetOrientationHorizontal)
|
||||
{
|
||||
//horizontally oriented sprites can be mirrored by rotating 180 deg and inverting the angle
|
||||
rotation = -(l.body.Rotation + MathHelper.Pi);
|
||||
}
|
||||
else
|
||||
{
|
||||
//vertically oriented limbs can be mirrored by inverting the angle (neutral angle is straight upwards)
|
||||
rotation = -l.body.Rotation;
|
||||
}
|
||||
}
|
||||
|
||||
TrySetLimbPosition(l,
|
||||
centerOfMass,
|
||||
new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y),
|
||||
rotation,
|
||||
lerp);
|
||||
|
||||
l.body.PositionSmoothingFactor = 0.8f;
|
||||
|
||||
if (!l.DoesFlip) { continue; }
|
||||
if (RagdollParams.IsSpritesheetOrientationHorizontal)
|
||||
{
|
||||
//horizontally oriented sprites can be mirrored by rotating 180 deg and inverting the angle
|
||||
l.body.SetTransform(l.SimPosition, -(l.body.Rotation + MathHelper.Pi));
|
||||
}
|
||||
else
|
||||
{
|
||||
//vertically oriented limbs can be mirrored by inverting the angle (neutral angle is straight upwards)
|
||||
l.body.SetTransform(l.SimPosition, -l.body.Rotation);
|
||||
}
|
||||
|
||||
}
|
||||
if (character.SelectedCharacter != null && CanDrag(character.SelectedCharacter))
|
||||
{
|
||||
|
||||
+2
-4
@@ -1846,11 +1846,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float angle = flipAngle ? -limb.body.Rotation : limb.body.Rotation;
|
||||
if (wrapAngle) angle = MathUtils.WrapAnglePi(angle);
|
||||
if (wrapAngle) { angle = MathUtils.WrapAnglePi(angle); }
|
||||
|
||||
TrySetLimbPosition(limb, Collider.SimPosition, position);
|
||||
|
||||
limb.body.SetTransform(limb.body.SimPosition, angle);
|
||||
TrySetLimbPosition(limb, Collider.SimPosition, position, angle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -803,9 +803,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SeverLimbJointProjSpecific(limbJoint, playSound: true);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.Status });
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.StatusEventData());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1693,7 +1693,7 @@ namespace Barotrauma
|
||||
if (limb.IsSevered) { continue; }
|
||||
//check visibility from the new position of the collider to the new position of this limb
|
||||
Vector2 movePos = limb.SimPosition + limbMoveAmount;
|
||||
TrySetLimbPosition(limb, simPosition, movePos, lerp, ignorePlatforms);
|
||||
TrySetLimbPosition(limb, simPosition, movePos, limb.Rotation, lerp, ignorePlatforms);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1708,7 +1708,7 @@ namespace Barotrauma
|
||||
IsHanging = true;
|
||||
}
|
||||
|
||||
protected void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true)
|
||||
protected void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, float rotation, bool lerp = false, bool ignorePlatforms = true)
|
||||
{
|
||||
Vector2 movePos = simPosition;
|
||||
|
||||
@@ -1730,11 +1730,12 @@ namespace Barotrauma
|
||||
if (lerp)
|
||||
{
|
||||
limb.body.TargetPosition = movePos;
|
||||
limb.body.MoveToTargetPosition(true);
|
||||
limb.body.TargetRotation = rotation;
|
||||
limb.body.MoveToTargetPosition(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
limb.body.SetTransform(movePos, limb.Rotation);
|
||||
limb.body.SetTransform(movePos, rotation);
|
||||
limb.PullJointWorldAnchorB = limb.PullJointWorldAnchorA;
|
||||
limb.PullJointEnabled = false;
|
||||
}
|
||||
|
||||
@@ -487,6 +487,10 @@ namespace Barotrauma
|
||||
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
|
||||
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity, worldPosition);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Parent))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker);
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
@@ -551,6 +555,10 @@ namespace Barotrauma
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Parent))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb.character);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
FriendlyNPC = 3
|
||||
}
|
||||
|
||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerSerializable
|
||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
||||
{
|
||||
public readonly static List<Character> CharacterList = new List<Character>();
|
||||
|
||||
@@ -130,6 +130,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Wallet wallet = new Wallet();
|
||||
|
||||
public Wallet Wallet
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfAccessingWalletsInSingleplayer();
|
||||
return wallet;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfAccessingWalletsInSingleplayer();
|
||||
wallet = value;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly HashSet<LatchOntoAI> Latchers = new HashSet<LatchOntoAI>();
|
||||
public readonly HashSet<Projectile> AttachedProjectiles = new HashSet<Projectile>();
|
||||
|
||||
@@ -137,6 +153,17 @@ namespace Barotrauma
|
||||
protected ActiveTeamChange currentTeamChange;
|
||||
const string OriginalTeamIdentifier = "original";
|
||||
|
||||
public static void ThrowIfAccessingWalletsInSingleplayer()
|
||||
{
|
||||
#if CLIENT && DEBUG
|
||||
if (Screen.Selected is TestScreen) { return; }
|
||||
#endif
|
||||
if (GameMain.NetworkMember is null || GameMain.IsSingleplayer)
|
||||
{
|
||||
throw new InvalidOperationException($"Tried to access crew wallets in singleplayer. Use {nameof(CampaignMode)}.{nameof(CampaignMode.Bank)} or {nameof(CampaignMode)}.{nameof(CampaignMode.GetWallet)} instead.");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOriginalTeam(CharacterTeamType newTeam)
|
||||
{
|
||||
TryRemoveTeamChange(OriginalTeamIdentifier);
|
||||
@@ -158,12 +185,11 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
// clear up any duties the character might have had from its old team (autonomous objectives are automatically recreated)
|
||||
var order = new Order(OrderPrefab.Dismissal, Identifier.Empty,
|
||||
manualPriority: 3, orderType: Order.OrderType.Current, aiObjective: null, target: null, orderGiver: this);
|
||||
SetOrder(order, speak: false);
|
||||
var order = OrderPrefab.Dismissal.CreateInstance(OrderPrefab.OrderTargetType.Entity, orderGiver: this).WithManualPriority(CharacterInfo.HighestManualOrderPriority);
|
||||
SetOrder(order, isNewOrder: true, speak: false);
|
||||
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.TeamChange });
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new TeamChangeEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -225,9 +251,8 @@ namespace Barotrauma
|
||||
|
||||
if (bestTeamChange.AggressiveBehavior) // this seemed like the least disruptive way to induce aggressive behavior
|
||||
{
|
||||
var order = new Order(OrderPrefab.Prefabs["fightintruders"], Identifier.Empty,
|
||||
manualPriority: 3, orderType: Order.OrderType.Current, aiObjective: null, target: null, orderGiver: this);
|
||||
SetOrder(order, speak: false);
|
||||
var order = OrderPrefab.Prefabs["fightintruders"].CreateInstance(OrderPrefab.OrderTargetType.Entity, orderGiver: this).WithManualPriority(CharacterInfo.HighestManualOrderPriority);
|
||||
SetOrder(order, isNewOrder: true, speak: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1023,7 +1048,7 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && Spawner != null && createNetworkEvent)
|
||||
{
|
||||
Spawner.CreateNetworkEvent(newCharacter, false);
|
||||
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(newCharacter));
|
||||
}
|
||||
#endif
|
||||
return newCharacter;
|
||||
@@ -1178,6 +1203,10 @@ namespace Barotrauma
|
||||
info = new CharacterInfo(nonHuskedSpeciesName);
|
||||
}
|
||||
}
|
||||
else if (Params.HasInfo && info == null)
|
||||
{
|
||||
info = new CharacterInfo(speciesName);
|
||||
}
|
||||
|
||||
if (IsHumanoid)
|
||||
{
|
||||
@@ -1418,9 +1447,9 @@ namespace Barotrauma
|
||||
{
|
||||
item.AddTag(s);
|
||||
}
|
||||
if (createNetworkEvent && (GameMain.NetworkMember?.IsServer ?? false))
|
||||
if (createNetworkEvent && GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ChangeProperty, item.SerializableProperties[nameof(item.Tags).ToIdentifier()] });
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ChangePropertyEventData(item.SerializableProperties[nameof(item.Tags).ToIdentifier()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3199,7 +3228,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <param name="force">Force an order to be set for the character, bypassing hearing checks</param>
|
||||
public void SetOrder(Order order, bool speak = true, bool force = false)
|
||||
public void SetOrder(Order order, bool isNewOrder, bool speak = true, bool force = false)
|
||||
{
|
||||
var orderGiver = order?.OrderGiver;
|
||||
//set the character order only if the character is close enough to hear the message
|
||||
@@ -3226,7 +3255,7 @@ namespace Barotrauma
|
||||
if (currentOrder.Identifier != order.Identifier) { continue; }
|
||||
if (currentOrder.TargetEntity != order.TargetEntity) { continue; }
|
||||
if (!currentOrder.AutoDismiss) { continue; }
|
||||
character.SetOrder(currentOrder.GetDismissal(), speak: speak, force: force);
|
||||
character.SetOrder(currentOrder.GetDismissal(), isNewOrder, speak: speak, force: force);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -3243,7 +3272,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (orderToReplace is { AutoDismiss: true })
|
||||
{
|
||||
SetOrder(orderToReplace.GetDismissal(), speak: speak, force: force);
|
||||
SetOrder(orderToReplace.GetDismissal(), isNewOrder, speak: speak, force: force);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3251,10 +3280,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// Prevent adding duplicate orders
|
||||
bool wasDuplicate = RemoveDuplicateOrders(order);
|
||||
RemoveDuplicateOrders(order);
|
||||
AddCurrentOrder(order);
|
||||
|
||||
if (orderGiver != null && order.Identifier != "dismissed" && !wasDuplicate)
|
||||
if (orderGiver != null && order.Identifier != "dismissed" && isNewOrder)
|
||||
{
|
||||
var abilityOrderedCharacter = new AbilityOrderedCharacter(this);
|
||||
orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter);
|
||||
@@ -3976,14 +4005,14 @@ namespace Barotrauma
|
||||
HealthUpdateInterval = 0.0f;
|
||||
|
||||
//clients aren't allowed to kill characters unless they receive a network message
|
||||
if (!isNetworkMessage && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
if (!isNetworkMessage && GameMain.NetworkMember is { IsClient: true })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new StatusEventData());
|
||||
}
|
||||
|
||||
isDead = true;
|
||||
@@ -4072,15 +4101,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
if (GameMain.GameSession.Campaign != null && TeamID == CharacterTeamType.Team1 && !IsAssistant)
|
||||
{
|
||||
GameMain.GameSession.Campaign.CrewHasDied = true;
|
||||
}
|
||||
|
||||
GameMain.GameSession.KillCharacter(this);
|
||||
}
|
||||
GameMain.GameSession?.KillCharacter(this);
|
||||
}
|
||||
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log);
|
||||
|
||||
@@ -4245,7 +4266,7 @@ namespace Barotrauma
|
||||
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
|
||||
GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status });
|
||||
GameMain.NetworkMember.CreateEntityEvent(newItem, new StatusEventData());
|
||||
}
|
||||
#if SERVER
|
||||
newItem.GetComponent<Terminal>()?.SyncHistory();
|
||||
@@ -4334,7 +4355,7 @@ namespace Barotrauma
|
||||
hull?.Submarine ?? Submarine);
|
||||
extraDuffelBags.Add(newDuffelBag);
|
||||
#if SERVER
|
||||
Spawner.CreateNetworkEvent(newDuffelBag, false);
|
||||
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(newDuffelBag));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -4547,7 +4568,7 @@ namespace Barotrauma
|
||||
info.UnlockedTalents.Add(talentPrefab.Identifier);
|
||||
if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; }
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new UpdateTalentsEventData());
|
||||
#endif
|
||||
CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this);
|
||||
characterTalents.Add(characterTalent);
|
||||
@@ -4626,23 +4647,44 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void GiveMoney(int amount)
|
||||
{
|
||||
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; }
|
||||
if (!(GameMain.GameSession?.Campaign is { } campaign)) { return; }
|
||||
if (amount <= 0) { return; }
|
||||
|
||||
int prevAmount = campaign.Money;
|
||||
campaign.Money += amount;
|
||||
OnMoneyChanged(prevAmount, campaign.Money);
|
||||
Wallet wallet;
|
||||
#if SERVER
|
||||
if (!(campaign is MultiPlayerCampaign mpCampaign)) { throw new InvalidOperationException("Campaign on a server is not a multiplayer campaign"); }
|
||||
Client targetClient = null;
|
||||
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (client.Character == this)
|
||||
{
|
||||
targetClient = client;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wallet = targetClient is null ? mpCampaign.Bank : mpCampaign.GetWallet(targetClient);
|
||||
#else
|
||||
wallet = campaign.Wallet;
|
||||
#endif
|
||||
|
||||
int prevAmount = wallet.Balance;
|
||||
wallet.Give(amount);
|
||||
OnMoneyChanged(prevAmount, wallet.Balance);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public void SetMoney(int amount)
|
||||
{
|
||||
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; }
|
||||
if (amount == campaign.Money) { return; }
|
||||
if (!(GameMain.GameSession?.Campaign is { } campaign)) { return; }
|
||||
if (amount == campaign.Wallet.Balance) { return; }
|
||||
|
||||
int prevAmount = campaign.Money;
|
||||
campaign.Money = amount;
|
||||
OnMoneyChanged(prevAmount, campaign.Money);
|
||||
int prevAmount = campaign.Wallet.Balance;
|
||||
campaign.Wallet.Balance = amount;
|
||||
OnMoneyChanged(prevAmount, campaign.Wallet.Balance);
|
||||
}
|
||||
#endif
|
||||
|
||||
partial void OnMoneyChanged(int prevAmount, int newAmount);
|
||||
partial void OnTalentGiven(TalentPrefab talentPrefab);
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Character
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
InventoryState = 0,
|
||||
Control = 1,
|
||||
Status = 2,
|
||||
Treatment = 3,
|
||||
SetAttackTarget = 4,
|
||||
ExecuteAttack = 5,
|
||||
AssignCampaignInteraction = 6,
|
||||
ObjectiveManagerState = 7,
|
||||
TeamChange = 8,
|
||||
AddToCrew = 9,
|
||||
UpdateExperience = 10,
|
||||
UpdateTalents = 11,
|
||||
UpdateSkills = 12,
|
||||
UpdateMoney = 13,
|
||||
UpdatePermanentStats = 14,
|
||||
|
||||
MinValue = 0,
|
||||
MaxValue = 14
|
||||
}
|
||||
|
||||
private interface IEventData : NetEntityEvent.IData
|
||||
{
|
||||
public EventType EventType { get; }
|
||||
}
|
||||
|
||||
public struct InventoryStateEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.InventoryState;
|
||||
}
|
||||
|
||||
public struct ControlEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Control;
|
||||
public readonly Client Owner;
|
||||
|
||||
public ControlEventData(Client owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
}
|
||||
|
||||
public struct TreatmentEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Treatment;
|
||||
}
|
||||
|
||||
private interface IAttackEventData : IEventData
|
||||
{
|
||||
public Limb AttackLimb { get; }
|
||||
public IDamageable TargetEntity { get; }
|
||||
public Limb TargetLimb { get; }
|
||||
public Vector2 TargetSimPos { get; }
|
||||
}
|
||||
|
||||
public struct SetAttackTargetEventData : IAttackEventData
|
||||
{
|
||||
public EventType EventType => EventType.SetAttackTarget;
|
||||
public Limb AttackLimb { get; }
|
||||
public IDamageable TargetEntity { get; }
|
||||
public Limb TargetLimb { get; }
|
||||
public Vector2 TargetSimPos { get; }
|
||||
|
||||
public SetAttackTargetEventData(Limb attackLimb, IDamageable targetEntity, Limb targetLimb, Vector2 targetSimPos)
|
||||
{
|
||||
AttackLimb = attackLimb;
|
||||
TargetEntity = targetEntity;
|
||||
TargetLimb = targetLimb;
|
||||
TargetSimPos = targetSimPos;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ExecuteAttackEventData : IAttackEventData
|
||||
{
|
||||
public EventType EventType => EventType.ExecuteAttack;
|
||||
public Limb AttackLimb { get; }
|
||||
public IDamageable TargetEntity { get; }
|
||||
public Limb TargetLimb { get; }
|
||||
public Vector2 TargetSimPos { get; }
|
||||
|
||||
public ExecuteAttackEventData(Limb attackLimb, IDamageable targetEntity, Limb targetLimb, Vector2 targetSimPos)
|
||||
{
|
||||
AttackLimb = attackLimb;
|
||||
TargetEntity = targetEntity;
|
||||
TargetLimb = targetLimb;
|
||||
TargetSimPos = targetSimPos;
|
||||
}
|
||||
}
|
||||
|
||||
public struct AssignCampaignInteractionEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.AssignCampaignInteraction;
|
||||
}
|
||||
|
||||
public struct ObjectiveManagerStateEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.ObjectiveManagerState;
|
||||
public readonly AIObjectiveManager.ObjectiveType ObjectiveType;
|
||||
|
||||
public ObjectiveManagerStateEventData(AIObjectiveManager.ObjectiveType objectiveType)
|
||||
{
|
||||
ObjectiveType = objectiveType;
|
||||
}
|
||||
}
|
||||
|
||||
private struct TeamChangeEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.TeamChange;
|
||||
}
|
||||
|
||||
public struct AddToCrewEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.AddToCrew;
|
||||
public readonly CharacterTeamType TeamType;
|
||||
public readonly ImmutableArray<Item> InventoryItems;
|
||||
|
||||
public AddToCrewEventData(CharacterTeamType teamType, IEnumerable<Item> inventoryItems)
|
||||
{
|
||||
TeamType = teamType;
|
||||
InventoryItems = inventoryItems.ToImmutableArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct UpdateExperienceEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.UpdateExperience;
|
||||
}
|
||||
|
||||
public struct UpdateTalentsEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.UpdateTalents;
|
||||
}
|
||||
|
||||
public struct UpdateSkillsEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.UpdateSkills;
|
||||
}
|
||||
|
||||
private struct UpdateMoneyEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.UpdateMoney;
|
||||
}
|
||||
|
||||
public struct UpdatePermanentStatsEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.UpdatePermanentStats;
|
||||
public readonly StatTypes StatType;
|
||||
|
||||
public UpdatePermanentStatsEventData(StatTypes statType)
|
||||
{
|
||||
StatType = statType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +299,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (handleBuff)
|
||||
{
|
||||
Character.CharacterHealth.ApplyAffliction(Character.AnimController.GetLimb(LimbType.Head), AfflictionPrefab.List.FirstOrDefault(a => a.Identifier == "disguised").Instantiate(100f));
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
Character.CharacterHealth.ApplyAffliction(head, AfflictionPrefab.List.FirstOrDefault(a => a.Identifier == "disguised").Instantiate(100f));
|
||||
}
|
||||
}
|
||||
|
||||
idCard ??= Character.Inventory?.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent<IdCard>();
|
||||
@@ -319,7 +323,11 @@ namespace Barotrauma
|
||||
|
||||
if (handleBuff)
|
||||
{
|
||||
Character.CharacterHealth.ReduceAfflictionOnLimb(Character.AnimController.GetLimb(LimbType.Head), "disguised".ToIdentifier(), 100f);
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
Character.CharacterHealth.ReduceAfflictionOnLimb(head, "disguised".ToIdentifier(), 100f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,15 +580,15 @@ namespace Barotrauma
|
||||
|
||||
private void CheckColors()
|
||||
{
|
||||
if (IsColorValid(Head.HairColor))
|
||||
if (!IsColorValid(Head.HairColor))
|
||||
{
|
||||
Head.HairColor = SelectRandomColor(HairColors, Rand.RandSync.Unsynced);
|
||||
}
|
||||
if (IsColorValid(Head.FacialHairColor))
|
||||
if (!IsColorValid(Head.FacialHairColor))
|
||||
{
|
||||
Head.FacialHairColor = SelectRandomColor(FacialHairColors, Rand.RandSync.Unsynced);
|
||||
}
|
||||
if (IsColorValid(Head.SkinColor))
|
||||
if (!IsColorValid(Head.SkinColor))
|
||||
{
|
||||
Head.SkinColor = SelectRandomColor(SkinColors, Rand.RandSync.Unsynced);
|
||||
}
|
||||
@@ -736,7 +744,7 @@ namespace Barotrauma
|
||||
|
||||
private int GetIdentifier(string name)
|
||||
{
|
||||
int id = ToolBox.StringToInt(name + string.Join("", Head.Preset.TagSet));
|
||||
int id = ToolBox.StringToInt(name + string.Join("", Head.Preset.TagSet.OrderBy(s => s)));
|
||||
id ^= Head.HairIndex << 12;
|
||||
id ^= Head.BeardIndex << 18;
|
||||
id ^= Head.MoustacheIndex << 24;
|
||||
@@ -822,12 +830,12 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var limbElement in Ragdoll.MainElement.Elements())
|
||||
{
|
||||
if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (!limbElement.GetAttributeString("type", string.Empty).Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
ContentXElement spriteElement = limbElement.GetChildElement("sprite");
|
||||
if (spriteElement == null) { continue; }
|
||||
|
||||
string spritePath = spriteElement.Attribute("texture").Value;
|
||||
string spritePath = spriteElement.GetAttributeContentPath("texture")?.Value;
|
||||
if (string.IsNullOrEmpty(spritePath)) { continue; }
|
||||
|
||||
spritePath = ReplaceVars(spritePath);
|
||||
@@ -1298,7 +1306,7 @@ namespace Barotrauma
|
||||
var orders = LoadOrders(orderData);
|
||||
foreach (var order in orders)
|
||||
{
|
||||
character.SetOrder(order, speak: false, force: true);
|
||||
character.SetOrder(order, isNewOrder: true, speak: false, force: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+62
-4
@@ -41,9 +41,11 @@ namespace Barotrauma
|
||||
if (previousValue > 0.0f && value <= 0.0f)
|
||||
{
|
||||
DeactivateHusk();
|
||||
highestStrength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
private float highestStrength;
|
||||
|
||||
public InfectionState State
|
||||
{
|
||||
@@ -75,6 +77,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (HuskPrefab == null) { return; }
|
||||
base.Update(characterHealth, targetLimb, deltaTime);
|
||||
highestStrength = Math.Max(_strength, highestStrength);
|
||||
character = characterHealth.Character;
|
||||
if (character == null) { return; }
|
||||
|
||||
@@ -98,7 +101,7 @@ namespace Barotrauma
|
||||
DeactivateHusk();
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
|
||||
{
|
||||
character.SpeechImpediment = 100;
|
||||
character.SpeechImpediment = 30;
|
||||
}
|
||||
State = InfectionState.Transition;
|
||||
}
|
||||
@@ -108,6 +111,10 @@ namespace Barotrauma
|
||||
{
|
||||
character.SetStun(Rand.Range(2f, 3f));
|
||||
}
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
|
||||
{
|
||||
character.SpeechImpediment = 100;
|
||||
}
|
||||
State = InfectionState.Active;
|
||||
ActivateHusk();
|
||||
}
|
||||
@@ -120,7 +127,57 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateMessages();
|
||||
private InfectionState? prevDisplayedMessage;
|
||||
private void UpdateMessages()
|
||||
{
|
||||
if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; }
|
||||
if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; }
|
||||
if (highestStrength > Strength) { return; }
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case InfectionState.Dormant:
|
||||
if (Strength < DormantThreshold * 0.5f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (character == Character.Controlled)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage(TextManager.Get("HuskDormant"), GUIStyle.Red);
|
||||
#endif
|
||||
}
|
||||
else if (character.IsBot)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoghuskdormant").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskdormant".ToIdentifier());
|
||||
}
|
||||
break;
|
||||
case InfectionState.Transition:
|
||||
if (character == Character.Controlled)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUIStyle.Red);
|
||||
#endif
|
||||
}
|
||||
else if (character.IsBot)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskcantspeak".ToIdentifier());
|
||||
}
|
||||
break;
|
||||
case InfectionState.Active:
|
||||
#if CLIENT
|
||||
if (character == Character.Controlled && character.Params.UseHuskAppendage)
|
||||
{
|
||||
GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Attack)), GUIStyle.Red);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case InfectionState.Final:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
prevDisplayedMessage = State;
|
||||
}
|
||||
|
||||
private void ApplyDamage(float deltaTime, bool applyForce)
|
||||
{
|
||||
@@ -209,7 +266,9 @@ namespace Barotrauma
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
|
||||
#endif
|
||||
character.Enabled = false;
|
||||
Entity.Spawner.AddEntityToRemoveQueue(character);
|
||||
UnsubscribeFromDeathEvent();
|
||||
@@ -246,7 +305,6 @@ namespace Barotrauma
|
||||
if (huskPrefab.ControlHusk)
|
||||
{
|
||||
#if SERVER
|
||||
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
|
||||
if (client != null)
|
||||
{
|
||||
GameMain.Server.SetClientCharacter(client, husk);
|
||||
|
||||
@@ -657,6 +657,7 @@ namespace Barotrauma
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; }
|
||||
if (Character.Params.Health.PoisonImmunity && newAffliction.Prefab.AfflictionType == "poison") { return; }
|
||||
if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab)
|
||||
{
|
||||
if (huskPrefab.TargetSpecies.None(s => s == Character.SpeciesName))
|
||||
@@ -731,48 +732,47 @@ namespace Barotrauma
|
||||
|
||||
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
|
||||
|
||||
if (Character.GodMode) { return; }
|
||||
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToUpdate.Clear();
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
if (!Character.GodMode)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
if (affliction.Strength <= 0.0f)
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToUpdate.Clear();
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
SteamAchievementManager.OnAfflictionRemoved(affliction, Character);
|
||||
if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||
continue;
|
||||
var affliction = kvp.Key;
|
||||
if (affliction.Strength <= 0.0f)
|
||||
{
|
||||
SteamAchievementManager.OnAfflictionRemoved(affliction, Character);
|
||||
if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||
continue;
|
||||
}
|
||||
afflictionsToUpdate.Add(kvp);
|
||||
}
|
||||
afflictionsToUpdate.Add(kvp);
|
||||
}
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictionsToUpdate)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
Limb targetLimb = null;
|
||||
if (kvp.Value != null)
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictionsToUpdate)
|
||||
{
|
||||
int healthIndex = limbHealths.IndexOf(kvp.Value);
|
||||
targetLimb =
|
||||
Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == healthIndex) ??
|
||||
Character.AnimController.MainLimb;
|
||||
var affliction = kvp.Key;
|
||||
Limb targetLimb = null;
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
int healthIndex = limbHealths.IndexOf(kvp.Value);
|
||||
targetLimb =
|
||||
Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == healthIndex) ??
|
||||
Character.AnimController.MainLimb;
|
||||
}
|
||||
affliction.Update(this, targetLimb, deltaTime);
|
||||
affliction.DamagePerSecondTimer += deltaTime;
|
||||
if (affliction is AfflictionBleeding bleeding)
|
||||
{
|
||||
UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime);
|
||||
}
|
||||
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
|
||||
}
|
||||
affliction.Update(this, targetLimb, deltaTime);
|
||||
affliction.DamagePerSecondTimer += deltaTime;
|
||||
if (affliction is AfflictionBleeding bleeding)
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime);
|
||||
}
|
||||
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
|
||||
}
|
||||
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
afflictions.Remove(affliction);
|
||||
afflictions.Remove(affliction);
|
||||
}
|
||||
}
|
||||
|
||||
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.MovementSpeed));
|
||||
|
||||
if (Character.InWater)
|
||||
{
|
||||
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed));
|
||||
@@ -782,13 +782,16 @@ namespace Barotrauma
|
||||
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.WalkingSpeed));
|
||||
}
|
||||
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
CalculateVitality();
|
||||
|
||||
if (Vitality <= MinVitality)
|
||||
if (!Character.GodMode)
|
||||
{
|
||||
Kill();
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
CalculateVitality();
|
||||
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ namespace Barotrauma
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
}
|
||||
|
||||
Entity.Spawner.CreateNetworkEvent(item, false);
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
#endif
|
||||
if (itemElement.GetAttributeBool("equip", false))
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace Barotrauma
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
}
|
||||
|
||||
Entity.Spawner.CreateNetworkEvent(item, false);
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1011,15 +1011,9 @@ namespace Barotrauma
|
||||
ExecuteAttack(damageTarget, targetLimb, out attackResult);
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ExecuteAttack,
|
||||
this,
|
||||
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
|
||||
damageTarget is Character && targetLimb != null ? Array.IndexOf(((Character)damageTarget).AnimController.Limbs, targetLimb) : 0,
|
||||
attackSimPos.X,
|
||||
attackSimPos.Y
|
||||
});
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.ExecuteAttackEventData(
|
||||
attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
|
||||
targetSimPos: attackSimPos));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1055,7 +1049,10 @@ namespace Barotrauma
|
||||
if (!attack.IsRunning)
|
||||
{
|
||||
// Set the main collider where the body lands after the attack
|
||||
character.AnimController.Collider.SetTransform(character.AnimController.MainLimb.body.SimPosition, rotation: character.AnimController.Collider.Rotation);
|
||||
if (Vector2.DistanceSquared(character.AnimController.Collider.SimPosition, character.AnimController.MainLimb.body.SimPosition) > 0.1f * 0.1f)
|
||||
{
|
||||
character.AnimController.Collider.SetTransform(character.AnimController.MainLimb.body.SimPosition, rotation: character.AnimController.Collider.Rotation);
|
||||
}
|
||||
}
|
||||
return wasHit;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace Barotrauma
|
||||
Identifier variantOf = MainElement.VariantOf();
|
||||
if (!variantOf.IsEmpty)
|
||||
{
|
||||
VariantFile = doc;
|
||||
VariantFile = new XDocument(doc);
|
||||
#warning TODO: determine that CreateVariantXML is equipped to do this
|
||||
XElement newRoot = CreateVariantXml(MainElement, CharacterPrefab.FindBySpeciesName(variantOf).ConfigElement);
|
||||
var oldElement = MainElement;
|
||||
@@ -477,12 +477,15 @@ namespace Barotrauma
|
||||
[Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
public float ConstantHealthRegeneration { get; private set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
[Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
|
||||
public float HealthRegenerationWhenEating { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool StunImmunity { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool PoisonImmunity { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Can afflictions affect the face/body tint of the character."), Editable]
|
||||
public bool ApplyAfflictionColors { get; private set; }
|
||||
|
||||
|
||||
@@ -956,7 +956,7 @@ namespace Barotrauma
|
||||
Deformations = new Dictionary<SpriteDeformationParams, XElement>();
|
||||
foreach (var deformationElement in element.GetChildElements("spritedeformation"))
|
||||
{
|
||||
string typeName = deformationElement.GetAttributeString("typename", null) ?? deformationElement.GetAttributeString("type", "");
|
||||
string typeName = deformationElement.GetAttributeString("type", null) ?? deformationElement.GetAttributeString("typename", string.Empty);
|
||||
SpriteDeformationParams deformation = null;
|
||||
switch (typeName.ToLowerInvariant())
|
||||
{
|
||||
@@ -982,7 +982,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (deformation != null)
|
||||
{
|
||||
deformation.TypeName = typeName;
|
||||
deformation.Type = typeName;
|
||||
}
|
||||
Deformations.Add(deformation, deformationElement);
|
||||
}
|
||||
|
||||
+15
-7
@@ -1,20 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionNoCrewDied : AbilityConditionDataless
|
||||
{
|
||||
public bool assistantsDontCount;
|
||||
|
||||
public AbilityConditionNoCrewDied(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
assistantsDontCount = conditionElement.GetAttributeBool(nameof(assistantsDontCount), true);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
|
||||
if (GameMain.GameSession == null) { return false; }
|
||||
|
||||
foreach (Character character in GameMain.GameSession.Casualties)
|
||||
{
|
||||
return !campaign.CrewHasDied;
|
||||
if (assistantsDontCount && character.Info?.Job?.Prefab.Identifier == "assistant")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (character.CauseOfDeath != null && character.CauseOfDeath.Type != CauseOfDeathType.Disconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+6
-2
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
@@ -63,7 +64,7 @@ namespace Barotrauma
|
||||
|
||||
public static Result<ContentFile, string> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
||||
{
|
||||
Result<ContentFile, string> fail(string error)
|
||||
static Result<ContentFile, string> fail(string error)
|
||||
=> Result<ContentFile, string>.Failure(error);
|
||||
|
||||
Identifier elemName = element.NameAsIdentifier();
|
||||
@@ -73,13 +74,16 @@ namespace Barotrauma
|
||||
{
|
||||
return fail($"Invalid content type \"{elemName}\"");
|
||||
}
|
||||
|
||||
if (filePath is null)
|
||||
{
|
||||
return fail($"No content path defined for file of type \"{elemName}\"");
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath.FullPath))
|
||||
{
|
||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": file not found.");
|
||||
}
|
||||
var file = type.CreateInstance(contentPackage, filePath);
|
||||
return file is null
|
||||
? throw new Exception($"Content type is not implemented correctly")
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
|
||||
+5
-3
@@ -54,8 +54,10 @@ namespace Barotrauma
|
||||
|
||||
public int Index => ContentPackageManager.EnabledPackages.IndexOf(this);
|
||||
|
||||
#warning TODO: remove this, unless we truly believe that determining "multiplayer-incompatible content" is something we should do
|
||||
public readonly bool HasMultiplayerIncompatibleContent;
|
||||
/// <summary>
|
||||
/// Does the content package include some content that needs to match between all players in multiplayer.
|
||||
/// </summary>
|
||||
public readonly bool HasMultiplayerSyncedContent;
|
||||
|
||||
protected ContentPackage(XDocument doc, string path)
|
||||
{
|
||||
@@ -93,7 +95,7 @@ namespace Barotrauma
|
||||
.Select(f => f.Error)
|
||||
.ToImmutableArray();
|
||||
|
||||
HasMultiplayerIncompatibleContent = Files.Any(f => !f.NotSyncedInMultiplayer);
|
||||
HasMultiplayerSyncedContent = Files.Any(f => !f.NotSyncedInMultiplayer);
|
||||
|
||||
Hash = CalculateHash();
|
||||
var expectedHash = rootElement.GetAttributeString("expectedhash", "");
|
||||
|
||||
@@ -392,6 +392,10 @@ namespace Barotrauma
|
||||
public static void LoadVanillaFileList()
|
||||
{
|
||||
VanillaCorePackage = new CorePackage(XDocument.Load(VanillaFileList), VanillaFileList);
|
||||
foreach (string error in VanillaCorePackage.Errors)
|
||||
{
|
||||
DebugConsole.ThrowError(error);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<LoadProgress> Init()
|
||||
|
||||
@@ -101,23 +101,27 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private static bool StringEquality(string? a, string? b)
|
||||
=> (a.IsNullOrEmpty() && b.IsNullOrEmpty()) ||
|
||||
string.Equals(Path.GetFullPath(a.CleanUpPathCrossPlatform(false) ?? ""),
|
||||
Path.GetFullPath(b.CleanUpPathCrossPlatform(false) ?? ""),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
{
|
||||
if (a.IsNullOrEmpty() || b.IsNullOrEmpty())
|
||||
{
|
||||
return a.IsNullOrEmpty() == b.IsNullOrEmpty();
|
||||
}
|
||||
return string.Equals(Path.GetFullPath(a.CleanUpPathCrossPlatform(false) ?? ""),
|
||||
Path.GetFullPath(b.CleanUpPathCrossPlatform(false) ?? ""), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool operator==(ContentPath a, ContentPath b)
|
||||
=> StringEquality(a.Value, b.Value);
|
||||
=> StringEquality(a?.Value, b?.Value);
|
||||
|
||||
public static bool operator!=(ContentPath a, ContentPath b) => !(a == b);
|
||||
|
||||
public static bool operator==(ContentPath a, string? b)
|
||||
=> StringEquality(a.Value, b);
|
||||
=> StringEquality(a?.Value, b);
|
||||
|
||||
public static bool operator!=(ContentPath a, string? b) => !(a == b);
|
||||
|
||||
public static bool operator==(string? a, ContentPath b)
|
||||
=> StringEquality(a, b.Value);
|
||||
=> StringEquality(a, b?.Value);
|
||||
|
||||
public static bool operator!=(string? a, ContentPath b) => !(a == b);
|
||||
|
||||
|
||||
@@ -67,8 +67,13 @@ namespace Barotrauma
|
||||
|
||||
public void Execute(string[] args)
|
||||
{
|
||||
if (OnExecute == null) return;
|
||||
if (!CheatsEnabled && IsCheat)
|
||||
if (OnExecute == null) { return; }
|
||||
|
||||
bool allowCheats = false;
|
||||
#if CLIENT
|
||||
allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is EditorScreen);
|
||||
#endif
|
||||
if (!allowCheats && !CheatsEnabled && IsCheat)
|
||||
{
|
||||
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red);
|
||||
#if USE_STEAM
|
||||
@@ -603,28 +608,27 @@ namespace Barotrauma
|
||||
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type] [use relative strength]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
|
||||
{
|
||||
if (args.Length < 2) { return; }
|
||||
|
||||
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a =>
|
||||
a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
|
||||
a.Identifier == args[0]);
|
||||
string affliction = args[0];
|
||||
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a => a.Identifier == affliction);
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
ThrowError("Affliction \"" + args[0] + "\" not found.");
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a => a.Name.Equals(affliction, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
ThrowError("Affliction \"" + affliction + "\" not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!float.TryParse(args[1], out float afflictionStrength))
|
||||
{
|
||||
ThrowError("\"" + args[1] + "\" is not a valid affliction strength.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool relativeStrength = false;
|
||||
if (args.Length > 4)
|
||||
{
|
||||
bool.TryParse(args[4], out relativeStrength);
|
||||
}
|
||||
|
||||
Character targetCharacter = args.Length <= 2 ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] });
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
@@ -671,7 +675,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -699,7 +703,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -720,7 +724,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -825,7 +829,7 @@ namespace Barotrauma
|
||||
{
|
||||
Character.Controlled?.Info?.Job?.Skills?.Select(skill => skill.Identifier.Value).ToArray() ?? Array.Empty<string>(),
|
||||
new[]{ "max" },
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray(),
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray(),
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -864,7 +868,7 @@ namespace Barotrauma
|
||||
return new string[][]
|
||||
{
|
||||
talentNames.Select(id => id).ToArray(),
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -916,7 +920,7 @@ namespace Barotrauma
|
||||
return new string[][]
|
||||
{
|
||||
availableArgs.ToArray(),
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -951,7 +955,7 @@ namespace Barotrauma
|
||||
return new[]
|
||||
{
|
||||
new string[] { "100" },
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray(),
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray(),
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -1066,7 +1070,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -1413,7 +1417,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -1455,7 +1459,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Where(c => c.IsDead).Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Where(c => c.IsDead).Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -1467,7 +1471,7 @@ namespace Barotrauma
|
||||
return new string[][]
|
||||
{
|
||||
GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(),
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray()
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -1538,14 +1542,14 @@ namespace Barotrauma
|
||||
NewMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", Color.White);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("money", "money [amount]: Gives the specified amount of money to the crew when a campaign is active.", args =>
|
||||
commands.Add(new Command("money", "money [amount] [character]: Gives the specified amount of money to the crew when a campaign is active.", args =>
|
||||
{
|
||||
if (args.Length == 0) { return; }
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
if (int.TryParse(args[0], out int money))
|
||||
{
|
||||
campaign.Money += money;
|
||||
campaign.Bank.Give(money);
|
||||
GameAnalyticsManager.AddMoneyGainedEvent(money, GameAnalyticsManager.MoneySource.Cheat, "console");
|
||||
}
|
||||
else
|
||||
@@ -1553,8 +1557,23 @@ namespace Barotrauma
|
||||
ThrowError($"\"{args[0]}\" is not a valid numeric value.");
|
||||
}
|
||||
}
|
||||
}, isCheat: true, getValidArgs: () => new []
|
||||
{
|
||||
new []{ string.Empty },
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
}));
|
||||
|
||||
commands.Add(new Command("showmoney", "showmoney: Shows the amount of money in everyones wallet.", args =>
|
||||
{
|
||||
if (!(GameMain.GameSession?.GameMode is CampaignMode campaign))
|
||||
{
|
||||
ThrowError("No campaign active!");
|
||||
return;
|
||||
}
|
||||
|
||||
NewMessage($"Bank: {campaign.Bank.Balance}");
|
||||
}, isCheat: true));
|
||||
|
||||
|
||||
commands.Add(new Command("skipeventcooldown", "skipeventcooldown: Skips the currently active event cooldown and triggers pending monster spawns immediately.", args =>
|
||||
{
|
||||
GameMain.GameSession?.EventManager?.SkipEventCooldown();
|
||||
@@ -1968,7 +1987,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] ListCharacterNames() => Character.CharacterList.OrderBy(c => c.IsDead).ThenByDescending(c => c.IsHuman).Select(c => c.Name).Distinct().ToArray();
|
||||
private static string[] ListCharacterNames() => Character.CharacterList.OrderBy(c => c.IsDead).ThenByDescending(c => c.IsHuman).ThenBy(c => c.Name).Select(c => c.Name).Distinct().ToArray();
|
||||
|
||||
private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false, Client allowedRemotePlayer = null)
|
||||
{
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(item, false);
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -7,15 +10,36 @@ namespace Barotrauma
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
public CheckMoneyAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Client matchingClient = null;
|
||||
bool hasTag = !TargetTag.IsEmpty;
|
||||
#if SERVER
|
||||
IEnumerable<Entity> targets = ParentEvent.GetTargets(TargetTag);
|
||||
|
||||
if (hasTag)
|
||||
{
|
||||
foreach (Entity entity in targets)
|
||||
{
|
||||
if (entity is Character && GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == entity) is { } matchingCharacter)
|
||||
{
|
||||
matchingClient = matchingCharacter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
return campaign.Money >= Amount;
|
||||
return !hasTag ? campaign.Bank.CanAfford(Amount) : campaign.GetWallet(matchingClient).CanAfford(Amount);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ namespace Barotrauma
|
||||
speaker.ActiveConversation = null;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
var humanAI = speaker.AIController as HumanAIController;
|
||||
if (humanAI != null && !speaker.IsDead && !speaker.Removed)
|
||||
@@ -259,7 +259,7 @@ namespace Barotrauma
|
||||
speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
TextManager.Get("CampaignInteraction.Talk"));
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -369,7 +369,7 @@ namespace Barotrauma
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -10,6 +14,9 @@ namespace Barotrauma
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
@@ -25,13 +32,44 @@ namespace Barotrauma
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
#if SERVER
|
||||
bool hasTag = !TargetTag.IsEmpty;
|
||||
List<Client> matchingClients = new List<Client>();
|
||||
if (hasTag)
|
||||
{
|
||||
IEnumerable targets = ParentEvent.GetTargets(TargetTag);
|
||||
|
||||
foreach (Entity entity in targets)
|
||||
{
|
||||
if (entity is Character && GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == entity) is { } matchingCharacter)
|
||||
{
|
||||
matchingClients.Add(matchingCharacter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
campaign.Money += Amount;
|
||||
GameAnalyticsManager.AddMoneyGainedEvent(Amount, GameAnalyticsManager.MoneySource.Event, ParentEvent.Prefab.Identifier.Value);
|
||||
#if SERVER
|
||||
(campaign as MultiPlayerCampaign).LastUpdateID++;
|
||||
if (!hasTag)
|
||||
{
|
||||
campaign.Bank.Give(Amount);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Client client in matchingClients)
|
||||
{
|
||||
campaign.GetWallet(client).Give(Amount);
|
||||
}
|
||||
}
|
||||
|
||||
((MultiPlayerCampaign)campaign).LastUpdateID++;
|
||||
#else
|
||||
campaign.Wallet.Give(Amount);
|
||||
#endif
|
||||
GameAnalyticsManager.AddMoneyGainedEvent(Amount, GameAnalyticsManager.MoneySource.Event, ParentEvent.Prefab.Identifier.Value);
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
npc.GiveIdCardTags(subWaypoint, createNetworkEvent: true);
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, TeamTag, npc.Inventory.AllItems.Select(it => it.ID).ToArray() });
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamTag, npc.Inventory.AllItems));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace Barotrauma
|
||||
npc.SetCustomInteract(
|
||||
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
|
||||
TextManager.Get("CampaignInteraction.Talk"));
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
}
|
||||
if (!AllowMultipleTargets) { return; }
|
||||
@@ -194,7 +194,7 @@ namespace Barotrauma
|
||||
npc.SetCustomInteract(null, null);
|
||||
npc.RequireConsciousnessForCustomInteract = true;
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
}
|
||||
else if (npcOrItem.TryGet(out Item item))
|
||||
|
||||
@@ -460,9 +460,12 @@ namespace Barotrauma
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
|
||||
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
CreateEvents(childEventSet, rand);
|
||||
if (!IsValidForLevel(childEventSet, level)) { continue; }
|
||||
if (location != null && !IsValidForLocation(childEventSet, location)) { continue; }
|
||||
CreateEvents(childEventSet, rand);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,10 +477,7 @@ namespace Barotrauma
|
||||
Random rand = random ?? new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
|
||||
var allowedEventSets =
|
||||
eventSets.Where(es =>
|
||||
level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty &&
|
||||
level.LevelData.Type == es.LevelType &&
|
||||
(es.BiomeIdentifier.IsEmpty || es.BiomeIdentifier == level.LevelData.Biome.Identifier));
|
||||
eventSets.Where(set => IsValidForLevel(set, level));
|
||||
|
||||
if (requireCampaignSet.HasValue)
|
||||
{
|
||||
@@ -501,13 +501,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
LocationType locationType = location?.GetLocationType();
|
||||
|
||||
if (location != null)
|
||||
{
|
||||
allowedEventSets = allowedEventSets.Where(set =>
|
||||
set.LocationTypeIdentifiers == null ||
|
||||
set.LocationTypeIdentifiers.Any(identifier => identifier == locationType.Identifier));
|
||||
allowedEventSets = allowedEventSets.Where(set => IsValidForLocation(set, location));
|
||||
}
|
||||
|
||||
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
|
||||
@@ -526,6 +522,20 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsValidForLevel(EventSet eventSet, Level level)
|
||||
{
|
||||
return
|
||||
level.Difficulty >= eventSet.MinLevelDifficulty && level.Difficulty <= eventSet.MaxLevelDifficulty &&
|
||||
level.LevelData.Type == eventSet.LevelType &&
|
||||
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier);
|
||||
}
|
||||
|
||||
private bool IsValidForLocation(EventSet eventSet, Location location)
|
||||
{
|
||||
return eventSet.LocationTypeIdentifiers == null ||
|
||||
eventSet.LocationTypeIdentifiers.Any(identifier => identifier == location.GetLocationType().Identifier);
|
||||
}
|
||||
|
||||
private bool CanStartEventSet(EventSet eventSet)
|
||||
{
|
||||
ISpatialEntity refEntity = GetRefEntity();
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace Barotrauma
|
||||
|
||||
Additive = element.GetAttributeBool("additive", false);
|
||||
|
||||
string levelTypeStr = element.GetAttributeString("leveltype", "LocationConnection");
|
||||
string levelTypeStr = element.GetAttributeString("leveltype", parentSet?.LevelType.ToString() ?? "LocationConnection");
|
||||
if (!Enum.TryParse(levelTypeStr, true, out LevelType))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.");
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Barotrauma
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
int probabilitySum = allowedMissions.Sum(m => m.Commonness);
|
||||
int randomNumber = rand.NextInt32() % probabilitySum;
|
||||
foreach (MissionPrefab missionPrefab in allowedMissions)
|
||||
@@ -377,10 +377,16 @@ namespace Barotrauma
|
||||
crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
|
||||
|
||||
int totalReward = (int)(reward * missionMoneyGainMultiplier.Value);
|
||||
campaign.Money += totalReward;
|
||||
|
||||
GameAnalyticsManager.AddMoneyGainedEvent(totalReward, GameAnalyticsManager.MoneySource.MissionReward, Prefab.Identifier.Value);
|
||||
|
||||
#if SERVER
|
||||
totalReward = DistributeRewardsToCrew(GetSalaryEligibleCrew(), totalReward);
|
||||
#endif
|
||||
if (totalReward > 0)
|
||||
{
|
||||
campaign.Bank.Give(totalReward);
|
||||
}
|
||||
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
character.Info.MissionsCompletedSinceDeath++;
|
||||
@@ -409,6 +415,57 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
public static int DistributeRewardsToCrew(IEnumerable<Character> crew, int totalReward)
|
||||
{
|
||||
int remainingRewards = totalReward;
|
||||
HashSet<Character> nonBotCrew = crew.Where(c => !c.IsBot).ToHashSet();
|
||||
float sum = nonBotCrew.Sum(c => c.Wallet.RewardDistribution);
|
||||
if (sum == 0) { return remainingRewards; }
|
||||
foreach (Character character in nonBotCrew)
|
||||
{
|
||||
float rewardWeight = character.Wallet.RewardDistribution / sum;
|
||||
int reward = (int)Math.Floor(totalReward * rewardWeight);
|
||||
reward = Math.Max(remainingRewards, reward);
|
||||
character.Wallet.Give(reward);
|
||||
remainingRewards -= reward;
|
||||
if (0 >= remainingRewards) { break; }
|
||||
}
|
||||
|
||||
return remainingRewards;
|
||||
}
|
||||
#endif
|
||||
|
||||
public static IEnumerable<Character> GetSalaryEligibleCrew()
|
||||
{
|
||||
if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return Array.Empty<Character>(); }
|
||||
|
||||
IEnumerable<Character> characters = crewManager.GetCharacters();
|
||||
#if SERVER
|
||||
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(IsAlive).Concat(characters);
|
||||
#elif CLIENT
|
||||
return characters;
|
||||
#endif
|
||||
static bool IsAlive(Character c) { return c.Info != null && !c.IsDead; }
|
||||
}
|
||||
|
||||
|
||||
public static (int Amount, int Percentage) GetRewardShare(int rewardDistribution, IEnumerable<Character> crew, Option<int> reward)
|
||||
{
|
||||
float sum = crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution;
|
||||
if (sum == 0) { return (0, 0); }
|
||||
|
||||
float rewardWeight = rewardDistribution / sum;
|
||||
int rewardPercentage = (int)(rewardWeight * 100);
|
||||
|
||||
return reward switch
|
||||
{
|
||||
Some<int> { Value: var amount } => ((int)(amount * rewardWeight), rewardPercentage),
|
||||
None<int> _ => (0, rewardPercentage),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
protected void ChangeLocationType(LocationTypeChange change)
|
||||
{
|
||||
if (change == null) { throw new ArgumentException(); }
|
||||
|
||||
@@ -361,7 +361,7 @@ namespace Barotrauma
|
||||
if (!SendUserStatistics) { return; }
|
||||
if (sentEventIdentifiers.Contains(identifier)) { return; }
|
||||
|
||||
if (GameMain.VanillaContent == null || ContentPackageManager.EnabledPackages.All.Any(p => p.HasMultiplayerIncompatibleContent && p != GameMain.VanillaContent))
|
||||
if (GameMain.VanillaContent == null || ContentPackageManager.EnabledPackages.All.Any(p => p.HasMultiplayerSyncedContent && p != GameMain.VanillaContent))
|
||||
{
|
||||
message = "[MODDED] " + message;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Barotrauma
|
||||
foreach (Item spawnedItem in spawnedItems)
|
||||
{
|
||||
#if SERVER
|
||||
Entity.Spawner.CreateNetworkEvent(spawnedItem, remove: false);
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(spawnedItem));
|
||||
#endif
|
||||
foreach (ItemComponent ic in spawnedItem.Components)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
#if SERVER
|
||||
using Barotrauma.Networking;
|
||||
#endif
|
||||
@@ -18,12 +19,34 @@ namespace Barotrauma
|
||||
public int Quantity { get; set; }
|
||||
public bool? IsStoreComponentEnabled { get; set; }
|
||||
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity)
|
||||
public readonly int BuyerCharacterInfoId;
|
||||
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyerCharacterInfoId;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer = null)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyer?.Character?.Info?.ID ?? Character.Controlled?.Info?.ID ?? 0;
|
||||
}
|
||||
#elif SERVER
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyer?.Character?.Info?.ID ?? 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
class SoldItem
|
||||
@@ -156,7 +179,7 @@ namespace Barotrauma
|
||||
OnPurchasedItemsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void ModifyItemQuantityInBuyCrate(ItemPrefab itemPrefab, int changeInQuantity)
|
||||
public void ModifyItemQuantityInBuyCrate(ItemPrefab itemPrefab, int changeInQuantity, Client client = null)
|
||||
{
|
||||
var itemInCrate = ItemsInBuyCrate.Find(i => i.ItemPrefab == itemPrefab);
|
||||
if (itemInCrate != null)
|
||||
@@ -167,15 +190,15 @@ namespace Barotrauma
|
||||
ItemsInBuyCrate.Remove(itemInCrate);
|
||||
}
|
||||
}
|
||||
else if(changeInQuantity > 0)
|
||||
else if (changeInQuantity > 0)
|
||||
{
|
||||
itemInCrate = new PurchasedItem(itemPrefab, changeInQuantity);
|
||||
itemInCrate = new PurchasedItem(itemPrefab, changeInQuantity, client);
|
||||
ItemsInBuyCrate.Add(itemInCrate);
|
||||
}
|
||||
OnItemsInBuyCrateChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void ModifyItemQuantityInSubSellCrate(ItemPrefab itemPrefab, int changeInQuantity)
|
||||
public void ModifyItemQuantityInSubSellCrate(ItemPrefab itemPrefab, int changeInQuantity, Client client = null)
|
||||
{
|
||||
var itemInCrate = ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab);
|
||||
if (itemInCrate != null)
|
||||
@@ -188,13 +211,13 @@ namespace Barotrauma
|
||||
}
|
||||
else if (changeInQuantity > 0)
|
||||
{
|
||||
itemInCrate = new PurchasedItem(itemPrefab, changeInQuantity);
|
||||
itemInCrate = new PurchasedItem(itemPrefab, changeInQuantity, client);
|
||||
ItemsInSellFromSubCrate.Add(itemInCrate);
|
||||
}
|
||||
OnItemsInSellFromSubCrateChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void PurchaseItems(List<PurchasedItem> itemsToPurchase, bool removeFromCrate)
|
||||
public void PurchaseItems(List<PurchasedItem> itemsToPurchase, bool removeFromCrate, Client client = null)
|
||||
{
|
||||
// Check all the prices before starting the transaction
|
||||
// to make sure the modifiers stay the same for the whole transaction
|
||||
@@ -210,13 +233,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity);
|
||||
purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity, client);
|
||||
PurchasedItems.Add(purchasedItem);
|
||||
}
|
||||
|
||||
// Exchange money
|
||||
var itemValue = item.Quantity * buyValues[item.ItemPrefab];
|
||||
campaign.Money -= itemValue;
|
||||
campaign.GetWallet(client).TryDeduct(itemValue);
|
||||
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
|
||||
Location.StoreCurrentBalance += itemValue;
|
||||
|
||||
@@ -427,7 +450,7 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -438,7 +461,7 @@ namespace Barotrauma
|
||||
|
||||
itemSpawned(item);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(item, false);
|
||||
Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
(itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo;
|
||||
static void itemSpawned(Item item)
|
||||
@@ -491,7 +514,8 @@ namespace Barotrauma
|
||||
if (item?.ItemPrefab == null) { continue; }
|
||||
itemsElement.Add(new XElement("item",
|
||||
new XAttribute("id", item.ItemPrefab.Identifier),
|
||||
new XAttribute("qty", item.Quantity)));
|
||||
new XAttribute("qty", item.Quantity),
|
||||
new XAttribute("buyer", item.BuyerCharacterInfoId)));
|
||||
}
|
||||
parentElement.Add(itemsElement);
|
||||
}
|
||||
@@ -503,12 +527,15 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (XElement itemElement in element.GetChildElements("item"))
|
||||
{
|
||||
var id = itemElement.GetAttributeString("id", null);
|
||||
string id = itemElement.GetAttributeString("id", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id);
|
||||
if (prefab == null) { continue; }
|
||||
var qty = itemElement.GetAttributeInt("qty", 0);
|
||||
purchasedItems.Add(new PurchasedItem(prefab, qty));
|
||||
int qty = itemElement.GetAttributeInt("qty", 0);
|
||||
int buyerId = itemElement.GetAttributeInt("buyer", 0);
|
||||
|
||||
purchasedItems.Add(new PurchasedItem(prefab, qty, buyerId));
|
||||
|
||||
}
|
||||
}
|
||||
SetPurchasedItems(purchasedItems);
|
||||
|
||||
@@ -20,6 +20,16 @@ namespace Barotrauma
|
||||
private readonly List<CharacterInfo> characterInfos = new List<CharacterInfo>();
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
|
||||
public IEnumerable<Character> GetCharacters()
|
||||
{
|
||||
return characters;
|
||||
}
|
||||
|
||||
public IEnumerable<CharacterInfo> GetCharacterInfos()
|
||||
{
|
||||
return characterInfos;
|
||||
}
|
||||
|
||||
private Character welcomeMessageNPC;
|
||||
|
||||
public List<CharacterInfo> CharacterInfos => characterInfos;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -27,6 +25,8 @@ namespace Barotrauma
|
||||
public LocalizedString Description { get; }
|
||||
public LocalizedString ShortDescription { get; }
|
||||
|
||||
public int MenuOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// How low the reputation can drop on this faction
|
||||
/// </summary>
|
||||
@@ -52,6 +52,7 @@ namespace Barotrauma
|
||||
|
||||
public FactionPrefab(ContentXElement element, FactionsFile file) : base(file, element.GetAttributeIdentifier("identifier", string.Empty))
|
||||
{
|
||||
MenuOrder = element.GetAttributeInt("menuorder", 0);
|
||||
MinReputation = element.GetAttributeInt("minreputation", -100);
|
||||
MaxReputation = element.GetAttributeInt("maxreputation", 100);
|
||||
InitialReputation = element.GetAttributeInt("initialreputation", 0);
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace Barotrauma
|
||||
("[reputationvalue]", ((int)Math.Round(value)).ToString()));
|
||||
if (addColorTags)
|
||||
{
|
||||
formattedReputation = $"‖color:{XMLExtensions.ColorToString(GetReputationColor(normalizedValue))}‖"+ formattedReputation+"‖end‖";
|
||||
formattedReputation = $"‖color:{XMLExtensions.ToStringHex(GetReputationColor(normalizedValue))}‖{formattedReputation}‖end‖";
|
||||
}
|
||||
return formattedReputation;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal readonly struct WalletChangedEvent
|
||||
{
|
||||
public readonly Wallet Wallet;
|
||||
public readonly WalletInfo Info;
|
||||
public readonly WalletChangedData ChangedData;
|
||||
|
||||
public WalletChangedEvent(Wallet wallet, WalletChangedData changedData, WalletInfo info)
|
||||
{
|
||||
Wallet = wallet;
|
||||
Info = info;
|
||||
ChangedData = changedData;
|
||||
}
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct WalletInfo : INetSerializableStruct
|
||||
{
|
||||
public int RewardDistribution;
|
||||
public int Balance;
|
||||
}
|
||||
|
||||
internal struct NetWalletUpdate : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize(ArrayMaxSize = NetConfig.MaxPlayers + 1)]
|
||||
public NetWalletTransaction[] Transactions;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct NetWalletTransfer : INetSerializableStruct
|
||||
{
|
||||
public Option<ushort> Sender;
|
||||
public Option<ushort> Receiver;
|
||||
public int Amount;
|
||||
}
|
||||
|
||||
internal struct NetWalletSalaryUpdate : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize]
|
||||
public ushort Target;
|
||||
|
||||
[NetworkSerialize(MinValueInt = 0, MaxValueInt = 100)]
|
||||
public int NewRewardDistribution;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct WalletChangedData : INetSerializableStruct
|
||||
{
|
||||
public Option<int> RewardDistributionChanged;
|
||||
public Option<int> BalanceChanged;
|
||||
|
||||
public WalletChangedData MergeInto(WalletChangedData other)
|
||||
{
|
||||
other.BalanceChanged = AddOptionalInt(other.BalanceChanged, BalanceChanged);
|
||||
other.RewardDistributionChanged = AddOptionalInt(other.RewardDistributionChanged, RewardDistributionChanged);
|
||||
return other;
|
||||
|
||||
static Option<int> AddOptionalInt(Option<int> a, Option<int> b)
|
||||
{
|
||||
return a switch
|
||||
{
|
||||
Some<int> some1 => b switch
|
||||
{
|
||||
Some<int> some2 => Option<int>.Some(some1.Value + some2.Value),
|
||||
None<int> _ => Option<int>.Some(some1.Value),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(b))
|
||||
},
|
||||
None<int> _ => b switch
|
||||
{
|
||||
Some<int> some1 => Option<int>.Some(some1.Value),
|
||||
None<int> _ => Option<int>.None(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(b))
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(a))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct NetWalletTransaction : INetSerializableStruct
|
||||
{
|
||||
public Option<ushort> CharacterID;
|
||||
public WalletChangedData ChangedData;
|
||||
public WalletInfo Info;
|
||||
}
|
||||
|
||||
// ReSharper disable ValueParameterNotUsed
|
||||
internal sealed class InvalidWallet : Wallet
|
||||
{
|
||||
public override int Balance
|
||||
{
|
||||
get => 0;
|
||||
set => new InvalidOperationException("Tried to set the balance on an invalid wallet");
|
||||
}
|
||||
|
||||
public override int RewardDistribution
|
||||
{
|
||||
get => 0;
|
||||
set => new InvalidOperationException("Tried to set the reward distribution on an invalid wallet");
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class Wallet
|
||||
{
|
||||
public static readonly Wallet Invalid = new InvalidWallet();
|
||||
|
||||
public const string LowerCaseSaveElementName = "wallet";
|
||||
|
||||
private const string AttributeNameBalance = "balance",
|
||||
AttrubuteNameRewardDistribution = "rewarddistribution",
|
||||
SaveElementName = "Wallet";
|
||||
|
||||
private int balance;
|
||||
|
||||
public virtual int Balance
|
||||
{
|
||||
get => balance;
|
||||
set => balance = ClampBalance(value);
|
||||
}
|
||||
|
||||
private int rewardDistribution;
|
||||
|
||||
public virtual int RewardDistribution
|
||||
{
|
||||
get => rewardDistribution;
|
||||
set => rewardDistribution = ClampRewardDistribution(value);
|
||||
}
|
||||
|
||||
public Wallet() { }
|
||||
|
||||
public Wallet(XElement element)
|
||||
{
|
||||
balance = ClampBalance(element.GetAttributeInt(AttributeNameBalance, 0));
|
||||
rewardDistribution = ClampBalance(element.GetAttributeInt(AttrubuteNameRewardDistribution, 0));
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement(SaveElementName, new XAttribute(AttributeNameBalance, Balance), new XAttribute(AttrubuteNameRewardDistribution, RewardDistribution));
|
||||
return element;
|
||||
}
|
||||
|
||||
public bool TryDeduct(int price)
|
||||
{
|
||||
if (!CanAfford(price)) { return false; }
|
||||
|
||||
Deduct(price);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanAfford(int price) => Balance >= price;
|
||||
public void Refund(int price) => Give(price);
|
||||
|
||||
public void Give(int amount)
|
||||
{
|
||||
Balance += amount;
|
||||
SettingsChanged(balanceChanged: Option<int>.Some(amount), rewardChanged: Option<int>.None());
|
||||
}
|
||||
|
||||
public void Deduct(int price)
|
||||
{
|
||||
Balance -= price;
|
||||
SettingsChanged(balanceChanged: Option<int>.Some(-price), rewardChanged: Option<int>.None());
|
||||
}
|
||||
|
||||
public void SetRewardDistrubiton(int value)
|
||||
{
|
||||
int oldValue = RewardDistribution;
|
||||
RewardDistribution = value;
|
||||
SettingsChanged(balanceChanged: Option<int>.None(), rewardChanged: Option<int>.Some(RewardDistribution - oldValue));
|
||||
}
|
||||
|
||||
public WalletInfo CreateWalletInfo()
|
||||
{
|
||||
return new WalletInfo
|
||||
{
|
||||
Balance = Balance,
|
||||
RewardDistribution = RewardDistribution
|
||||
};
|
||||
}
|
||||
|
||||
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged);
|
||||
|
||||
private static int ClampBalance(int value) => Math.Clamp(value, 0, CampaignMode.MaxMoney);
|
||||
private static int ClampRewardDistribution(int value) => Math.Clamp(value, 0, 100);
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ namespace Barotrauma
|
||||
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
const int MaxMoney = int.MaxValue / 2; //about 1 billion
|
||||
public const int MaxMoney = int.MaxValue / 2; //about 1 billion
|
||||
public const int InitialMoney = 8500;
|
||||
|
||||
//duration of the cinematic + credits at the end of the campaign
|
||||
@@ -102,6 +102,8 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<Mission> extraMissions = new List<Mission>();
|
||||
|
||||
public readonly NamedEvent<WalletChangedEvent> OnMoneyChanged = new NamedEvent<WalletChangedEvent>();
|
||||
|
||||
public enum TransitionType
|
||||
{
|
||||
None,
|
||||
@@ -167,12 +169,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private int money;
|
||||
public int Money
|
||||
{
|
||||
get { return money; }
|
||||
set { money = MathHelper.Clamp(value, 0, MaxMoney); }
|
||||
}
|
||||
public Wallet Bank;
|
||||
|
||||
public LevelData NextLevel
|
||||
{
|
||||
@@ -183,11 +180,20 @@ namespace Barotrauma
|
||||
protected CampaignMode(GameModePreset preset)
|
||||
: base(preset)
|
||||
{
|
||||
Money = InitialMoney;
|
||||
Bank = new Wallet
|
||||
{
|
||||
Balance = InitialMoney
|
||||
};
|
||||
|
||||
CargoManager = new CargoManager(this);
|
||||
MedicalClinic = new MedicalClinic(this);
|
||||
}
|
||||
|
||||
public virtual Wallet GetWallet(Client client = null)
|
||||
{
|
||||
return Bank;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The location that's displayed as the "current one" in the map screen. Normally the current outpost or the location at the start of the level,
|
||||
/// but when selecting the next destination at the end of the level at an uninhabited location we use the location at the end
|
||||
@@ -200,7 +206,7 @@ namespace Barotrauma
|
||||
{
|
||||
return Level.Loaded.EndLocation;
|
||||
}
|
||||
return Level.Loaded?.StartLocation ?? Map.CurrentLocation;
|
||||
return Level.Loaded?.StartLocation ?? Map.CurrentLocation;
|
||||
}
|
||||
|
||||
public List<Submarine> GetSubsToLeaveBehind(Submarine leavingSub)
|
||||
@@ -255,8 +261,6 @@ namespace Barotrauma
|
||||
PurchasedLostShuttles = false;
|
||||
var connectedSubs = Submarine.MainSub.GetConnectedSubs();
|
||||
wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
|
||||
|
||||
ResetTalentData();
|
||||
}
|
||||
|
||||
public void InitCampaignData()
|
||||
@@ -702,21 +706,20 @@ namespace Barotrauma
|
||||
string eventId = "FinishCampaign:";
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"));
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0));
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "Money", Money);
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "Money", Bank.Balance);
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "Playtime", TotalPlayTime);
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "PassedLevels", TotalPassedLevels);
|
||||
}
|
||||
|
||||
protected virtual void EndCampaignProjSpecific() { }
|
||||
|
||||
public bool TryHireCharacter(Location location, CharacterInfo characterInfo)
|
||||
public bool TryHireCharacter(Location location, CharacterInfo characterInfo, Client client = null)
|
||||
{
|
||||
if (characterInfo == null) { return false; }
|
||||
if (Money < characterInfo.Salary) { return false; }
|
||||
if (!GetWallet(client).TryDeduct(characterInfo.Salary)) { return false; }
|
||||
characterInfo.IsNewHire = true;
|
||||
location.RemoveHireableCharacter(characterInfo);
|
||||
CrewManager.AddCharacterInfo(characterInfo);
|
||||
Money -= characterInfo.Salary;
|
||||
GameAnalyticsManager.AddMoneySpentEvent(characterInfo.Salary, GameAnalyticsManager.MoneySink.Crew, characterInfo.Job?.Prefab.Identifier.Value ?? "unknown");
|
||||
return true;
|
||||
}
|
||||
@@ -740,8 +743,7 @@ namespace Barotrauma
|
||||
HumanAIController humanAI = npc.AIController as HumanAIController;
|
||||
if (humanAI == null) { yield return CoroutineStatus.Success; }
|
||||
|
||||
var waitOrderPrefab = OrderPrefab.Prefabs["wait"];
|
||||
var waitOrder = new Order(waitOrderPrefab, Identifier.Empty, null, orderGiver: null);
|
||||
var waitOrder = OrderPrefab.Prefabs["wait"].CreateInstance(OrderPrefab.OrderTargetType.Entity);
|
||||
humanAI.SetForcedOrder(waitOrder);
|
||||
var waitObjective = humanAI.ObjectiveManager.ForcedOrder;
|
||||
humanAI.FaceTarget(interactor);
|
||||
@@ -856,7 +858,7 @@ namespace Barotrauma
|
||||
{
|
||||
|
||||
GameMain.Server.SendDirectChatMessage(Networking.ChatMessage.Create(
|
||||
TextManager.Get("RadioAnnouncerName").Value,
|
||||
TextManager.Get("RadioAnnouncerName").Value,
|
||||
TextManager.Get("TooFarFromOutpostWarning").Value, Networking.ChatMessageType.Default, null), c);
|
||||
}
|
||||
#endif
|
||||
@@ -906,7 +908,7 @@ namespace Barotrauma
|
||||
public void LogState()
|
||||
{
|
||||
DebugConsole.NewMessage("********* CAMPAIGN STATUS *********", Color.White);
|
||||
DebugConsole.NewMessage(" Money: " + Money, Color.White);
|
||||
DebugConsole.NewMessage(" Money: " + Bank.Balance, Color.White);
|
||||
DebugConsole.NewMessage(" Current location: " + map.CurrentLocation.Name, Color.White);
|
||||
|
||||
DebugConsole.NewMessage(" Available destinations: ", Color.White);
|
||||
@@ -960,13 +962,5 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// Talent relevant data, only stored for the duration of the mission
|
||||
private void ResetTalentData()
|
||||
{
|
||||
CrewHasDied = false;
|
||||
}
|
||||
|
||||
public bool CrewHasDied { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-28
@@ -26,33 +26,6 @@ namespace Barotrauma
|
||||
private XElement itemData;
|
||||
private XElement healthData;
|
||||
public XElement OrderData { get; private set; }
|
||||
|
||||
public void Refresh(Character character)
|
||||
{
|
||||
healthData = new XElement("health");
|
||||
character.CharacterHealth.Save(healthData);
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
itemData = new XElement("inventory");
|
||||
Character.SaveInventory(character.Inventory, itemData);
|
||||
}
|
||||
OrderData = new XElement("orders");
|
||||
CharacterInfo.SaveOrderData(character.Info, OrderData);
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement("CharacterCampaignData",
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("endpoint", ClientEndPoint),
|
||||
new XAttribute("steamid", SteamID));
|
||||
|
||||
CharacterInfo?.Save(element);
|
||||
if (itemData != null) { element.Add(itemData); }
|
||||
if (healthData != null) { element.Add(healthData); }
|
||||
if (OrderData != null) { element.Add(OrderData); }
|
||||
|
||||
return element;
|
||||
}
|
||||
public XElement WalletData;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-1
@@ -99,7 +99,6 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private void Load(XElement element)
|
||||
{
|
||||
Money = element.GetAttributeInt("money", 0);
|
||||
PurchasedLostShuttles = element.GetAttributeBool("purchasedlostshuttles", false);
|
||||
PurchasedHullRepairs = element.GetAttributeBool("purchasedhullrepairs", false);
|
||||
PurchasedItemRepairs = element.GetAttributeBool("purchaseditemrepairs", false);
|
||||
@@ -166,6 +165,9 @@ namespace Barotrauma
|
||||
case "stats":
|
||||
LoadStats(subElement);
|
||||
break;
|
||||
case Wallet.LowerCaseSaveElementName:
|
||||
Bank = new Wallet(subElement);
|
||||
break;
|
||||
#if SERVER
|
||||
case "savedexperiencepoints":
|
||||
foreach (XElement savedExp in subElement.Elements())
|
||||
@@ -177,6 +179,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
int oldMoney = element.GetAttributeInt("money", 0);
|
||||
if (oldMoney > 0)
|
||||
{
|
||||
Bank = new Wallet
|
||||
{
|
||||
Balance = oldMoney
|
||||
};
|
||||
}
|
||||
|
||||
CampaignMetadata ??= new CampaignMetadata(this);
|
||||
UpgradeManager ??= new UpgradeManager(this);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -30,10 +31,14 @@ namespace Barotrauma
|
||||
private readonly List<Mission> missions = new List<Mission>();
|
||||
public IEnumerable<Mission> Missions { get { return missions; } }
|
||||
|
||||
private readonly HashSet<Character> casualties = new HashSet<Character>();
|
||||
public IEnumerable<Character> Casualties { get { return casualties; } }
|
||||
|
||||
|
||||
public CharacterTeamType? WinningTeam;
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
|
||||
public bool RoundEnding { get; private set; }
|
||||
|
||||
public Level? Level { get; private set; }
|
||||
@@ -201,7 +206,8 @@ namespace Barotrauma
|
||||
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
if (selectedSub != null)
|
||||
{
|
||||
campaign.Money = Math.Max(MultiPlayerCampaign.MinimumInitialMoney, campaign.Money - selectedSub.Price);
|
||||
campaign.Bank.TryDeduct(selectedSub.Price);
|
||||
campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, MultiPlayerCampaign.MinimumInitialMoney);
|
||||
}
|
||||
return campaign;
|
||||
}
|
||||
@@ -211,7 +217,8 @@ namespace Barotrauma
|
||||
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
if (selectedSub != null)
|
||||
{
|
||||
campaign.Money = Math.Max(SinglePlayerCampaign.MinimumInitialMoney, campaign.Money - selectedSub.Price);
|
||||
campaign.Bank.TryDeduct(selectedSub.Price);
|
||||
campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, MultiPlayerCampaign.MinimumInitialMoney);
|
||||
}
|
||||
return campaign;
|
||||
}
|
||||
@@ -264,7 +271,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Switch to another submarine. The sub is loaded when the next round starts.
|
||||
/// </summary>
|
||||
public SubmarineInfo SwitchSubmarine(SubmarineInfo newSubmarine, int cost)
|
||||
public SubmarineInfo SwitchSubmarine(SubmarineInfo newSubmarine, int cost, Client? client = null)
|
||||
{
|
||||
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
|
||||
{
|
||||
@@ -283,19 +290,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Campaign!.Money -= cost;
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && cost > 0)
|
||||
{
|
||||
Campaign!.GetWallet(client).TryDeduct(cost);
|
||||
}
|
||||
GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name);
|
||||
|
||||
return newSubmarine;
|
||||
}
|
||||
|
||||
public void PurchaseSubmarine(SubmarineInfo newSubmarine)
|
||||
public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null)
|
||||
{
|
||||
if (Campaign is null) { return; }
|
||||
if (Campaign.Money < newSubmarine.Price) { return; }
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && !Campaign.GetWallet(client).TryDeduct(newSubmarine.Price)) { return; }
|
||||
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
|
||||
{
|
||||
Campaign.Money -= newSubmarine.Price;
|
||||
GameAnalyticsManager.AddMoneySpentEvent(newSubmarine.Price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name);
|
||||
OwnedSubmarines.Add(newSubmarine);
|
||||
}
|
||||
@@ -346,7 +355,7 @@ namespace Barotrauma
|
||||
public void StartRound(LevelData? levelData, bool mirrorLevel = false, SubmarineInfo? startOutpost = null, SubmarineInfo? endOutpost = null)
|
||||
{
|
||||
AfflictionPrefab.LoadAllEffects();
|
||||
|
||||
|
||||
MirrorLevel = mirrorLevel;
|
||||
if (SubmarineInfo == null)
|
||||
{
|
||||
@@ -411,9 +420,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//Clear out the stored grids
|
||||
Powered.Grids.Clear();
|
||||
|
||||
Level? level = null;
|
||||
if (levelData != null)
|
||||
{
|
||||
@@ -422,6 +428,11 @@ namespace Barotrauma
|
||||
|
||||
InitializeLevel(level);
|
||||
|
||||
//Clear out the cached grids and force update
|
||||
Powered.Grids.Clear();
|
||||
|
||||
casualties.Clear();
|
||||
|
||||
GameAnalyticsManager.AddProgressionEvent(
|
||||
GameAnalyticsManager.ProgressionStatus.Start,
|
||||
GameMode?.Preset?.Identifier.Value ?? "none");
|
||||
@@ -480,7 +491,7 @@ namespace Barotrauma
|
||||
existingRoundSummary.ContinueButton.Visible = true;
|
||||
}
|
||||
|
||||
RoundSummary = new RoundSummary(Submarine.Info, GameMode, Missions, StartLocation, EndLocation);
|
||||
RoundSummary = new RoundSummary(GameMode, Missions, StartLocation, EndLocation);
|
||||
|
||||
if (!(GameMode is TutorialMode) && !(GameMode is TestGameMode))
|
||||
{
|
||||
@@ -723,7 +734,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
|
||||
public static IEnumerable<Character> GetSessionCrewCharacters()
|
||||
{
|
||||
#if SERVER
|
||||
@@ -745,7 +756,7 @@ namespace Barotrauma
|
||||
{
|
||||
IEnumerable<Character> crewCharacters = GetSessionCrewCharacters();
|
||||
|
||||
int prevMoney = (GameMode as CampaignMode)?.Money ?? 0;
|
||||
int prevMoney = (GameMode as CampaignMode)?.Bank.Balance ?? 0; // FIXME personal wallets - reward distribution
|
||||
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
@@ -759,14 +770,13 @@ namespace Barotrauma
|
||||
|
||||
if (missions.Any())
|
||||
{
|
||||
if (missions.Any())
|
||||
if (missions.Any(m => m.Completed))
|
||||
{
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.OnAnyMissionCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
if (missions.All(m => m.Completed))
|
||||
{
|
||||
foreach (Character character in crewCharacters)
|
||||
@@ -818,7 +828,7 @@ namespace Barotrauma
|
||||
LogEndRoundStats(eventId);
|
||||
if (GameMode is CampaignMode campaignMode)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", campaignMode.Money - prevMoney);
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", campaignMode.Bank.Balance - prevMoney); // FIXME personal wallets - reward distrubiton
|
||||
campaignMode.TotalPlayTime += roundDuration;
|
||||
}
|
||||
#if CLIENT
|
||||
@@ -910,6 +920,10 @@ namespace Barotrauma
|
||||
|
||||
public void KillCharacter(Character character)
|
||||
{
|
||||
if (CrewManager != null && CrewManager.GetCharacters().Contains(character))
|
||||
{
|
||||
casualties.Add(character);
|
||||
}
|
||||
#if CLIENT
|
||||
CrewManager?.KillCharacter(character);
|
||||
#endif
|
||||
@@ -917,6 +931,7 @@ namespace Barotrauma
|
||||
|
||||
public void ReviveCharacter(Character character)
|
||||
{
|
||||
casualties.Remove(character);
|
||||
#if CLIENT
|
||||
CrewManager?.ReviveCharacter(character);
|
||||
#endif
|
||||
@@ -939,7 +954,7 @@ namespace Barotrauma
|
||||
List<string> excessPackages = new List<string>();
|
||||
foreach (ContentPackage cp in ContentPackageManager.EnabledPackages.All)
|
||||
{
|
||||
//if (!cp.HasMultiplayerIncompatibleContent) { continue; }
|
||||
if (!cp.HasMultiplayerSyncedContent) { continue; }
|
||||
if (!contentPackagePaths.Any(p => p == cp.Path))
|
||||
{
|
||||
excessPackages.Add(cp.Name);
|
||||
@@ -949,7 +964,7 @@ namespace Barotrauma
|
||||
bool orderMismatch = false;
|
||||
if (missingPackages.Count == 0 && missingPackages.Count == 0)
|
||||
{
|
||||
var enabledPackages = ContentPackageManager.EnabledPackages.All/*.Where(cp => cp.HasMultiplayerIncompatibleContent)*/.ToImmutableArray();
|
||||
var enabledPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).ToImmutableArray();
|
||||
for (int i = 0; i < contentPackagePaths.Count && i < enabledPackages.Length; i++)
|
||||
{
|
||||
if (contentPackagePaths[i] != enabledPackages[i].Path)
|
||||
@@ -1015,7 +1030,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (Map != null) { rootElement.Add(new XAttribute("mapseed", Map.Seed)); }
|
||||
rootElement.Add(new XAttribute("selectedcontentpackages",
|
||||
string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerIncompatibleContent).Select(cp => cp.Path))));
|
||||
string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Path))));
|
||||
|
||||
((CampaignMode)GameMode).Save(doc.Root);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -177,17 +178,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<NetCrewMember> PendingHeals = new List<NetCrewMember>();
|
||||
|
||||
public Action? OnUpdate;
|
||||
|
||||
private readonly CampaignMode? campaign;
|
||||
|
||||
public MedicalClinic(CampaignMode campaign)
|
||||
{
|
||||
this.campaign = campaign;
|
||||
#if CLIENT
|
||||
campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(MedicalClinic).ToIdentifier(), OnMoneyChanged);
|
||||
#endif
|
||||
}
|
||||
|
||||
public readonly List<NetCrewMember> PendingHeals = new List<NetCrewMember>();
|
||||
|
||||
public Action? OnUpdate;
|
||||
|
||||
private static bool IsOutpostInCombat()
|
||||
{
|
||||
if (!(Level.Loaded is { Type: LevelData.LevelType.Outpost })) { return false; }
|
||||
@@ -203,14 +207,13 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
private HealRequestResult HealAllPending(bool force = false)
|
||||
private HealRequestResult HealAllPending(bool force = false, Client? client = null)
|
||||
{
|
||||
int totalCost = GetTotalCost();
|
||||
if (!force)
|
||||
{
|
||||
if (GetMoney() < totalCost) { return HealRequestResult.InsufficientFunds; }
|
||||
|
||||
if (IsOutpostInCombat()) { return HealRequestResult.Refused; }
|
||||
if (!GetWallet(client).TryDeduct(totalCost)) { return HealRequestResult.InsufficientFunds; }
|
||||
}
|
||||
|
||||
ImmutableArray<CharacterInfo> crew = GetCrewCharacters();
|
||||
@@ -225,11 +228,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (campaign != null)
|
||||
{
|
||||
campaign.Money -= totalCost;
|
||||
}
|
||||
|
||||
ClearPendingHeals();
|
||||
|
||||
return HealRequestResult.Success;
|
||||
@@ -316,7 +314,10 @@ namespace Barotrauma
|
||||
|
||||
private int GetAdjustedPrice(int price) => campaign?.Map?.CurrentLocation is { Type: { HasOutpost: true } } currentLocation ? currentLocation.GetAdjustedHealCost(price) : int.MaxValue;
|
||||
|
||||
public int GetMoney() => campaign?.Money ?? 0;
|
||||
public Wallet GetWallet(Client? c = null)
|
||||
{
|
||||
return campaign?.GetWallet(c) ?? Wallet.Invalid;
|
||||
}
|
||||
|
||||
public static ImmutableArray<CharacterInfo> GetCrewCharacters()
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
@@ -178,7 +179,7 @@ namespace Barotrauma
|
||||
/// Purchased upgrades are temporarily stored in <see cref="PendingUpgrades"/> and they are applied
|
||||
/// after the next round starts similarly how items are spawned in the stowage room after the round starts.
|
||||
/// </remarks>
|
||||
public void PurchaseUpgrade(UpgradePrefab prefab, UpgradeCategory category, bool force = false)
|
||||
public void PurchaseUpgrade(UpgradePrefab prefab, UpgradeCategory category, bool force = false, Client? client = null)
|
||||
{
|
||||
if (!CanUpgradeSub())
|
||||
{
|
||||
@@ -215,7 +216,7 @@ namespace Barotrauma
|
||||
price = 0;
|
||||
}
|
||||
|
||||
if (Campaign.Money >= price)
|
||||
if (Campaign.GetWallet(client).TryDeduct(price)) // FIXME personal wallets
|
||||
{
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
@@ -227,7 +228,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Campaign.Money -= price;
|
||||
GameAnalyticsManager.AddMoneySpentEvent(price, GameAnalyticsManager.MoneySink.SubmarineUpgrade, prefab.Identifier.Value);
|
||||
|
||||
PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category);
|
||||
@@ -253,14 +253,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to purchase an upgrade with insufficient funds, the transaction has not been completed.\n" +
|
||||
$"Upgrade: {prefab.Name}, Cost: {price}, Have: {Campaign.Money}");
|
||||
$"Upgrade: {prefab.Name}, Cost: {price}, Have: {Campaign.GetWallet(client).Balance}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Purchases an item swap and handles logic for deducting the credit.
|
||||
/// </summary>
|
||||
public void PurchaseItemSwap(Item itemToRemove, ItemPrefab itemToInstall, bool force = false)
|
||||
public void PurchaseItemSwap(Item itemToRemove, ItemPrefab itemToInstall, bool force = false, Client? client = null)
|
||||
{
|
||||
if (!CanUpgradeSub())
|
||||
{
|
||||
@@ -313,7 +313,7 @@ namespace Barotrauma
|
||||
price = 0;
|
||||
}
|
||||
|
||||
if (Campaign.Money >= price)
|
||||
if (Campaign.GetWallet(client).TryDeduct(price))
|
||||
{
|
||||
PurchasedItemSwaps.RemoveAll(p => linkedItems.Contains(p.ItemToRemove));
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
@@ -326,7 +326,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Campaign.Money -= price;
|
||||
GameAnalyticsManager.AddMoneySpentEvent(price, GameAnalyticsManager.MoneySink.SubmarineWeapon, itemToInstall.Identifier.Value);
|
||||
|
||||
foreach (Item itemToSwap in linkedItems)
|
||||
@@ -355,7 +354,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to swap an item with insufficient funds, the transaction has not been completed.\n" +
|
||||
$"Item to remove: {itemToRemove.Name}, Item to install: {itemToInstall.Name}, Cost: {price}, Have: {Campaign.Money}");
|
||||
$"Item to remove: {itemToRemove.Name}, Item to install: {itemToInstall.Name}, Cost: {price}, Have: {Campaign.GetWallet(client).Balance}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Barotrauma
|
||||
IsEquipped = new bool[capacity];
|
||||
SlotTypes = new InvSlotType[capacity];
|
||||
|
||||
AccessibleWhenAlive = element.GetAttributeBool("accessiblewhenalive", true);
|
||||
AccessibleWhenAlive = element.GetAttributeBool("accessiblewhenalive", false);
|
||||
AccessibleByOwner = element.GetAttributeBool("accessiblebyowner", AccessibleWhenAlive);
|
||||
|
||||
string[] slotTypeNames = ParseSlotTypes(element);
|
||||
@@ -159,14 +159,14 @@ namespace Barotrauma
|
||||
{
|
||||
return
|
||||
base.CanBePutInSlot(item, i, ignoreCondition) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) &&
|
||||
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
|
||||
(SlotTypes[i] == InvSlotType.Any || slots[i].Items.Count < 1);
|
||||
}
|
||||
|
||||
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null)
|
||||
{
|
||||
return
|
||||
base.CanBePutInSlot(itemPrefab, i, condition, quality) &&
|
||||
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
|
||||
(SlotTypes[i] == InvSlotType.Any || slots[i].Items.Count < 1);
|
||||
}
|
||||
|
||||
public bool CanBeAutoMovedToCorrectSlots(Item item)
|
||||
|
||||
@@ -507,7 +507,7 @@ namespace Barotrauma.Items.Components
|
||||
list.Remove(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
//no further data needed, the event just triggers the discharge
|
||||
}
|
||||
|
||||
@@ -229,7 +229,6 @@ namespace Barotrauma.Items.Components
|
||||
int amount = Rand.Range(minAmount, maxAmount, Rand.RandSync.Unsynced);
|
||||
|
||||
Vector2 offset = SpawnAreaOffset;
|
||||
offset.Y = -offset.Y;
|
||||
|
||||
switch (SpawnAreaShape)
|
||||
{
|
||||
|
||||
@@ -707,7 +707,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
for (int i = 0; i < Vines.Count; i += VineChunkSize)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), i });
|
||||
item.CreateServerEvent(this, new EventData(offset: i));
|
||||
}
|
||||
#elif CLIENT
|
||||
ResetPlanterSize();
|
||||
|
||||
@@ -12,6 +12,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Holdable : Pickable, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private readonly struct EventData : IEventData
|
||||
{
|
||||
public readonly Vector2 AttachPos;
|
||||
|
||||
public EventData(Vector2 attachPos)
|
||||
{
|
||||
AttachPos = attachPos;
|
||||
}
|
||||
}
|
||||
|
||||
const float MaxAttachDistance = 150.0f;
|
||||
|
||||
//the position(s) in the item that the Character grabs
|
||||
@@ -155,8 +165,8 @@ namespace Barotrauma.Items.Components
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the item swing around when it's being used (for example, when firing a weapon or a welding tool).")]
|
||||
public bool SwingWhenUsing { get; set; }
|
||||
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.Attachable, MinValueFloat = 0.0f, MaxValueFloat = 0.999f, DecimalCount = 3), Serialize(0.85f, IsPropertySaveable.No, description: "Sprite depth that's used when the item is attached to a wall.")]
|
||||
public float SpriteDepthWhenAttached
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.Attachable, MinValueFloat = 0.0f, MaxValueFloat = 0.999f, DecimalCount = 3), Serialize(0.55f, IsPropertySaveable.No, description: "Sprite depth that's used when the item is NOT attached to a wall.")]
|
||||
public float SpriteDepthWhenDropped
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -244,12 +254,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool loadedFromXml;
|
||||
private bool loadedFromInstance;
|
||||
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
|
||||
loadedFromXml = true;
|
||||
loadedFromInstance = true;
|
||||
|
||||
if (usePrefabValues)
|
||||
{
|
||||
@@ -583,7 +593,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Attached = true;
|
||||
#if CLIENT
|
||||
item.DrawDepthOffset = SpriteDepthWhenAttached - item.SpriteDepth;
|
||||
item.DrawDepthOffset = 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -600,6 +610,9 @@ namespace Barotrauma.Items.Components
|
||||
requiredItems.Clear();
|
||||
DisplayMsg = "";
|
||||
PickKey = InputType.Select;
|
||||
#if CLIENT
|
||||
item.DrawDepthOffset = SpriteDepthWhenDropped - item.SpriteDepth;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void ParseMsg()
|
||||
@@ -663,12 +676,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
#if CLIENT
|
||||
Vector2 attachPos = ConvertUnits.ToSimUnits(GetAttachPosition(character));
|
||||
GameMain.Client.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ComponentState,
|
||||
item.GetComponentIndex(this),
|
||||
attachPos
|
||||
});
|
||||
item.CreateClientEvent(this, new EventData(attachPos));
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
@@ -867,8 +875,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!attachable) { return; }
|
||||
|
||||
//a mod has overridden the item, and the base item didn't have a Holdable component = a mod made the item movable/detachable
|
||||
if (item.Prefab.IsOverride && !loadedFromXml)
|
||||
//the Holdable component didn't get loaded from an instance of the item, just the prefab xml = a mod or update must've made the item movable/detachable
|
||||
if (!loadedFromInstance)
|
||||
{
|
||||
if (attachedByDefault)
|
||||
{
|
||||
|
||||
@@ -67,7 +67,6 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize("#ffffff", IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public Color OwnerSkinColor { get; set; }
|
||||
|
||||
#warning TODO: figure out how to set Vector2.Zero as the default here
|
||||
[Serialize("0,0", IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public Vector2 OwnerSheetIndex { get; set; }
|
||||
|
||||
|
||||
@@ -436,13 +436,10 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
Networking.NetEntityEvent.Type.ApplyStatusEffect,
|
||||
GameMain.Server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(
|
||||
success ? ActionType.OnUse : ActionType.OnFailure,
|
||||
null, //itemcomponent
|
||||
targetCharacter.ID, targetLimb
|
||||
});
|
||||
targetItemComponent: null,
|
||||
targetCharacter, targetLimb));
|
||||
|
||||
string logStr = picker?.LogName + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
|
||||
@@ -282,12 +282,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(activePicker == null ? (ushort)0 : activePicker.ID);
|
||||
msg.Write(activePicker?.ID ?? (ushort)0);
|
||||
}
|
||||
|
||||
public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
public virtual void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
ushort pickerID = msg.ReadUInt16();
|
||||
if (pickerID == 0)
|
||||
|
||||
@@ -178,11 +178,11 @@ namespace Barotrauma.Items.Components
|
||||
throwDone = true;
|
||||
IsActive = true;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnSecondaryUse, this, CurrentThrower.ID });
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnSecondaryUse, this, CurrentThrower));
|
||||
}
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
if (!(GameMain.NetworkMember is { IsClient: true }))
|
||||
{
|
||||
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
|
||||
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Networking;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Sounds;
|
||||
@@ -1036,6 +1037,30 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public interface IEventData { }
|
||||
|
||||
public virtual bool ValidateEventData(NetEntityEvent.IData data)
|
||||
=> true;
|
||||
|
||||
protected T ExtractEventData<T>(NetEntityEvent.IData data) where T : IEventData
|
||||
=> TryExtractEventData(data, out T componentData)
|
||||
? componentData
|
||||
: throw new Exception($"Malformed item component state event for {item.Name} " +
|
||||
$"(item ID {item.ID}, component type {GetType().Name}): " +
|
||||
$"could not extract ComponentData of type {typeof(T).Name}");
|
||||
|
||||
protected bool TryExtractEventData<T>(NetEntityEvent.IData data, out T componentData)
|
||||
{
|
||||
componentData = default;
|
||||
if (data is Item.ComponentStateEventData { ComponentData: T nestedData })
|
||||
{
|
||||
componentData = nestedData;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region AI related
|
||||
protected const float AIUpdateInterval = 0.2f;
|
||||
protected float aiUpdateTimer;
|
||||
|
||||
@@ -370,7 +370,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Item GetFocusTarget()
|
||||
{
|
||||
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), "position_out");
|
||||
var positionOut = item.Connections.Find(c => c.Name == "position_out");
|
||||
if (positionOut == null) { return null; }
|
||||
|
||||
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), positionOut);
|
||||
|
||||
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -380,7 +383,16 @@ namespace Barotrauma.Items.Components
|
||||
return item.LastSentSignalRecipients[i].Item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var recipientPanel in item.GetConnectedComponentsRecursive<ConnectionPanel>(positionOut, allowTraversingBackwards: false))
|
||||
{
|
||||
if (recipientPanel.Item.Condition <= 0.0f) { continue; }
|
||||
if (recipientPanel.Item.Prefab.FocusOnSelected)
|
||||
{
|
||||
return recipientPanel.Item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
float condition = deconstructProduct.CopyCondition ?
|
||||
percentageHealth * itemPrefab.Health :
|
||||
percentageHealth * itemPrefab.Health * deconstructProduct.OutConditionMax :
|
||||
itemPrefab.Health * Rand.Range(deconstructProduct.OutConditionMin, deconstructProduct.OutConditionMax);
|
||||
|
||||
if (DeconstructItemsSimultaneously && deconstructProduct.RequiredOtherItem.Length > 0)
|
||||
|
||||
@@ -354,7 +354,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Item containedItem in availableItem.OwnInventory.AllItemsMod)
|
||||
{
|
||||
containedItem.Drop(dropper: null);
|
||||
if (availableItem.GetComponent<ItemContainer>()?.RemoveContainedItemsOnDeconstruct ?? false)
|
||||
{
|
||||
Entity.Spawner.AddItemToRemoveQueue(containedItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
containedItem.Drop(dropper: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components
|
||||
hull.BallastFlora = new BallastFloraBehavior(hull, ballastFloraPrefab, offset, firstGrowth: true);
|
||||
|
||||
#if SERVER
|
||||
hull.BallastFlora.SendNetworkMessage(hull.BallastFlora, BallastFloraBehavior.NetworkHeader.Spawn);
|
||||
hull.BallastFlora.SendNetworkMessage(new BallastFloraBehavior.SpawnEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -287,7 +285,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (autoTemp)
|
||||
{
|
||||
UpdateAutoTemp(10.0f, deltaTime * 2f);
|
||||
UpdateAutoTemp(2.0f, deltaTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +298,14 @@ namespace Barotrauma.Items.Components
|
||||
if (!item.HasTag("reactorfuel")) { continue; }
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
bool isConnectedToFriendlyOutpost = Level.IsLoadedOutpost &&
|
||||
Item.Submarine?.TeamID == CharacterTeamType.Team1 &&
|
||||
Item.Submarine.GetConnectedSubs().Any(s => s.Info.IsOutpost && s.TeamID == CharacterTeamType.FriendlyNPC);
|
||||
|
||||
if (!isConnectedToFriendlyOutpost)
|
||||
{
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
}
|
||||
}
|
||||
fuelLeft += item.ConditionPercentage;
|
||||
}
|
||||
@@ -418,7 +423,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float idealLoad = MaxPowerOutput / minMaxPower.ReactorMaxOutput * loadLeft;
|
||||
float loadAdjust = MathHelper.Clamp((ratio - 0.5f) * 25 + idealLoad - (turbineOutput / 100 * MaxPowerOutput), -MaxPowerOutput / 100, MaxPowerOutput / 100);
|
||||
newLoad = MathHelper.Clamp(loadLeft - (expectedPower + output) + loadAdjust, 0, loadLeft);
|
||||
newLoad = MathHelper.Clamp(loadLeft - (expectedPower - output) + loadAdjust, 0, loadLeft);
|
||||
}
|
||||
|
||||
if (float.IsNegative(newLoad))
|
||||
@@ -498,7 +503,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (temperature > optimalTemperature.Y)
|
||||
{
|
||||
float prevFireTimer = fireTimer;
|
||||
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
|
||||
#if SERVER
|
||||
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item)
|
||||
@@ -506,9 +510,10 @@ namespace Barotrauma.Items.Components
|
||||
GameMain.Server.KarmaManager.OnReactorOverHeating(item, blameOnBroken.Character, deltaTime);
|
||||
}
|
||||
#endif
|
||||
if (fireTimer >= FireDelay && prevFireTimer < fireDelay)
|
||||
if (fireTimer >= FireDelay)
|
||||
{
|
||||
new FireSource(item.WorldPosition);
|
||||
fireTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
|
||||
public void ServerEventRead(IReadMessage msg, Client c)
|
||||
{
|
||||
bool isActive = msg.ReadBoolean();
|
||||
bool directionalPing = useDirectionalPing;
|
||||
@@ -394,7 +394,7 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(currentMode == Mode.Active);
|
||||
if (currentMode == Mode.Active)
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace Barotrauma.Items.Components
|
||||
var dockingConnection = item.Connections.FirstOrDefault(c => c.Name == "toggle_docking");
|
||||
if (dockingConnection != null)
|
||||
{
|
||||
var connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection);
|
||||
var connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection, allowTraversingBackwards: false);
|
||||
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.Info.IsOutpost));
|
||||
}
|
||||
}
|
||||
@@ -271,13 +271,9 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateClientEvent(this);
|
||||
correctionTimer = CorrectionDelay;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
|
||||
networkUpdateTimer = 0.1f;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -11,7 +10,7 @@ namespace Barotrauma.Items.Components
|
||||
//[power/min]
|
||||
private float capacity;
|
||||
|
||||
private float charge;
|
||||
private float charge, prevCharge;
|
||||
|
||||
//how fast the battery can be recharged
|
||||
private float maxRechargeSpeed;
|
||||
@@ -34,7 +33,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return currPowerOutput; }
|
||||
private set
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(value >= 0.0f);
|
||||
System.Diagnostics.Debug.Assert(value >= 0.0f, $"Tried to set PowerContainer's output to a negative value ({value})");
|
||||
currPowerOutput = Math.Max(0, value);
|
||||
}
|
||||
}
|
||||
@@ -140,6 +139,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
IsActive = true;
|
||||
InitProjSpecific();
|
||||
prevCharge = Charge;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
@@ -171,7 +171,7 @@ namespace Barotrauma.Items.Components
|
||||
loadReading = powerOut.Grid.Load;
|
||||
}
|
||||
|
||||
item.SendSignal(((int)Math.Round(-CurrPowerOutput)).ToString(), "power_value_out");
|
||||
item.SendSignal(((int)Math.Round(CurrPowerOutput)).ToString(), "power_value_out");
|
||||
item.SendSignal(((int)Math.Round(loadReading)).ToString(), "load_value_out");
|
||||
item.SendSignal(((int)Math.Round(Charge)).ToString(), "charge");
|
||||
item.SendSignal(((int)Math.Round(Charge / capacity * 100)).ToString(), "charge_%");
|
||||
@@ -194,6 +194,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Condition <= 0.0f) { return 0.0f; }
|
||||
|
||||
float missingCharge = capacity - charge;
|
||||
float targetRechargeSpeed = rechargeSpeed;
|
||||
|
||||
@@ -231,7 +233,7 @@ namespace Barotrauma.Items.Components
|
||||
if (connection == powerOut)
|
||||
{
|
||||
float maxOutput;
|
||||
float chargeRatio = charge / capacity;
|
||||
float chargeRatio = prevCharge / capacity;
|
||||
if (chargeRatio < 0.1f)
|
||||
{
|
||||
maxOutput = Math.Max(chargeRatio * 10.0f, 0.0f) * MaxOutPut;
|
||||
@@ -242,7 +244,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
//Limit max power out to not exceed the charge of the container
|
||||
maxOutput = Math.Min(maxOutput, charge * 60 / UpdateInterval);
|
||||
maxOutput = Math.Min(maxOutput, prevCharge * 60 / UpdateInterval);
|
||||
return new PowerRange(0.0f, maxOutput);
|
||||
}
|
||||
|
||||
@@ -261,18 +263,11 @@ namespace Barotrauma.Items.Components
|
||||
/// <returns></returns>
|
||||
public override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
|
||||
{
|
||||
if (connection == powerOut)
|
||||
//Only power out connection can provide power and Max poweroutput can't be negative
|
||||
if (connection == powerOut && minMaxPower.Max > 0)
|
||||
{
|
||||
//Calculate the max power the container can output
|
||||
float maxPowerOutput = MaxOutPut;
|
||||
float chargeRatio = charge / capacity;
|
||||
if (chargeRatio < 0.1f)
|
||||
{
|
||||
maxPowerOutput *= Math.Max(chargeRatio * 10.0f, 0.0f);
|
||||
}
|
||||
|
||||
//Set power output based on the relative max power output capabilities and load demand
|
||||
CurrPowerOutput = MathHelper.Clamp((load - power) / minMaxPower.Max, 0, 1) * maxPowerOutput;
|
||||
CurrPowerOutput = MathHelper.Clamp((load - power) / minMaxPower.Max, 0, 1) * MinMaxPowerOut(connection, load).Max;
|
||||
return CurrPowerOutput;
|
||||
}
|
||||
return 0.0f;
|
||||
@@ -292,6 +287,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//Decrease charge based on how much power is leaving the device
|
||||
Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, Capacity);
|
||||
prevCharge = Charge;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,23 +176,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
RefreshConnections();
|
||||
|
||||
float powerReadingOut = 0;
|
||||
float loadReadingOut = ExtraLoad;
|
||||
if (powerLoad < 0)
|
||||
{
|
||||
powerReadingOut = -powerLoad;
|
||||
loadReadingOut = 0;
|
||||
}
|
||||
|
||||
if (powerOut != null && powerOut.Grid != null)
|
||||
{
|
||||
powerReadingOut = powerOut.Grid.Power;
|
||||
loadReadingOut = powerOut.Grid.Load;
|
||||
}
|
||||
|
||||
item.SendSignal(((int)Math.Round(powerReadingOut)).ToString(), "power_value_out");
|
||||
item.SendSignal(((int)Math.Round(loadReadingOut)).ToString(), "load_value_out");
|
||||
|
||||
if (Timing.TotalTime > extraLoadSetTime + 1.0)
|
||||
{
|
||||
//Decay the extra load to 0 from either positive or negative
|
||||
@@ -216,22 +199,36 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
//if the item can't be fixed, don't allow it to break
|
||||
if (!item.Repairables.Any() || !CanBeOverloaded) { return; }
|
||||
|
||||
if (prevSentPowerValue != (int)-CurrPowerConsumption || powerSignal == null)
|
||||
float powerReadingOut = 0;
|
||||
float loadReadingOut = ExtraLoad;
|
||||
if (powerLoad < 0)
|
||||
{
|
||||
prevSentPowerValue = (int)Math.Round(-CurrPowerConsumption);
|
||||
powerReadingOut = -powerLoad;
|
||||
loadReadingOut = 0;
|
||||
}
|
||||
|
||||
if (powerOut != null && powerOut.Grid != null)
|
||||
{
|
||||
powerReadingOut = powerOut.Grid.Power;
|
||||
loadReadingOut = powerOut.Grid.Load;
|
||||
}
|
||||
|
||||
if (prevSentPowerValue != (int)powerReadingOut || powerSignal == null)
|
||||
{
|
||||
prevSentPowerValue = (int)Math.Round(powerReadingOut);
|
||||
powerSignal = prevSentPowerValue.ToString();
|
||||
}
|
||||
if (prevSentLoadValue != (int)powerLoad || loadSignal == null)
|
||||
if (prevSentLoadValue != (int)loadReadingOut || loadSignal == null)
|
||||
{
|
||||
prevSentLoadValue = (int)Math.Round(powerLoad);
|
||||
prevSentLoadValue = (int)Math.Round(loadReadingOut);
|
||||
loadSignal = prevSentLoadValue.ToString();
|
||||
}
|
||||
item.SendSignal(powerSignal, "power_value_out");
|
||||
item.SendSignal(loadSignal, "load_value_out");
|
||||
|
||||
//if the item can't be fixed, don't allow it to break
|
||||
if (!item.Repairables.Any() || !CanBeOverloaded) { return; }
|
||||
|
||||
float maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
|
||||
|
||||
Overload = Voltage > maxOverVoltage;
|
||||
|
||||
@@ -187,14 +187,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected void UpdateOnActiveEffects(float deltaTime)
|
||||
{
|
||||
if (currPowerConsumption <= 0.0f)
|
||||
if (currPowerConsumption <= 0.0f && PowerConsumption <= 0.0f)
|
||||
{
|
||||
//if the item consumes no power, ignore the voltage requirement and
|
||||
//apply OnActive statuseffects as long as this component is active
|
||||
if (PowerConsumption <= 0.0f)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
}
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -216,6 +213,11 @@ namespace Barotrauma.Items.Components
|
||||
powerOnSoundPlayed = false;
|
||||
}
|
||||
#endif
|
||||
if (powerIn == null)
|
||||
{
|
||||
//power down the device here if it has no power connection (= receives power from contained battery cells instead of the "normal" power logic)
|
||||
Voltage -= deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -238,7 +240,11 @@ namespace Barotrauma.Items.Components
|
||||
else if (c.Name == "power_out")
|
||||
{
|
||||
powerOut = c;
|
||||
powerOut.Priority = Priority;
|
||||
// Connection takes the lowest priority
|
||||
if (Priority > powerOut.Priority)
|
||||
{
|
||||
powerOut.Priority = Priority;
|
||||
}
|
||||
}
|
||||
else if (c.Name == "power")
|
||||
{
|
||||
@@ -258,7 +264,11 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
}
|
||||
powerOut = c;
|
||||
powerOut.Priority = Priority;
|
||||
// Connection takes the lowest priority
|
||||
if (Priority > powerOut.Priority)
|
||||
{
|
||||
powerOut.Priority = Priority;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -591,7 +601,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Connection con in grid.Connections)
|
||||
{
|
||||
Powered device = con.Item.GetComponent<Powered>();
|
||||
device.GridResolved(con);
|
||||
device?.GridResolved(con);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +196,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool FriendlyFire
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private float deactivationTimer;
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.No)]
|
||||
@@ -309,7 +316,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
#if SERVER
|
||||
launchRot = rotation;
|
||||
Item.CreateServerEvent(this, new object[] { true }); //true = indicate that this is a launch event
|
||||
Item.CreateServerEvent(this, new EventData(launch: true));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -673,7 +680,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Unstick();
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
item.CreateServerEvent(this, new EventData(launch: false));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -697,9 +704,9 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
if (ShouldIgnoreSubmarineCollision(target, contact))
|
||||
if (target.Body.UserData is Submarine)
|
||||
{
|
||||
return false;
|
||||
if (ShouldIgnoreSubmarineCollision(ref target, contact)) { return false; }
|
||||
}
|
||||
else if (target.Body.UserData is Limb limb)
|
||||
{
|
||||
@@ -709,6 +716,10 @@ namespace Barotrauma.Items.Components
|
||||
limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition);
|
||||
return false;
|
||||
}
|
||||
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (target.Body.UserData is Item item)
|
||||
{
|
||||
@@ -893,8 +904,8 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, actionType, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, targetLimb.character, targetLimb, null, item.WorldPosition));
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, null, item.WorldPosition));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -905,8 +916,8 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, actionType, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition });
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -952,7 +963,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
item.CreateServerEvent(this, new EventData(launch: false));
|
||||
}
|
||||
#endif
|
||||
item.body.LinearVelocity *= speedMultiplier;
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, CurrentFixer);
|
||||
#if SERVER
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, CurrentFixer.ID });
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, this, CurrentFixer));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -256,10 +256,10 @@ namespace Barotrauma.Items.Components
|
||||
if (!CheckCharacterSuccess(character, bestRepairItem))
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction);
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, this, character));
|
||||
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(bestRepairItem, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, h, character.ID });
|
||||
GameMain.Server?.CreateEntityEvent(bestRepairItem, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, h, character));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
+6
-2
@@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
protected readonly Character[] signalSender = new Character[2];
|
||||
|
||||
[Serialize(999999.0f, IsPropertySaveable.Yes, description: "The output of the item is restricted below this value.", alwaysUseInstanceValues: true),
|
||||
InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f)]
|
||||
public float ClampMax
|
||||
@@ -33,7 +35,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
[InGameEditable(DecimalCount = 2),
|
||||
Serialize(0.0f, IsPropertySaveable.Yes, description: "The item must have received signals to both inputs within this timeframe to output the result." +
|
||||
" If set to 0, the inputs must be received at the same time.", alwaysUseInstanceValues: true)]
|
||||
" If set to 0, the inputs must be received at the same time.", alwaysUseInstanceValues: true, translationTextTag: "sp.")]
|
||||
public float TimeFrame
|
||||
{
|
||||
get { return timeFrame; }
|
||||
@@ -71,7 +73,7 @@ namespace Barotrauma.Items.Components
|
||||
float output = Calculate(receivedSignal[0], receivedSignal[1]);
|
||||
if (MathUtils.IsValid(output))
|
||||
{
|
||||
item.SendSignal(MathHelper.Clamp(output, ClampMin, ClampMax).ToString("G", CultureInfo.InvariantCulture), "signal_out");
|
||||
item.SendSignal(new Signal(MathHelper.Clamp(output, ClampMin, ClampMax).ToString("G", CultureInfo.InvariantCulture), sender: signalSender[0] ?? signalSender[1]), "signal_out");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +85,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
case "signal_in1":
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
|
||||
signalSender[0] = signal.sender;
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
IsActive = true;
|
||||
break;
|
||||
case "signal_in2":
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
|
||||
signalSender[1] = signal.sender;
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
IsActive = true;
|
||||
break;
|
||||
|
||||
@@ -109,10 +109,23 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Write(IWriteMessage msg, object[] extraData)
|
||||
private readonly struct EventData : IEventData
|
||||
{
|
||||
if (extraData == null || extraData.Length < 3) { return; }
|
||||
msg.WriteRangedInteger((int)extraData[2], 0, Signals.Length - 1);
|
||||
public readonly int SignalIndex;
|
||||
|
||||
public EventData(int signalIndex)
|
||||
{
|
||||
SignalIndex = signalIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ValidateEventData(NetEntityEvent.IData data)
|
||||
=> TryExtractEventData<EventData>(data, out _);
|
||||
|
||||
private void Write(IWriteMessage msg, NetEntityEvent.IData extraData)
|
||||
{
|
||||
var eventData = ExtractEventData<EventData>(extraData);
|
||||
msg.WriteRangedInteger(eventData.SignalIndex, 0, Signals.Length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,7 +400,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
#if CLIENT
|
||||
TriggerRewiringSound();
|
||||
|
||||
@@ -8,6 +8,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable
|
||||
{
|
||||
private readonly struct EventData : IEventData
|
||||
{
|
||||
public readonly CustomInterfaceElement BtnElement;
|
||||
|
||||
public EventData(CustomInterfaceElement btnElement)
|
||||
{
|
||||
BtnElement = btnElement;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomInterfaceElement : ISerializableEntity
|
||||
{
|
||||
public bool ContinuousSignal;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class NotComponent : ItemComponent
|
||||
{
|
||||
private bool signalReceived;
|
||||
|
||||
private bool continuousOutput;
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "When enabled, the component continuously outputs \"1\" when it's not receiving a signal.", alwaysUseInstanceValues: true)]
|
||||
[InGameEditable, Serialize(false, IsPropertySaveable.Yes, description: "When enabled, the component continuously outputs \"1\" when it's not receiving a signal.", alwaysUseInstanceValues: true)]
|
||||
public bool ContinuousOutput
|
||||
{
|
||||
get { return continuousOutput; }
|
||||
|
||||
@@ -372,12 +372,12 @@ namespace Barotrauma.Items.Components
|
||||
IsOn = on;
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(isOn);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float _)
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
SetState(msg.ReadBoolean(), true);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (FireSource fireSource in hull.FireSources)
|
||||
{
|
||||
if (fireSource.IsInDamageRange(item.WorldPosition, fireSource.DamageRange * 2.0f)) { return true; }
|
||||
if (fireSource.IsInDamageRange(item.WorldPosition, Math.Max(fireSource.DamageRange * 2.0f, 500.0f))) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
|
||||
+4
-1
@@ -20,6 +20,8 @@ namespace Barotrauma.Items.Components
|
||||
private readonly float[] receivedSignal = new float[2];
|
||||
private readonly float[] timeSinceReceived = new float[2];
|
||||
|
||||
protected Character signalSender;
|
||||
|
||||
[Serialize(FunctionType.Sin, IsPropertySaveable.No, description: "Which kind of function to run the input through.", alwaysUseInstanceValues: true)]
|
||||
public FunctionType Function
|
||||
{
|
||||
@@ -56,7 +58,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float angle = (float)Math.Atan2(receivedSignal[1], receivedSignal[0]);
|
||||
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
|
||||
item.SendSignal(angle.ToString("G", CultureInfo.InvariantCulture), "signal_out");
|
||||
item.SendSignal(new Signal(angle.ToString("G", CultureInfo.InvariantCulture), sender: signalSender), "signal_out");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +67,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float value);
|
||||
bool sendOutputImmediately = true;
|
||||
signalSender = signal.sender;
|
||||
switch (Function)
|
||||
{
|
||||
case FunctionType.Sin:
|
||||
|
||||
@@ -455,12 +455,7 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
GameMain.Client.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ComponentState,
|
||||
item.GetComponentIndex(this),
|
||||
nodes.Count
|
||||
});
|
||||
item.CreateClientEvent(this, new ClientEventData(nodes.Count));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -482,12 +477,7 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
GameMain.Client.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ComponentState,
|
||||
item.GetComponentIndex(this),
|
||||
nodes.Count
|
||||
});
|
||||
item.CreateClientEvent(this, new ClientEventData(nodes.Count));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -49,8 +49,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private ChargingState currentChargingState;
|
||||
|
||||
private float currentBarrelSpin = 0f;
|
||||
|
||||
private readonly List<Item> activeProjectiles = new List<Item>();
|
||||
public IEnumerable<Item> ActiveProjectiles => activeProjectiles;
|
||||
|
||||
@@ -330,10 +328,10 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
base.OnMapLoaded();
|
||||
FindLightComponent();
|
||||
if (loadedRotationLimits.HasValue) { RotationLimits = loadedRotationLimits.Value; }
|
||||
if (loadedBaseRotation.HasValue) { BaseRotation = loadedBaseRotation.Value; }
|
||||
targetRotation = rotation;
|
||||
FindLightComponent();
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
|
||||
@@ -736,6 +734,16 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private readonly struct EventData : IEventData
|
||||
{
|
||||
public readonly Item Projectile;
|
||||
|
||||
public EventData(Item projectile)
|
||||
{
|
||||
Projectile = projectile;
|
||||
}
|
||||
}
|
||||
|
||||
private void Launch(Item projectile, Character user = null, float? launchRotation = null, float tinkeringStrength = 0f)
|
||||
{
|
||||
reload = reloadTime;
|
||||
@@ -749,7 +757,7 @@ namespace Barotrauma.Items.Components
|
||||
if (projectile != null)
|
||||
{
|
||||
activeProjectiles.Add(projectile);
|
||||
projectile.Drop(null);
|
||||
projectile.Drop(null, setTransform: false);
|
||||
if (projectile.body != null)
|
||||
{
|
||||
projectile.body.Dir = 1.0f;
|
||||
@@ -787,12 +795,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (projectile.Container != null) { projectile.Container.RemoveContained(projectile); }
|
||||
}
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), projectile });
|
||||
projectile.Container?.RemoveContained(projectile);
|
||||
}
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this, new EventData(projectile));
|
||||
#endif
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, user: user);
|
||||
LaunchProjSpecific();
|
||||
@@ -1630,11 +1637,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
if (extraData.Length > 2)
|
||||
if (TryExtractEventData(extraData, out EventData eventData))
|
||||
{
|
||||
msg.Write(!(extraData[2] is Item item) ? ushort.MaxValue : item.ID);
|
||||
msg.Write(eventData.Projectile.ID);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -540,16 +540,16 @@ namespace Barotrauma.Items.Components
|
||||
Variant = loadedVariant;
|
||||
}
|
||||
}
|
||||
public override void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write((byte)Variant);
|
||||
base.ServerWrite(msg, c, extraData);
|
||||
base.ServerEventWrite(msg, c, extraData);
|
||||
}
|
||||
|
||||
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
public override void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
Variant = (int)msg.ReadByte();
|
||||
base.ClientRead(type, msg, sendingTime);
|
||||
base.ClientEventRead(msg, sendingTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class Inventory : IServerSerializable, IClientSerializable
|
||||
{
|
||||
public const int MaxStackSize = 32;
|
||||
public const int MaxStackSize = (1 << 6) - 1; //the max value that will fit in 6 bits, i.e 63
|
||||
|
||||
public class ItemSlot
|
||||
{
|
||||
@@ -18,15 +18,7 @@ namespace Barotrauma
|
||||
|
||||
public bool HideIfEmpty;
|
||||
|
||||
public IEnumerable<Item> Items
|
||||
{
|
||||
get { return items; }
|
||||
}
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get { return items.Count; }
|
||||
}
|
||||
public IReadOnlyList<Item> Items => items;
|
||||
|
||||
public bool CanBePut(Item item, bool ignoreCondition = false)
|
||||
{
|
||||
@@ -631,7 +623,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!slots[i].Any()) { return false; }
|
||||
var item = slots[i].FirstOrDefault();
|
||||
if (slots[i].ItemCount < item.Prefab.MaxStackSize) { return false; }
|
||||
if (slots[i].Items.Count < item.Prefab.MaxStackSize) { return false; }
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -842,29 +834,30 @@ namespace Barotrauma
|
||||
|
||||
public virtual void CreateNetworkEvent()
|
||||
{
|
||||
if (GameMain.NetworkMember != null)
|
||||
if (GameMain.NetworkMember == null) { return; }
|
||||
if (GameMain.NetworkMember.IsClient) { syncItemsDelay = 1.0f; }
|
||||
|
||||
if (Owner is Character character)
|
||||
{
|
||||
if (GameMain.NetworkMember.IsClient) { syncItemsDelay = 1.0f; }
|
||||
GameMain.NetworkMember.CreateEntityEvent(Owner as INetSerializable, new object[] { NetEntityEvent.Type.InventoryState });
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.InventoryStateEventData());
|
||||
}
|
||||
else if (Owner is Item item)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new Item.InventoryStateEventData());
|
||||
}
|
||||
}
|
||||
|
||||
public Item FindItem(Func<Item, bool> predicate, bool recursive)
|
||||
{
|
||||
Item match = AllItems.FirstOrDefault(i => predicate(i));
|
||||
Item match = AllItems.FirstOrDefault(predicate);
|
||||
if (match == null && recursive)
|
||||
{
|
||||
foreach (var item in AllItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (item.OwnInventory != null)
|
||||
{
|
||||
match = item.OwnInventory.FindItem(predicate, recursive: true);
|
||||
if (match != null)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
}
|
||||
if (item?.OwnInventory == null) { continue; }
|
||||
|
||||
match = item.OwnInventory.FindItem(predicate, recursive: true);
|
||||
if (match != null) { return match; }
|
||||
}
|
||||
}
|
||||
return match;
|
||||
@@ -946,16 +939,31 @@ namespace Barotrauma
|
||||
slots[index].RemoveItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void SharedWrite(IWriteMessage msg, object[] extraData = null)
|
||||
public void SharedRead(IReadMessage msg, out List<ushort>[] newItemIds)
|
||||
{
|
||||
byte slotCount = msg.ReadByte();
|
||||
newItemIds = new List<ushort>[slotCount];
|
||||
for (int i = 0; i < slotCount; i++)
|
||||
{
|
||||
newItemIds[i] = new List<ushort>();
|
||||
int itemCount = msg.ReadRangedInteger(0, MaxStackSize);
|
||||
for (int j = 0; j < itemCount; j++)
|
||||
{
|
||||
newItemIds[i].Add(msg.ReadUInt16());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SharedWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write((byte)capacity);
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
msg.WriteRangedInteger(slots[i].ItemCount, 0, MaxStackSize);
|
||||
foreach (Item item in slots[i].Items)
|
||||
msg.WriteRangedInteger(slots[i].Items.Count, 0, MaxStackSize);
|
||||
for (int j = 0; j < Math.Min(slots[i].Items.Count, MaxStackSize); j++)
|
||||
{
|
||||
msg.Write((ushort)(item == null ? 0 : item.ID));
|
||||
var item = slots[i].Items[j];
|
||||
msg.Write(item?.ID ?? (ushort)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Item : MapEntity, IDamageable, IIgnorable, ISerializableEntity, IServerSerializable, IClientSerializable
|
||||
partial class Item : MapEntity, IDamageable, IIgnorable, ISerializableEntity, IServerPositionSync, IClientSerializable
|
||||
{
|
||||
public static List<Item> ItemList = new List<Item>();
|
||||
public new ItemPrefab Prefab => base.Prefab as ItemPrefab;
|
||||
@@ -573,7 +573,7 @@ namespace Barotrauma
|
||||
if (connections == null) { return; }
|
||||
foreach (Connection c in connections.Values)
|
||||
{
|
||||
if (c.IsPower && c.Grid != null)
|
||||
if (c.IsPower)
|
||||
{
|
||||
Powered.ChangedConnections.Add(c);
|
||||
foreach (Connection conn in c.Recipients)
|
||||
@@ -827,6 +827,23 @@ namespace Barotrauma
|
||||
public bool IgnoreByAI(Character character) => HasTag("ignorebyai") || OrderedToBeIgnored && character.IsOnPlayerTeam;
|
||||
public bool OrderedToBeIgnored { get; set; }
|
||||
|
||||
public bool HasBallastFloraInHull
|
||||
{
|
||||
get
|
||||
{
|
||||
return CurrentHull?.BallastFlora != null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsClaimedByBallastFlora
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentHull?.BallastFlora == null) { return false; }
|
||||
return CurrentHull.BallastFlora.ClaimedTargets.Contains(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID, bool callOnItemLoaded = true)
|
||||
: this(new Rectangle(
|
||||
(int)(position.X - itemPrefab.Sprite.size.X / 2 * itemPrefab.Scale),
|
||||
@@ -1417,6 +1434,7 @@ namespace Barotrauma
|
||||
|
||||
public bool HasAccess(Character character)
|
||||
{
|
||||
if (HiddenInGame) { return false; }
|
||||
if (character.IsBot && IgnoreByAI(character)) { return false; }
|
||||
if (!IsInteractable(character)) { return false; }
|
||||
var itemContainer = GetComponent<ItemContainer>();
|
||||
@@ -1656,14 +1674,13 @@ namespace Barotrauma
|
||||
|
||||
public void SendPendingNetworkUpdates()
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsServer) { return; }
|
||||
if (conditionUpdatePending)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });
|
||||
lastSentCondition = condition;
|
||||
sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval;
|
||||
conditionUpdatePending = false;
|
||||
}
|
||||
if (!(GameMain.NetworkMember is { IsServer: true })) { return; }
|
||||
if (!conditionUpdatePending) { return; }
|
||||
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new StatusEventData());
|
||||
lastSentCondition = condition;
|
||||
sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval;
|
||||
conditionUpdatePending = false;
|
||||
}
|
||||
|
||||
private bool isActive = true;
|
||||
@@ -1919,20 +1936,19 @@ namespace Barotrauma
|
||||
private void HandleCollision(float impact)
|
||||
{
|
||||
OnCollisionProjSpecific(impact);
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (ImpactTolerance > 0.0f && condition > 0.0f && Math.Abs(impact) > ImpactTolerance)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f);
|
||||
#if SERVER
|
||||
GameMain.Server?.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact });
|
||||
#endif
|
||||
}
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
|
||||
foreach (Item contained in ContainedItems)
|
||||
{
|
||||
if (contained.body != null) { contained.HandleCollision(impact); }
|
||||
}
|
||||
if (ImpactTolerance > 0.0f && condition > 0.0f && Math.Abs(impact) > ImpactTolerance)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f);
|
||||
#if SERVER
|
||||
GameMain.Server?.CreateEntityEvent(this, new ApplyStatusEffectEventData(ActionType.OnImpact));
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (Item contained in ContainedItems)
|
||||
{
|
||||
if (contained.body != null) { contained.HandleCollision(impact); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1995,14 +2011,14 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
|
||||
/// </summary>
|
||||
public List<T> GetConnectedComponents<T>(bool recursive = false) where T : ItemComponent
|
||||
public List<T> GetConnectedComponents<T>(bool recursive = false, bool allowTraversingBackwards = true) where T : ItemComponent
|
||||
{
|
||||
List<T> connectedComponents = new List<T>();
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
HashSet<Connection> alreadySearched = new HashSet<Connection>();
|
||||
GetConnectedComponentsRecursive(alreadySearched, connectedComponents);
|
||||
GetConnectedComponentsRecursive(alreadySearched, connectedComponents, allowTraversingBackwards: allowTraversingBackwards);
|
||||
return connectedComponents;
|
||||
}
|
||||
|
||||
@@ -2025,7 +2041,7 @@ namespace Barotrauma
|
||||
return connectedComponents;
|
||||
}
|
||||
|
||||
private void GetConnectedComponentsRecursive<T>(HashSet<Connection> alreadySearched, List<T> connectedComponents, bool ignoreInactiveRelays = false) where T : ItemComponent
|
||||
private void GetConnectedComponentsRecursive<T>(HashSet<Connection> alreadySearched, List<T> connectedComponents, bool ignoreInactiveRelays = false, bool allowTraversingBackwards = true) where T : ItemComponent
|
||||
{
|
||||
ConnectionPanel connectionPanel = GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel == null) { return; }
|
||||
@@ -2034,18 +2050,18 @@ namespace Barotrauma
|
||||
{
|
||||
if (alreadySearched.Contains(c)) { continue; }
|
||||
alreadySearched.Add(c);
|
||||
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents, ignoreInactiveRelays);
|
||||
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
|
||||
/// </summary>
|
||||
public List<T> GetConnectedComponentsRecursive<T>(Connection c, bool ignoreInactiveRelays = false) where T : ItemComponent
|
||||
public List<T> GetConnectedComponentsRecursive<T>(Connection c, bool ignoreInactiveRelays = false, bool allowTraversingBackwards = true) where T : ItemComponent
|
||||
{
|
||||
List<T> connectedComponents = new List<T>();
|
||||
HashSet<Connection> alreadySearched = new HashSet<Connection>();
|
||||
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents, ignoreInactiveRelays);
|
||||
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards);
|
||||
|
||||
return connectedComponents;
|
||||
}
|
||||
@@ -2062,7 +2078,7 @@ namespace Barotrauma
|
||||
("signal_in2".ToIdentifier(), "signal_out".ToIdentifier())
|
||||
}.ToImmutableArray();
|
||||
|
||||
private void GetConnectedComponentsRecursive<T>(Connection c, HashSet<Connection> alreadySearched, List<T> connectedComponents, bool ignoreInactiveRelays) where T : ItemComponent
|
||||
private void GetConnectedComponentsRecursive<T>(Connection c, HashSet<Connection> alreadySearched, List<T> connectedComponents, bool ignoreInactiveRelays, bool allowTraversingBackwards = true) where T : ItemComponent
|
||||
{
|
||||
alreadySearched.Add(c);
|
||||
|
||||
@@ -2087,12 +2103,12 @@ namespace Barotrauma
|
||||
foreach (Connection wifiOutput in receiverConnections)
|
||||
{
|
||||
if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; }
|
||||
GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents, ignoreInactiveRelays);
|
||||
GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents, ignoreInactiveRelays);
|
||||
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards);
|
||||
}
|
||||
|
||||
if (ignoreInactiveRelays)
|
||||
@@ -2111,12 +2127,12 @@ namespace Barotrauma
|
||||
if (pairedConnection != null)
|
||||
{
|
||||
if (alreadySearched.Contains(pairedConnection)) { return; }
|
||||
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents, ignoreInactiveRelays);
|
||||
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards);
|
||||
}
|
||||
}
|
||||
}
|
||||
searchFromAToB(input, output);
|
||||
searchFromAToB(output, input);
|
||||
if (allowTraversingBackwards) { searchFromAToB(output, input); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2490,7 +2506,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Treatment, character.ID, targetLimb });
|
||||
GameMain.Client.CreateEntityEvent(this, new TreatmentEventData(character, targetLimb));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -2523,12 +2539,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ApplyStatusEffect, actionType, ic, character.ID, targetLimb
|
||||
});
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ApplyStatusEffectEventData(
|
||||
actionType, ic, character, targetLimb));
|
||||
}
|
||||
|
||||
if (ic.DeleteOnUse) { remove = true; }
|
||||
@@ -2553,12 +2567,18 @@ namespace Barotrauma
|
||||
if (ic.Combine(item, user)) { isCombined = true; }
|
||||
}
|
||||
#if CLIENT
|
||||
if (isCombined) { GameMain.Client?.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Combine, item.ID }); }
|
||||
if (isCombined) { GameMain.Client?.CreateEntityEvent(this, new CombineEventData(item)); }
|
||||
#endif
|
||||
return isCombined;
|
||||
}
|
||||
|
||||
public void Drop(Character dropper, bool createNetworkEvent = true)
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="dropper">Character who dropped the item</param>
|
||||
/// <param name="createNetworkEvent">Should clients be notified of the item being dropped</param>
|
||||
/// <param name="setTransform">Should the transform of the physics body be updated. Only disable this if you're moving the item somewhere else / calling SetTransform manually immediately after dropping!</param>
|
||||
public void Drop(Character dropper, bool createNetworkEvent = true, bool setTransform = true)
|
||||
{
|
||||
if (createNetworkEvent)
|
||||
{
|
||||
@@ -2585,7 +2605,7 @@ namespace Barotrauma
|
||||
"Failed to drop the item \"" + Name + "\" (body has been removed"
|
||||
+ (Removed ? ", item has been removed)" : ")"));
|
||||
}
|
||||
else
|
||||
else if (setTransform)
|
||||
{
|
||||
body.SetTransform(dropper.SimPosition, 0.0f);
|
||||
}
|
||||
@@ -2596,7 +2616,10 @@ namespace Barotrauma
|
||||
|
||||
if (Container != null)
|
||||
{
|
||||
SetTransform(Container.SimPosition, 0.0f);
|
||||
if (setTransform)
|
||||
{
|
||||
SetTransform(Container.SimPosition, 0.0f);
|
||||
}
|
||||
Container.RemoveContained(this);
|
||||
Container = null;
|
||||
}
|
||||
@@ -2646,12 +2669,12 @@ namespace Barotrauma
|
||||
return allProperties;
|
||||
}
|
||||
|
||||
private void WritePropertyChange(IWriteMessage msg, object[] extraData, bool inGameEditableOnly)
|
||||
private void WritePropertyChange(IWriteMessage msg, ChangePropertyEventData extraData, bool inGameEditableOnly)
|
||||
{
|
||||
//ignoreConditions: true = include all ConditionallyEditable properties at this point,
|
||||
//to ensure client/server doesn't get any properties mixed up if there's some conditions that can vary between the server and the clients
|
||||
var allProperties = inGameEditableOnly ? GetInGameEditableProperties(ignoreConditions: true) : GetProperties<Editable>();
|
||||
SerializableProperty property = extraData[1] as SerializableProperty;
|
||||
SerializableProperty property = extraData.SerializableProperty;
|
||||
if (property != null)
|
||||
{
|
||||
var propertyOwner = allProperties.Find(p => p.Second == property);
|
||||
@@ -2910,9 +2933,9 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ChangeProperty, property });
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ChangePropertyEventData(property));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2980,7 +3003,7 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (createNetworkEvent)
|
||||
{
|
||||
Spawner.CreateNetworkEvent(item, remove: false);
|
||||
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3016,7 +3039,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!(property.GetValue(item)?.Equals(prevValue) ?? true))
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ChangeProperty, property });
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new ChangePropertyEventData(property));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Item
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
ComponentState = 0,
|
||||
InventoryState = 1,
|
||||
Treatment = 2,
|
||||
ChangeProperty = 3,
|
||||
Combine = 4,
|
||||
Status = 5,
|
||||
AssignCampaignInteraction = 6,
|
||||
ApplyStatusEffect = 7,
|
||||
Upgrade = 8,
|
||||
|
||||
MinValue = 0,
|
||||
MaxValue = 6
|
||||
}
|
||||
|
||||
public interface IEventData : NetEntityEvent.IData
|
||||
{
|
||||
public EventType EventType { get; }
|
||||
}
|
||||
|
||||
public struct ComponentStateEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.ComponentState;
|
||||
public readonly ItemComponent Component;
|
||||
public readonly ItemComponent.IEventData ComponentData;
|
||||
|
||||
public ComponentStateEventData(ItemComponent component, ItemComponent.IEventData componentData)
|
||||
{
|
||||
Component = component;
|
||||
ComponentData = componentData;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct InventoryStateEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.InventoryState;
|
||||
public readonly ItemContainer Component;
|
||||
|
||||
public InventoryStateEventData(ItemContainer component)
|
||||
{
|
||||
Component = component;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ChangePropertyEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.ChangeProperty;
|
||||
public readonly SerializableProperty SerializableProperty;
|
||||
|
||||
public ChangePropertyEventData(SerializableProperty serializableProperty)
|
||||
{
|
||||
SerializableProperty = serializableProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct StatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
}
|
||||
|
||||
private readonly struct AssignCampaignInteractionEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.AssignCampaignInteraction;
|
||||
}
|
||||
|
||||
public readonly struct ApplyStatusEffectEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.ApplyStatusEffect;
|
||||
public readonly ActionType ActionType;
|
||||
public readonly ItemComponent TargetItemComponent;
|
||||
public readonly Character TargetCharacter;
|
||||
public readonly Limb TargetLimb;
|
||||
public readonly Entity UseTarget;
|
||||
public readonly Vector2? WorldPosition;
|
||||
|
||||
public ApplyStatusEffectEventData(
|
||||
ActionType actionType,
|
||||
ItemComponent targetItemComponent = null,
|
||||
Character targetCharacter = null,
|
||||
Limb targetLimb = null,
|
||||
Entity useTarget = null,
|
||||
Vector2? worldPosition = null)
|
||||
{
|
||||
ActionType = actionType;
|
||||
TargetItemComponent = targetItemComponent;
|
||||
TargetCharacter = targetCharacter;
|
||||
TargetLimb = targetLimb;
|
||||
UseTarget = useTarget;
|
||||
WorldPosition = worldPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct UpgradeEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Upgrade;
|
||||
public readonly Upgrade Upgrade;
|
||||
|
||||
public UpgradeEventData(Upgrade upgrade)
|
||||
{
|
||||
Upgrade = upgrade;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,14 +48,14 @@ namespace Barotrauma
|
||||
if (ItemOwnsSelf(item)) { return false; }
|
||||
if (i < 0 || i >= slots.Length) { return false; }
|
||||
if (!container.CanBeContained(item, i)) { return false; }
|
||||
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i);
|
||||
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].Items.Count < container.GetMaxStackSize(i);
|
||||
}
|
||||
|
||||
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null)
|
||||
{
|
||||
if (i < 0 || i >= slots.Length) { return false; }
|
||||
if (!container.CanBeContained(itemPrefab, i)) { return false; }
|
||||
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].ItemCount < container.GetMaxStackSize(i);
|
||||
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].Items.Count < container.GetMaxStackSize(i);
|
||||
}
|
||||
|
||||
public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition)
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!slots[i].Any()) { return false; }
|
||||
var item = slots[i].FirstOrDefault();
|
||||
if (slots[i].ItemCount < Math.Min(item.Prefab.MaxStackSize, container.GetMaxStackSize(i))) { return false; }
|
||||
if (slots[i].Items.Count < Math.Min(item.Prefab.MaxStackSize, container.GetMaxStackSize(i))) { return false; }
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -145,8 +145,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
int componentIndex = container.Item.GetComponentIndex(container);
|
||||
if (componentIndex == -1)
|
||||
if (!container.Item.Components.Contains(container))
|
||||
{
|
||||
DebugConsole.Log("Creating a network event for the item \"" + container.Item + "\" failed, ItemContainer not found in components");
|
||||
return;
|
||||
@@ -155,7 +154,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
if (GameMain.NetworkMember.IsClient) { syncItemsDelay = 1.0f; }
|
||||
GameMain.NetworkMember.CreateEntityEvent(Owner as INetSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex });
|
||||
GameMain.NetworkMember.CreateEntityEvent(Owner as INetSerializable, new Item.InventoryStateEventData(container));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,6 @@ namespace Barotrauma
|
||||
OutConditionMax = element.GetAttributeFloat("outconditionmax", element.GetAttributeFloat("outcondition", 1.0f));
|
||||
CopyCondition = element.GetAttributeBool("copycondition", false);
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
if (element.Attribute("copycondition") != null && element.Attribute("outcondition") != null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Invalid deconstruction output in \"{parentDebugName}\": the output item \"{ItemIdentifier}\" has the out condition set, but is also set to copy the condition of the deconstructed item. Ignoring the out condition.");
|
||||
}
|
||||
RequiredDeconstructor = element.GetAttributeStringArray("requireddeconstructor",
|
||||
element.Parent?.GetAttributeStringArray("requireddeconstructor", new string[0]) ?? new string[0]);
|
||||
RequiredOtherItem = element.GetAttributeStringArray("requiredotheritem", new string[0]);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user