(3a5d98b) v0.9.6.0

This commit is contained in:
Regalis
2019-12-17 14:38:24 +01:00
parent 5c95c53118
commit a3569b8bf0
95 changed files with 1579 additions and 728 deletions
@@ -283,6 +283,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\MTRandom.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\Rand.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\SaveUtil.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\TaskPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\ToolBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\UpdaterUtil.cs" />
</ItemGroup>
@@ -21,7 +21,7 @@ namespace Barotrauma
/// <summary>
/// How long does it take for the ai target to fade out if not kept alive.
/// </summary>
public float FadeOutTime { get; private set; }
public float FadeOutTime { get; private set; } = 1;
public bool Static { get; private set; }
@@ -611,6 +611,7 @@ namespace Barotrauma
}
bool canAttack = true;
bool pursue = false;
if (IsCoolDownRunning)
{
switch (AttackingLimb.attack.AfterAttack)
@@ -623,6 +624,7 @@ namespace Barotrauma
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
{
canAttack = false;
pursue = true;
}
else
{
@@ -661,6 +663,7 @@ namespace Barotrauma
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
{
canAttack = false;
pursue = true;
}
else
{
@@ -853,32 +856,35 @@ namespace Barotrauma
if (pathSteering.CurrentPath != null)
{
// Attack doors
if (canAttackSub && pathSteering.CurrentPath.CurrentNode?.ConnectedDoor != null && SelectedAiTarget != pathSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget)
if (canAttackSub)
{
SelectTarget(pathSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget);
return;
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
{
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen && door.Item.Condition > 0)
{
if (SelectedAiTarget != door.Item.AiTarget)
{
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
}
}
}
}
else if (canAttackSub && pathSteering.CurrentPath.NextNode?.ConnectedDoor != null && SelectedAiTarget != pathSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget)
// Steer towards the target if in the same room and swimming
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{
SelectTarget(pathSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget);
return;
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else
{
// Steer towards the target if in the same room and swimming
if (Character.AnimController.InWater && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else
{
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
return;
}
State = AIState.Idle;
return;
}
}
}
@@ -1148,8 +1154,8 @@ namespace Barotrauma
{
selectedTargetMemory.Priority = 0;
}
return true;
}
return true;
}
return false;
}
@@ -525,6 +525,7 @@ namespace Barotrauma
protected void ReportProblems()
{
Order newOrder = null;
Hull targetHull = null;
if (Character.CurrentHull != null)
{
foreach (var hull in VisibleHulls)
@@ -534,21 +535,21 @@ namespace Barotrauma
if (c.CurrentHull != hull || !c.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
{
AddTargets<AIObjectiveFightIntruders, Character>(Character, c);
if (newOrder == null)
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character);
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
}
if (AIObjectiveExtinguishFires.IsValidTarget(hull, Character))
{
AddTargets<AIObjectiveExtinguishFires, Hull>(Character, hull);
if (newOrder == null)
if (AddTargets<AIObjectiveExtinguishFires, Hull>(Character, hull) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportfire");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
foreach (Character c in Character.CharacterList)
@@ -556,13 +557,11 @@ namespace Barotrauma
if (c.CurrentHull != hull) { continue; }
if (AIObjectiveRescueAll.IsValidTarget(c, Character))
{
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character))
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
if (newOrder == null)
{
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character);
}
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
}
@@ -570,11 +569,11 @@ namespace Barotrauma
{
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
{
AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap);
if (newOrder == null && !gap.IsRoomToRoom)
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
{
var orderPrefab = Order.GetPrefab("reportbreach");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
}
@@ -584,11 +583,11 @@ namespace Barotrauma
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
{
if (item.Repairables.All(r => item.ConditionPercentage > r.ShowRepairUIThreshold)) { continue; }
AddTargets<AIObjectiveRepairItems, Item>(Character, item);
if (newOrder == null)
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
{
var orderPrefab = Order.GetPrefab("reportbrokendevices");
newOrder = new Order(orderPrefab, item.CurrentHull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
targetHull = hull;
}
}
}
@@ -598,9 +597,9 @@ namespace Barotrauma
{
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
{
Character.Speak(newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
#if SERVER
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", Character.CurrentHull, null, Character));
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", targetHull, null, Character));
#endif
}
}
@@ -191,7 +191,8 @@ namespace Barotrauma
{
diff.Y = 0.0f;
}
if (diff.LengthSquared() < 0.001f) { return -host.Steering; }
//if (diff.LengthSquared() < 0.001f) { return -host.Steering; }
if (diff == Vector2.Zero) { return Vector2.Zero; }
return Vector2.Normalize(diff) * weight;
}
@@ -71,7 +71,7 @@ namespace Barotrauma
foreach (FireSource fs in targetHull.FireSources)
{
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
bool move = !inRange;
bool move = !inRange || !HumanAIController.VisibleHulls.Contains(fs.Hull);
if (inRange || useExtinquisherTimer > 0.0f)
{
useExtinquisherTimer += deltaTime;
@@ -79,7 +79,6 @@ namespace Barotrauma
{
useExtinquisherTimer = 0.0f;
}
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
@@ -106,20 +105,19 @@ namespace Barotrauma
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
if (!character.CanSeeTarget(fs, sightLimb))
if (character.CanSeeTarget(fs, sightLimb))
{
move = true;
}
else
{
move = false;
character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.Name, true), null, 0, "putoutfire", 10.0f);
{
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.RoomName, true), null, 0, "putoutfire", 10.0f);
}
}
else
{
move = true;
}
}
if (move)
{
@@ -128,6 +126,10 @@ namespace Barotrauma
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref gotoObjective));
}
else
{
character.AIController.SteeringManager.Reset();
}
break;
}
}
@@ -11,7 +11,7 @@ namespace Barotrauma
{
public override string DebugTag => "fight intruders";
protected override float IgnoreListClearInterval => 30;
public virtual bool IgnoreUnsafeHulls => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
@@ -200,7 +200,7 @@ namespace Barotrauma
{
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
}
if (!insideSteering)
if (!insideSteering && character.CurrentHull == null)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 1);
}
@@ -293,7 +293,7 @@ namespace Barotrauma
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override auto pilot unless it's an order by a player
// Don't override unless it's an order by a player
Override = orderGiver == Character.Controlled || orderGiver.IsRemotePlayer
};
break;
@@ -302,7 +302,7 @@ namespace Barotrauma
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override auto control unless it's an order by a player
// Don't override unless it's an order by a player
Override = orderGiver == Character.Controlled || orderGiver.IsRemotePlayer
};
break;
@@ -24,6 +24,8 @@ namespace Barotrauma
public Entity OperateTarget => operateTarget;
public ItemComponent Component => component;
public ItemComponent GetTarget() => useController ? controller : component;
public Func<bool> completionCondition;
public override float GetPriority()
@@ -35,6 +37,7 @@ namespace Barotrauma
}
if (component.Item.CurrentHull == null) { return 0; }
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
if (IsOperatedByAnother(GetTarget())) { return 0; }
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
float devotion = MathHelper.Min(10, Priority);
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
@@ -58,6 +61,45 @@ namespace Barotrauma
}
}
private bool IsOperatedByAnother(ItemComponent target)
{
foreach (var c in Character.CharacterList)
{
if (c == character) { continue; }
if (!HumanAIController.IsFriendly(c)) { continue; }
if (c.SelectedConstruction != target.Item) { continue; }
// If the other character is player, don't try to operate
if (c.IsRemotePlayer || Character.Controlled == c) { return true; }
if (c.AIController is HumanAIController humanAi)
{
// If the other character is ordered to operate the item, let him do it
if (humanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
{
return true;
}
else
{
if (target is Steering)
{
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
}
else
{
return target.DegreeOfSuccess(character) <= target.DegreeOfSuccess(c);
}
}
}
else
{
// Shouldn't go here, unless we allow non-humans to operate items
return false;
}
}
return false;
}
protected override void Act(float deltaTime)
{
if (character.LockHands)
@@ -65,24 +107,24 @@ namespace Barotrauma
Abandon = true;
return;
}
ItemComponent target = useController ? controller : component;
ItemComponent target = GetTarget();
if (useController && controller == null)
{
character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name, true), null, 2.0f, "cantfindcontroller", 30.0f);
Abandon = true;
return;
}
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(target))
{
// Don't abandon
return;
}
if (target.CanBeSelected)
{
if (character.CanInteractWith(target.Item, out _, checkLinked: false))
{
HumanAIController.FaceTarget(target.Item);
// Don't allow to operate an item that someone already operates, unless this objective is an order
if (objectiveManager.CurrentOrder != this && Character.CharacterList.Any(c => c.SelectedConstruction == target.Item && c != character && HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
{
// Don't abandon
return;
}
if (character.SelectedConstruction != target.Item)
{
target.Item.TryInteract(character, false, true);
@@ -764,12 +764,17 @@ namespace Barotrauma
.Where(p => p.AfflictionType == "huskinfection")
.Select(p => p as AfflictionPrefabHusk)
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.InvariantCultureIgnoreCase)));
string nonHuskedSpeciesName = string.Empty;
if (matchingAffliction == null)
{
DebugConsole.ThrowError("Cannot find a husk infection that matches this species! Please add the speciesnames as 'targets' in the husk affliction prefab definition!");
return;
// Crashes if we fail to create a ragdoll -> Let's just use some ragdoll so that the user sees the error msg.
nonHuskedSpeciesName = IsHumanoid ? HumanSpeciesName : "crawler";
}
else
{
nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
}
string nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(nonHuskedSpeciesName) : RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(nonHuskedSpeciesName) as RagdollParams;
if (info == null)
{
@@ -1912,7 +1917,7 @@ namespace Barotrauma
{
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
{
focusedCharacter = FindCharacterAtPosition(mouseSimPos);
focusedCharacter = CanInteract ? FindCharacterAtPosition(mouseSimPos) : null;
focusedItem = CanInteract ?
FindItemAtPosition(mouseSimPos, GameMain.Config.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f)) : null;
findFocusedTimer = 0.05f;
@@ -1972,11 +1977,11 @@ namespace Barotrauma
{
DeselectCharacter();
}
else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged)
else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged && CanInteract)
{
SelectCharacter(focusedCharacter);
}
else if (focusedCharacter != null && IsKeyHit(InputType.Health) && focusedCharacter.CharacterHealth.UseHealthWindow && CanInteractWith(focusedCharacter, 160f, false))
else if (focusedCharacter != null && IsKeyHit(InputType.Health) && focusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(focusedCharacter, 160f, false))
{
if (focusedCharacter == SelectedCharacter)
{
@@ -289,7 +289,7 @@ namespace Barotrauma
public static string GetNonHuskedSpeciesName(string huskedSpeciesName, AfflictionPrefabHusk prefab)
{
string nonTag = prefab.HuskedSpeciesName.Remove(AfflictionPrefabHusk.Tag);
return huskedSpeciesName.Remove(nonTag);
return huskedSpeciesName.ToLowerInvariant().Remove(nonTag);
}
}
}
@@ -41,7 +41,7 @@ namespace Barotrauma
{
public AfflictionPrefabHusk(XElement element, Type type = null) : base(element, type)
{
HuskedSpeciesName = element.GetAttributeString("huskedspeciesname", null);
HuskedSpeciesName = element.GetAttributeString("huskedspeciesname", null).ToLowerInvariant();
if (HuskedSpeciesName == null)
{
DebugConsole.NewMessage($"No 'huskedspeciesname' defined for the husk affliction ({Identifier}) in {element.ToString()}", Color.Orange);
@@ -85,7 +85,11 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
if (IsClient) { return; }
if (IsClient)
{
if (item.ParentInventory != null) { item.body.FarseerBody.IsKinematic = false; }
return;
}
switch (State)
{
case 0:
@@ -296,10 +296,11 @@ namespace Barotrauma
{
get
{
#if DEBUG
return false;
/*#if DEBUG
return false;
#endif
return sendUserStatistics;
return sendUserStatistics;*/
}
set
{
@@ -25,7 +25,18 @@ namespace Barotrauma.Items.Components
private readonly bool autoOrientGap;
private bool isStuck;
public bool IsStuck => isStuck;
public bool IsStuck
{
get { return isStuck; }
private set
{
if (isStuck == value) { return; }
isStuck = value;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
private float resetPredictionTimer;
@@ -65,12 +76,12 @@ namespace Barotrauma.Items.Components
public float Stuck
{
get { return stuck; }
set
set
{
if (isOpen || isBroken || !CanBeWelded) return;
stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
if (stuck <= 0.0f) isStuck = false;
if (stuck >= 100.0f) isStuck = true;
if (stuck <= 0.0f) { IsStuck = false; }
if (stuck >= 100.0f) { IsStuck = true; }
}
}
@@ -296,7 +307,7 @@ namespace Barotrauma.Items.Components
}
bool isClosing = false;
if (!isStuck)
if (!IsStuck)
{
if (PredictedState == null)
{
@@ -541,7 +552,7 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (isStuck) return;
if (IsStuck) return;
bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value;
@@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components
{
partial class ElectricalDischarger : Powered
{
private static List<ElectricalDischarger> list = new List<ElectricalDischarger>();
private static readonly List<ElectricalDischarger> list = new List<ElectricalDischarger>();
public static IEnumerable<ElectricalDischarger> List
{
get { return list; }
@@ -48,14 +48,14 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(100.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
[Serialize(500.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
public float Range
{
get;
set;
}
[Serialize(10.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
[Serialize(25.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float RangeMultiplierInWalls
{
get;
@@ -127,20 +127,42 @@ namespace Barotrauma.Items.Components
#if CLIENT
frameOffset = Rand.Int(electricitySprite.FrameCount);
#endif
if (timer > 0.0f)
{
if (charging)
{
if (Voltage > MinVoltage)
{
Discharge();
}
}
timer -= deltaTime;
}
else
if (timer <= 0.0f)
{
IsActive = false;
return;
}
timer -= deltaTime;
if (charging)
{
if (GetAvailableBatteryPower() >= powerConsumption)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
float takePower = neededPower / batteries.Count;
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
foreach (PowerContainer battery in batteries)
{
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
{
battery.Item.CreateServerEvent(battery);
}
#endif
}
}
Discharge();
}
else if (Voltage > MinVoltage)
{
Discharge();
}
}
}
@@ -141,7 +141,7 @@ namespace Barotrauma.Items.Components
//TODO: refactor the hitting logic (get rid of the magic numbers, make it possible to use different kinds of animations for different items)
if (!hitting)
{
bool aim = picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
bool aim = picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
if (aim)
{
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4));
@@ -96,11 +96,14 @@ namespace Barotrauma.Items.Components
{
useState -= deltaTime;
if (useState <= 0.0f) IsActive = false;
if (item.AiTarget != null)
if (useState <= 0.0f)
{
item.AiTarget.SoundRange = IsActive ? item.AiTarget.MaxSoundRange : item.AiTarget.MinSoundRange;
IsActive = false;
}
if (item.AiTarget != null && IsActive)
{
item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange;
}
}
@@ -399,7 +399,7 @@ namespace Barotrauma.Items.Components
case "activate":
case "use":
case "trigger_in":
item.Use(1.0f);
item.Use(1.0f, sender);
break;
case "toggle":
if (signal != "0")
@@ -669,7 +669,7 @@ namespace Barotrauma.Items.Components
}
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Character user = null)
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null)
{
if (statusEffectLists == null) return;
@@ -680,20 +680,24 @@ namespace Barotrauma.Items.Components
{
if (broken && effect.type != ActionType.OnBroken) { continue; }
if (user != null) { effect.SetUser(user); }
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, false, false);
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, false, false, worldPosition);
}
}
public virtual void Load(XElement componentElement, bool usePrefabValues)
{
if (componentElement == null || usePrefabValues) { return; }
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
property.TrySetValue(this, attribute.Value);
if (componentElement != null && !usePrefabValues)
{
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
property.TrySetValue(this, attribute.Value);
}
ParseMsg();
OverrideRequiredItems(componentElement);
}
ParseMsg();
OverrideRequiredItems(componentElement);
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.GameVersion); }
}
/// <summary>
@@ -123,9 +123,9 @@ namespace Barotrauma.Items.Components
if (user.AnimController.InWater)
{
if (diff.Length() > 30.0f)
if (diff.LengthSquared() > 30.0f * 30.0f)
{
user.AnimController.TargetMovement = Vector2.Clamp(diff*0.01f, -Vector2.One, Vector2.One);
user.AnimController.TargetMovement = Vector2.Clamp(diff * 0.01f, -Vector2.One, Vector2.One);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
}
else
@@ -136,11 +136,29 @@ namespace Barotrauma.Items.Components
else
{
diff.Y = 0.0f;
if (diff != Vector2.Zero && diff.LengthSquared() > 10.0f * 10.0f)
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
{
user.AnimController.TargetMovement = Vector2.Normalize(diff);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
return;
if (Math.Abs(diff.X) > 20.0f)
{
//wait for the character to walk to the correct position
return;
}
else if (Math.Abs(diff.X) > 0.1f)
{
//aim to keep the collider at the correct position once close enough
user.AnimController.Collider.LinearVelocity = new Vector2(
diff.X * 0.1f,
user.AnimController.Collider.LinearVelocity.Y);
}
}
else
{
if (Math.Abs(diff.X) > 10.0f)
{
user.AnimController.TargetMovement = Vector2.Normalize(diff);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
return;
}
}
user.AnimController.TargetMovement = Vector2.Zero;
}
@@ -293,7 +311,7 @@ namespace Barotrauma.Items.Components
private void CancelUsing(Character character)
{
if (character == null || character.Removed) return;
if (character == null || character.Removed) { return; }
foreach (LimbPos lb in limbPositions)
{
@@ -304,18 +322,21 @@ namespace Barotrauma.Items.Components
limb.PullJointEnabled = false;
}
if (character.SelectedConstruction == this.item) character.SelectedConstruction = null;
if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; }
character.AnimController.Anim = AnimController.Animation.None;
if (character == Character.Controlled)
{
HideHUDs(false);
}
#if SERVER
item.CreateServerEvent(this);
#endif
}
public override bool Select(Character activator)
{
if (activator == null || activator.Removed) return false;
if (activator == null || activator.Removed) { return false; }
//someone already using the item
if (user != null && !user.Removed)
@@ -330,10 +351,12 @@ namespace Barotrauma.Items.Components
}
else
{
user = activator;
user = activator;
IsActive = true;
}
#if SERVER
item.CreateServerEvent(this);
#endif
item.SendSignal(0, "1", "signal_out", user);
return true;
}
@@ -86,7 +86,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull == null) { return; }
float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : Voltage;
float powerFactor = Math.Min(currPowerConsumption <= 0.0f ? 1.0f : Voltage, 1.0f);
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
//less effective when in a bad condition
@@ -640,9 +640,15 @@ namespace Barotrauma.Items.Components
}
}
if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item)
if (objective.Override)
{
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
if (lastUser != null && lastUser != character && lastUser != lastAIUser)
{
if (lastUser.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
}
}
}
LastUser = lastAIUser = character;
@@ -479,9 +479,12 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (user != character && user != null && user.SelectedConstruction == item)
if (objective.Override)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
if (user != character && user != null && user.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
}
}
user = character;
if (!AutoPilot)
@@ -175,9 +175,8 @@ namespace Barotrauma.Items.Components
else
{
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f);
Charge += currPowerConsumption * Voltage / 3600.0f;
}
Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f;
}
if (charge <= 0.0f)
{
@@ -280,6 +280,8 @@ namespace Barotrauma.Items.Components
{
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }
if (item.Condition <= 0.0f) { return; }
lastPowerProbeRecipients.Add(this);
if (power < 0.0f)
@@ -306,15 +308,15 @@ namespace Barotrauma.Items.Components
foreach (ItemComponent ic in recipient.Item.Components)
{
//powertransfer components don't need to receive the signal in the pass-through signal connections
//other junction boxes don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
if (ic is PowerTransfer && connection.Name.Contains("signal")) { continue; }
if (ic is PowerTransfer && !(ic is RelayComponent) && connection.Name.Contains("signal")) { continue; }
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, 0.0f, signalStrength);
}
foreach (StatusEffect effect in recipient.Effects)
{
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false);
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
}
}
}
@@ -279,13 +279,30 @@ namespace Barotrauma.Items.Components
var pc = powerSource.Item.GetComponent<PowerContainer>();
if (pc != null)
{
float voltage = -pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f);
float voltage = pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f);
powered.voltage += voltage;
}
}
}
}
/// <summary>
/// Returns the amount of power that can be supplied by batteries directly connected to the item
/// </summary>
protected float GetAvailableBatteryPower()
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
{
float batteryPower = Math.Min(battery.Charge * 3600.0f, battery.MaxOutPut);
availablePower += batteryPower;
}
return availablePower;
}
protected override void RemoveComponentSpecific()
{
poweredList.Remove(this);
@@ -458,6 +458,11 @@ namespace Barotrauma.Items.Components
if (character != null) { character.LastDamageSource = item; }
#if CLIENT
PlaySound(ActionType.OnUse, item.WorldPosition, user: user);
PlaySound(ActionType.OnImpact, item.WorldPosition, user: user);
#endif
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (target.Body.UserData is Limb targetLimb)
@@ -489,14 +494,26 @@ namespace Barotrauma.Items.Components
}
}
}
}
#if SERVER
if (GameMain.NetworkMember.IsServer)
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse });
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact });
}
if (GameMain.NetworkMember.IsServer)
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, 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 });
}
#endif
}
else
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
#if SERVER
if (GameMain.NetworkMember.IsServer)
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, 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 });
}
#endif
}
}
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
@@ -130,6 +130,12 @@ namespace Barotrauma.Items.Components
}
else
{
#if SERVER
if (CurrentFixer != character || currentFixerAction != action)
{
item.CreateServerEvent(this);
}
#endif
CurrentFixer = character;
CurrentFixerAction = action;
return true;
@@ -140,12 +146,15 @@ namespace Barotrauma.Items.Components
{
if (CurrentFixer == character)
{
#if SERVER
if (CurrentFixer != character || currentFixerAction != FixActions.None)
{
item.CreateServerEvent(this);
}
#endif
CurrentFixer.AnimController.Anim = AnimController.Animation.None;
CurrentFixer = null;
currentFixerAction = FixActions.None;
#if SERVER
item.CreateServerEvent(this);
#endif
#if CLIENT
repairSoundChannel?.FadeOutAndDispose();
repairSoundChannel = null;
@@ -214,16 +223,16 @@ namespace Barotrauma.Items.Components
return;
}
UpdateFixAnimation(CurrentFixer);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (CurrentFixer != null && (CurrentFixer.SelectedConstruction != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
{
StopRepairing(CurrentFixer);
return;
}
UpdateFixAnimation(CurrentFixer);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
float successFactor = requiredSkills.Count == 0 ? 1.0f : DegreeOfSuccess(CurrentFixer, requiredSkills);
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it
@@ -258,7 +258,7 @@ namespace Barotrauma.Items.Components
foreach (StatusEffect effect in recipient.Effects)
{
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false);
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step);
}
}
}
@@ -35,6 +35,11 @@ namespace Barotrauma.Items.Components
set { /*do nothing*/ }
}
public Character User
{
get { return user; }
}
public ConnectionPanel(Item item, XElement element)
: base(item, element)
{
@@ -126,6 +131,9 @@ namespace Barotrauma.Items.Components
if (user == null || user.SelectedConstruction != item)
{
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
#endif
user = null;
return;
}
@@ -135,6 +143,11 @@ namespace Barotrauma.Items.Components
user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
partial void UpdateProjSpecific(float deltaTime);
public override bool Select(Character picker)
@@ -147,13 +160,16 @@ namespace Barotrauma.Items.Components
}
user = picker;
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
#endif
IsActive = true;
return true;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character != user) return false;
if (character == null || character != user) { return false; }
var powered = item.GetComponent<Powered>();
if (powered != null)
@@ -257,6 +273,10 @@ namespace Barotrauma.Items.Components
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
#if CLIENT
TriggerRewiringSound();
#endif
foreach (Connection connection in Connections)
{
foreach (Wire wire in connection.Wires)
@@ -172,7 +172,7 @@ namespace Barotrauma.Items.Components
foreach (StatusEffect effect in ciElement.StatusEffects)
{
item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, true, false);
item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, null, true, false);
}
}
}
@@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components
{
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
#if CLIENT
if (light != null) light.Range = range;
if (light != null) { light.Range = range; }
#endif
}
}
@@ -75,7 +75,7 @@ namespace Barotrauma.Items.Components
get { return IsActive; }
set
{
if (IsActive == value) return;
if (IsActive == value) { return; }
IsActive = value;
#if SERVER
@@ -135,11 +135,8 @@ namespace Barotrauma.Items.Components
{
if (base.IsActive == value) { return; }
base.IsActive = value;
#if CLIENT
if (light == null) return;
light.Color = value ? lightColor : Color.Transparent;
if (!value) lightBrightness = 0.0f;
#endif
SetLightSourceState(value, value ? lightBrightness : 0.0f);
}
}
@@ -153,7 +150,8 @@ namespace Barotrauma.Items.Components
Position = item.Position,
CastShadows = castShadows,
IsBackground = drawBehindSubs,
SpriteScale = Vector2.One * item.Scale
SpriteScale = Vector2.One * item.Scale,
Range = range
};
#endif
@@ -161,40 +159,34 @@ namespace Barotrauma.Items.Components
item.AddTag("light");
}
#if CLIENT
public override void OnScaleChanged()
{
light.SpriteScale = Vector2.One * item.Scale;
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
}
#endif
public override void OnItemLoaded()
{
base.OnItemLoaded();
itemLoaded = true;
#if CLIENT
light.Color = IsActive ? lightColor : Color.Transparent;
if (!IsActive) lightBrightness = 0.0f;
#endif
SetLightSourceState(IsActive, lightBrightness);
}
public override void Update(float deltaTime, Camera cam)
{
if (item.AiTarget != null)
{
UpdateAITarget(item.AiTarget);
}
UpdateOnActiveEffects(deltaTime);
#if CLIENT
light.ParentSub = item.Submarine;
#endif
if (item.Container != null)
{
light.Color = Color.Transparent;
SetLightSourceState(false, 0.0f);
return;
}
#if CLIENT
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
#endif
PhysicsBody body = ParentBody ?? item.body;
if (body != null)
{
#if CLIENT
@@ -203,9 +195,7 @@ namespace Barotrauma.Items.Components
#endif
if (!body.Enabled)
{
#if CLIENT
light.Color = Color.Transparent;
#endif
SetLightSourceState(false, 0.0f);
return;
}
}
@@ -217,7 +207,6 @@ namespace Barotrauma.Items.Components
}
currPowerConsumption = powerConsumption;
if (Rand.Range(0.0f, 1.0f) < 0.05f && Voltage < Rand.Range(0.0f, MinVoltage))
{
#if CLIENT
@@ -240,36 +229,21 @@ namespace Barotrauma.Items.Components
if (blinkTimer > 0.5f)
{
#if CLIENT
light.Color = Color.Transparent;
#endif
SetLightSourceState(false, lightBrightness);
}
else
{
#if CLIENT
light.Color = lightColor * lightBrightness * (1.0f - Rand.Range(0.0f, Flicker));
light.Range = range;
#endif
SetLightSourceState(true, lightBrightness * (1.0f - Rand.Range(0.0f, flicker)));
}
if (item.AiTarget != null)
{
UpdateAITarget(item.AiTarget);
}
}
#if CLIENT
public override void UpdateBroken(float deltaTime, Camera cam)
{
light.Color = Color.Transparent;
lightBrightness = 0.0f;
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
}
protected override void RemoveComponentSpecific()
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.RemoveComponentSpecific();
light.Remove();
SetLightSourceState(false, 0.0f);
}
#endif
public override bool Use(float deltaTime, Character character = null)
{
return true;
@@ -277,8 +251,6 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
switch (connection.Name)
{
case "toggle":
@@ -300,13 +272,14 @@ namespace Barotrauma.Items.Components
private void UpdateAITarget(AITarget target)
{
//voltage > minVoltage || powerConsumption <= 0.0f; <- ?
target.Enabled = IsActive;
if (!IsActive) { return; }
if (target.MaxSightRange <= 0)
{
target.MaxSightRange = Range * 5;
}
target.SightRange = IsActive ? target.MaxSightRange * lightBrightness : 0;
target.SightRange = target.MaxSightRange * lightBrightness;
}
partial void SetLightSourceState(bool enabled, float brightness);
}
}
@@ -102,7 +102,7 @@ namespace Barotrauma.Items.Components
public override void ReceivePowerProbeSignal(Connection connection, Item source, float power)
{
if (!IsOn) { return; }
if (!IsOn || item.Condition <= 0.0f) { return; }
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }
@@ -276,7 +276,7 @@ namespace Barotrauma.Items.Components
if (reload > 0.0f) return false;
if (GetAvailablePower() < powerConsumption)
if (GetAvailableBatteryPower() < powerConsumption)
{
#if CLIENT
if (!flashLowPower && character != null && character == Character.Controlled)
@@ -410,7 +410,7 @@ namespace Barotrauma.Items.Components
character.AIController.SelectTarget(null);
}
if (GetAvailablePower() < powerConsumption)
if (GetAvailableBatteryPower() < powerConsumption)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
@@ -540,21 +540,6 @@ namespace Barotrauma.Items.Components
return false;
}
private float GetAvailablePower()
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
{
float batteryPower = Math.Min(battery.Charge*3600.0f, battery.MaxOutPut);
availablePower += batteryPower;
}
return availablePower;
}
private void GetAvailablePower(out float availableCharge, out float availableCapacity)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
@@ -425,7 +425,7 @@ namespace Barotrauma
string[] splitTags = value.Split(',');
foreach (string tag in splitTags)
{
string[] splitTag = tag.Split(':');
string[] splitTag = tag.Trim().Split(':');
splitTag[0] = splitTag[0].ToLowerInvariant();
tags.Add(string.Join(":", splitTag));
}
@@ -1060,18 +1060,18 @@ namespace Barotrauma
return true;
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false)
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, Vector2? worldPosition = null)
{
if (!hasStatusEffectsOfType[(int)type]) { return; }
foreach (StatusEffect effect in statusEffectLists[type])
{
ApplyStatusEffect(effect, type, deltaTime, character, limb, isNetworkEvent, false);
ApplyStatusEffect(effect, type, deltaTime, character, limb, useTarget, isNetworkEvent, false, worldPosition);
}
}
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false, bool checkCondition = true)
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null)
{
if (!isNetworkEvent && checkCondition)
{
@@ -1110,6 +1110,12 @@ namespace Barotrauma
if (targets.Count > 0) { hasTargets = true; }
}
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget) && useTarget is ISerializableEntity serializableTarget)
{
hasTargets = true;
targets.Add(serializableTarget);
}
if (!hasTargets) { return; }
if (effect.HasTargetType(StatusEffect.TargetType.Hull) && CurrentHull != null)
@@ -1125,30 +1131,32 @@ namespace Barotrauma
}
}
if (effect.HasTargetType(StatusEffect.TargetType.Character))
if (character != null)
{
if (type == ActionType.OnContained && ParentInventory is CharacterInventory characterInventory)
if (effect.HasTargetType(StatusEffect.TargetType.Character))
{
targets.Add(characterInventory.Owner as ISerializableEntity);
if (type == ActionType.OnContained && ParentInventory is CharacterInventory characterInventory)
{
targets.Add(characterInventory.Owner as ISerializableEntity);
}
else
{
targets.Add(character);
}
}
else
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
targets.Add(character);
targets.AddRange(character.AnimController.Limbs.ToList());
}
}
if (effect.HasTargetType(StatusEffect.TargetType.Limb))
{
targets.Add(limb);
}
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
targets.AddRange(character.AnimController.Limbs.ToList());
}
if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) targets.Add(Container);
effect.Apply(type, deltaTime, this, targets);
effect.Apply(type, deltaTime, this, targets, worldPosition);
}
@@ -1360,7 +1368,8 @@ namespace Barotrauma
{
if (transformDirty) { return false; }
Vector2 normal = contact.Manifold.LocalNormal;
contact.GetWorldManifold(out Vector2 normal, out _);
if (contact.FixtureA.Body == f1.Body) { normal = -normal; }
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
OnCollisionProjSpecific(f1, f2, contact, impact);
@@ -1568,7 +1577,7 @@ namespace Barotrauma
foreach (StatusEffect effect in connection.Effects)
{
if (condition <= 0.0f && effect.type != ActionType.OnBroken) { continue; }
if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false); }
if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step); }
}
connection.SendSignal(stepsTaken, signal, source ?? this, sender, power, signalStrength);
}
@@ -575,6 +575,10 @@ namespace Barotrauma
public void ApplyFlowForces(float deltaTime, Item item)
{
if (item.body.Mass <= 0.0f)
{
return;
}
foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))
{
var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f);
@@ -180,14 +180,22 @@ namespace Barotrauma
}
linkedSub.filePath = element.GetAttributeString("filepath", "");
int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]);
int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]);
for (int i = 0; i < linkedToIds.Length; i++)
{
linkedSub.linkedToID.Add((ushort)linkedToIds[i]);
if (Screen.Selected == GameMain.SubEditorScreen)
{
if (FindEntityByID((ushort)linkedToIds[i]) is MapEntity linked)
{
linkedSub.linkedTo.Add(linked);
}
}
}
linkedSub.originalLinkedToID = (ushort)element.GetAttributeInt("originallinkedto", 0);
linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0);
return linkedSub.loadSub ? linkedSub : null;
}
@@ -107,11 +107,14 @@ namespace Barotrauma
{
public readonly Entity Entity;
public readonly UInt16 OriginalID;
public readonly bool Remove = false;
public SpawnOrRemove(Entity entity, bool remove)
{
Entity = entity;
OriginalID = entity.ID;
Remove = remove;
}
}
@@ -328,7 +328,16 @@ namespace Barotrauma.Networking
}
}
public string ServerName;
private string serverName;
public string ServerName
{
get { return serverName; }
set
{
serverName = value;
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
}
}
private string serverMessageText;
public string ServerMessageText
@@ -177,7 +177,7 @@ namespace Barotrauma
string[] readTags = valStr.Split(',');
int matches = 0;
foreach (string tag in readTags)
if (((Item)target).HasTag(tag)) matches++;
if (target is Item item && item.HasTag(tag)) matches++;
//If operator is == then it needs to match everything, otherwise if its != there must be zero matches.
return Operator == OperatorType.Equals ? matches >= readTags.Length : matches <= 0;
@@ -225,8 +225,7 @@ namespace Barotrauma
return success;
case ConditionType.SpeciesName:
if (target == null) { return Operator == OperatorType.NotEquals; }
Character targetCharacter = target as Character;
if (targetCharacter == null) { return false; }
if (!(target is Character targetCharacter)) { return false; }
return (Operator == OperatorType.Equals) == (targetCharacter.SpeciesName == valStr);
case ConditionType.EntityType:
switch (valStr)
+175 -129
View File
@@ -12,8 +12,10 @@ namespace Barotrauma
//only used if none of the selected content packages contain any text files
const string VanillaTextFilePath = "Content/Texts/EnglishVanilla.xml";
private static readonly object mutex = new object();
//key = language
private static Dictionary<string, List<TextPack>> textPacks = new Dictionary<string, List<TextPack>>();
private static Dictionary<string, List<TextPack>> textPacks;
private static readonly string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" };
@@ -25,10 +27,16 @@ namespace Barotrauma
private set;
}
private static readonly HashSet<string> availableLanguages = new HashSet<string>();
private static HashSet<string> availableLanguages;
public static IEnumerable<string> AvailableLanguages
{
get { return availableLanguages; }
get
{
lock (mutex)
{
return new HashSet<string>(availableLanguages);
}
}
}
public static List<string> GetTextFiles()
@@ -55,25 +63,29 @@ namespace Barotrauma
/// </summary>
public static string GetTranslatedLanguageName(string language)
{
if (!textPacks.ContainsKey(language))
lock (mutex)
{
if (!textPacks.ContainsKey(language))
{
return language;
}
foreach (var textPack in textPacks[language])
{
if (textPack.Language == language)
{
return textPack.TranslatedName;
}
}
return language;
}
foreach (var textPack in textPacks[language])
{
if (textPack.Language == language)
{
return textPack.TranslatedName;
}
}
return language;
}
public static void LoadTextPacks(IEnumerable<ContentPackage> selectedContentPackages)
{
availableLanguages.Clear();
textPacks.Clear();
HashSet<string> newLanguages = new HashSet<string>();
Dictionary<string, List<TextPack>> newTextPacks = new Dictionary<string, List<TextPack>>();
var textFiles = ContentPackage.GetFilesOfType(selectedContentPackages, ContentType.Text);
foreach (string file in textFiles)
@@ -81,12 +93,12 @@ namespace Barotrauma
try
{
var textPack = new TextPack(file);
availableLanguages.Add(textPack.Language);
if (!textPacks.ContainsKey(textPack.Language))
newLanguages.Add(textPack.Language);
if (!newTextPacks.ContainsKey(textPack.Language))
{
textPacks.Add(textPack.Language, new List<TextPack>());
newTextPacks.Add(textPack.Language, new List<TextPack>());
}
textPacks[textPack.Language].Add(textPack);
newTextPacks[textPack.Language].Add(textPack);
}
catch (Exception e)
{
@@ -94,7 +106,7 @@ namespace Barotrauma
}
}
if (textPacks.Count == 0)
if (newTextPacks.Count == 0)
{
DebugConsole.ThrowError("No text files available in any of the selected content packages. Attempting to find a vanilla English text file...");
if (!File.Exists(VanillaTextFilePath))
@@ -102,9 +114,21 @@ namespace Barotrauma
throw new Exception("No text files found in any of the selected content packages or in the default text path!");
}
var textPack = new TextPack(VanillaTextFilePath);
availableLanguages.Add(textPack.Language);
textPacks.Add(textPack.Language, new List<TextPack>() { textPack });
newLanguages.Add(textPack.Language);
newTextPacks.Add(textPack.Language, new List<TextPack>() { textPack });
}
if (newTextPacks.Count == 0)
{
throw new Exception("Failed to load text packs!");
}
lock (mutex)
{
textPacks = newTextPacks;
availableLanguages = newLanguages;
}
Initialized = true;
}
@@ -112,74 +136,81 @@ namespace Barotrauma
{
if (string.IsNullOrEmpty(textTag)) { return false; }
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
foreach (TextPack textPack in textPacks[Language])
{
if (textPack.Get(textTag) != null) { return true; }
}
}
foreach (TextPack textPack in textPacks[Language])
{
if (textPack.Get(textTag) != null) { return true; }
}
return false;
}
public static string Get(string textTag, bool returnNull = false, string fallBackTag = null)
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(textTag);
if (text != null) { return text; }
}
if (!string.IsNullOrEmpty(fallBackTag))
{
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(fallBackTag);
if (text != null) { return text; }
}
}
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
{
string text = textPack.Get(textTag);
if (text != null)
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
#if DEBUG
DebugConsole.NewMessage("Text \"" + textTag + "\" not found for the language \"" + Language + "\". Using the English text \"" + text + "\" instead.");
#endif
return text;
throw new Exception("No text packs available in English!");
}
}
}
if (returnNull)
{
return null;
}
else
{
DebugConsole.ThrowError("Text \"" + textTag + "\" not found.");
return textTag;
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(textTag);
if (text != null) { return text; }
}
if (!string.IsNullOrEmpty(fallBackTag))
{
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(fallBackTag);
if (text != null) { return text; }
}
}
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
{
string text = textPack.Get(textTag);
if (text != null)
{
#if DEBUG
DebugConsole.NewMessage("Text \"" + textTag + "\" not found for the language \"" + Language + "\". Using the English text \"" + text + "\" instead.");
#endif
return text;
}
}
}
if (returnNull)
{
return null;
}
else
{
DebugConsole.ThrowError("Text \"" + textTag + "\" not found.");
return textTag;
}
}
}
@@ -418,13 +449,16 @@ namespace Barotrauma
// And: replacement=formatter(value)
public static string GetServerMessage(string serverMessage)
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
}
@@ -588,58 +622,64 @@ namespace Barotrauma
public static List<string> GetAll(string textTag)
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
}
List<string> allText;
List<string> allText;
foreach (TextPack textPack in textPacks[Language])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
foreach (TextPack textPack in textPacks[Language])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
}
return null;
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
}
return null;
}
}
public static List<KeyValuePair<string, string>> GetAllTagTextPairs()
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
List<KeyValuePair<string, string>> allText = new List<KeyValuePair<string, string>>();
foreach (TextPack textPack in textPacks[Language])
{
allText.AddRange(textPack.GetAllTagTextPairs());
}
return allText;
}
List<KeyValuePair<string, string>> allText = new List<KeyValuePair<string, string>>();
foreach (TextPack textPack in textPacks[Language])
{
allText.AddRange(textPack.GetAllTagTextPairs());
}
return allText;
}
public static string ReplaceGenderPronouns(string text, Gender gender)
@@ -689,35 +729,41 @@ namespace Barotrauma
#if DEBUG
public static void CheckForDuplicates(string lang)
{
if (!textPacks.ContainsKey(lang))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
if (!textPacks.ContainsKey(lang))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.CheckForDuplicates(packIndex);
packIndex++;
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.CheckForDuplicates(packIndex);
packIndex++;
}
}
}
public static void WriteToCSV()
{
string lang = "English";
if (!textPacks.ContainsKey(lang))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
string lang = "English";
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.WriteToCSV(packIndex);
packIndex++;
if (!textPacks.ContainsKey(lang))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.WriteToCSV(packIndex);
packIndex++;
}
}
}
#endif
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma
{
public static class TaskPool
{
private struct TaskAction
{
public Task Task;
public Action<Task, object> OnCompletion;
public object UserData;
}
private static List<TaskAction> taskActions = new List<TaskAction>();
private static void AddInternal(Task task, Action<Task, object> onCompletion, object userdata)
{
lock (taskActions)
{
taskActions.Add(new TaskAction() { Task = task, OnCompletion = onCompletion, UserData = userdata });
}
}
public static void Add(Task task, Action<Task> onCompletion)
{
AddInternal(task, (Task t, object obj) => { onCompletion(t); }, null);
}
public static void Add<U>(Task task, U userdata, Action<Task, U> onCompletion) where U : class
{
AddInternal(task, (Task t, object obj) => { onCompletion(t, (U)obj); }, userdata);
}
public static void Add<T>(Task<T> task, Action<Task<T>> onCompletion)
{
AddInternal(task, (Task t, object obj) => { onCompletion((Task<T>)t); }, null);
}
public static void Add<T,U>(Task<T> task, U userdata, Action<Task<T>, U> onCompletion) where U : class
{
AddInternal(task, (Task t, object obj) => { onCompletion((Task<T>)t, (U)obj); }, userdata);
}
public static void Update()
{
lock (taskActions)
{
for (int i = 0; i < taskActions.Count; i++)
{
if (taskActions[i].Task.IsCompleted)
{
taskActions[i].OnCompletion?.Invoke(taskActions[i].Task, taskActions[i].UserData);
taskActions.RemoveAt(i);
i--;
}
}
}
}
public static void PrintTaskExceptions(Task task, string msg)
{
DebugConsole.ThrowError(msg);
foreach (Exception e in task.Exception.InnerExceptions)
{
DebugConsole.ThrowError(e.Message + "\n" + e.StackTrace);
}
}
}
}
@@ -437,5 +437,90 @@ namespace Barotrauma
IEnumerable<string> filtered = splitted.SkipWhile(part => part != currentFolder).Skip(1);
return string.Join("/", filtered);
}
public static string EscapeCharacters(string str)
{
return str.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
public static string UnescapeCharacters(string str)
{
string retVal = "";
for (int i=0;i<str.Length;i++)
{
if (str[i] != '\\')
{
retVal += str[i];
}
else if (i+1<str.Length)
{
if (str[i+1] == '\\')
{
retVal += "\\";
}
else if (str[i+1] == '\"')
{
retVal += "\"";
}
i++;
}
}
return retVal;
}
public static string ParseQuotedArgument(string[] arguments, int startIndex, out int endIndex)
{
#if WINDOWS
endIndex = startIndex + 1;
return arguments[startIndex];
#else
string retVal = "";
int currIndex = startIndex;
bool escaped = false;
if (arguments[startIndex][0] != '\"')
{
endIndex = startIndex+1;
return UnescapeCharacters(arguments[startIndex]);
}
while (currIndex < arguments.Length)
{
for (int i=currIndex == startIndex ? 1 : 0;i<arguments[currIndex].Length;i++)
{
if (!escaped)
{
if (arguments[currIndex][i] == '\\')
{
escaped = true;
}
else if (arguments[currIndex][i] == '\"')
{
endIndex = currIndex+1;
return UnescapeCharacters(retVal);
}
}
else
{
escaped = false;
}
retVal += arguments[currIndex][i];
}
retVal += " ";
currIndex++;
}
endIndex = arguments.Length;
return retVal;
#endif
}
public static string[] MergeArguments(string[] arguments)
{
List<string> mergedArgs = new List<string>();
for (int i=0;i<arguments.Length;)
{
mergedArgs.Add(ParseQuotedArgument(arguments, i, out i));
}
return mergedArgs.ToArray();
}
}
}
Binary file not shown.
Binary file not shown.
+59 -2
View File
@@ -1,3 +1,61 @@
---------------------------------------------------------------------------------------------------------
v0.9.6.0
---------------------------------------------------------------------------------------------------------
Networking/multiplayer fixes:
- Fixed the game occasionally freezing when joining a server.
- Fixed a bug that occasionally caused clients to disconnect with an "entity not found" error in the multiplayer. In technical terms, the issue occurred when the server spawned an entity with a given ID, sent a network event for that entity, and then spawned another entity that took up the ID of the previous entity. This could happen, for example, if in the multiplayer campaign a player happens to get an item with the same ID as an item in the inventory of another player who isn't currently present, and the other player joins the server. There are still most likely additional bugs that can cause similar error messages, so this fix will probably not get rid of the errors entirely.
- Fixed start button staying disabled in the server lobby when switching from the campaign mode to another game mode.
- Fixed clients occasionally getting disconnected due to the campaign store. Happened if a location had changed from one type to another (for example, natural formation to outpost), the players had bought something that wasn't originally available from the location and a new client joined.
- Fixed clients occasionally being prompted to download subs from the server even if they already have a matching sub.
- Fixed server having a password even if the password box is left empty in the in-game "host server" menu when hosting with the Linux version.
- Fixed server name being shortened to one word when hosting from the in-game "host server" menu with the Linux version.
- Fixed server name length not being restricted when set from serversettings.xml or using console commands.
- Fixed "ready to start" tickbox disappearing if a client joins while a round is running.
- Fixed traitors remaining as traitors client-side after restarting the round, allowing them to access the sabotage interface despite not having a traitor objective.
- Fixed characters occasionally not selecting/deselecting a periscope client-side, causing them to walk in place or not grab the periscope.
- Fixed male asian heads not appearing in the server lobby.
- Fixed campaign servers never being filtered out from the server browser when filtering based on game mode.
- Fixed clients not displaying the "could not connect" popup if the initial connection to a server times out.
- Fixed door weld state occasionally getting desynced, causing the door to be welded server-side but possible to open client-side.
Electricity fixes:
- Fixed junction boxes not passing signals to relays.
- Fixed broken junction boxes and relays carrying power.
- Fixed devices not receiving power if they're connected directly to batteries/supercapacitors with no relays or junction boxes in between.
- Fixed light components that receive power from somewhere else than a power input connection not going out when they lose power (e.g. lights on the diving suits).
- Fixed pumps and batteries operating/recharging faster when receiving overvoltage, with no upper limit, making it possible to for example operate pumps at 10x the speed by connecting them to a relay that's receiving 10x more power than is being drawn from it.
- Fixed LightComponents being toggled twice when they receive a signal to the toggle connection.
Misc fixes:
- Fixed "last used" listbox overlapping with the entity visibility tickboxes in the submarine editor on low resolutions.
- Fixed misaligned colliders on the "Shell A Cap 0 deg A/B" wall pieces.
- Fixed links disappearing between linked subs and docking ports when loading in the sub editor.
- Fixed loading freezing for up to 10 seconds if the game cannot fetch the remote content for the main menu (update notifications, changelogs, etc).
- Fixed lights on devices (junction boxes, nav terminals, reactor) being rendered on top of characters.
- Fixed lights lagging a little behind moving objects (e.g. diving suit light).
- Fixed characters being able to grab/heal others when handcuffed.
- Character editor: don't switch to the limb mode when clicking a limb in the joint edit mode. Fixes difficulties in using the joint angle widgets when the widgets are interloping limb source rects.
- Character editor: Fixed the texture path field not being copied if the source character has no texture path defined. In this case, we can use a texture path found in one of the limbs.
- Fixed bots incorrectly reporting problems in the room they are in instead of the room the issues is spotted at.
- Fixed bots reporting broken devices or asking medical attendance even if they are taking care of those things.
- Fixed bots saying "put out a fire in hull" instead of using the name of the room when they extinguish a fire.
- Fixed AITargets staing active even if the entity is not active (for example, the sonar could still be heard by monsters after it's turned off).
- The bots now only report about issues that have not been already been reported.
- Fixed monsters attacking doors that are open.
- Monsters keep pursuing the target if the behavior is "pursue" even when they cannot attack.
- Bots stop operating devices if a player starts operating them (unless they have been ordered to operate the device).
- Fixed bots playing the rewiring sound when repairing an item with a screwdriver.
- Fixed bots sometimes stopping behind a closed door when trying to extinguish a fire.
- Fixed chat messages occasionally extending below the lower bound of the chatbox.
- Fixed searchlight and turret lights disappearing before the light is fully off-screen.
- Waypoint fixes in Kastrull and the drone.
- Fixed Kastrull flooding when the drone is undocked.
- Fixed ballast pumps in Kastrull's drone being off by default.
- Fixed enormous in-game ballistic helmet sprite.
- Fire extinguishers can't be placed inside toolboxes anymore.
- Fixed lobby screen not scaling correctly after changing to a bigger resolution.
---------------------------------------------------------------------------------------------------------
v0.9.5.1
---------------------------------------------------------------------------------------------------------
@@ -8,8 +66,7 @@ v0.9.5.1
- Nerfed Hammerhead Spawns.
- Fixed occasional disconnections when the Hammerhead Matriarch releases her spawns.
- Fixed Hammerhead Matriarch exploding twice when it attacks.
- Fixed bots sometimes getting stuck in an objective loop, causing them to repeatedly drop and pick up
diving suits or spam doors.
- Fixed bots sometimes getting stuck in an objective loop, causing them to repeatedly drop and pick up diving suits or spam doors.
- Fixed bots being allowed to go outside without a diving suit.
- Fixed characters with a rectangular main collider being unable to use path finding.
- Fixed microphone volume scrollbar resetting to the maximum value when opening the settings menu.