(7788ec72a) Test issuing orders automatically.

This commit is contained in:
Joonas Rikkonen
2019-05-16 05:03:49 +03:00
parent d00e2975ba
commit 60f52375e6
202 changed files with 2714 additions and 5825 deletions
@@ -14,11 +14,6 @@ namespace Barotrauma
{
public static bool DisableEnemyAI;
/// <summary>
/// Enable the character to attack the outposts and the characters inside them. Disabled by default.
/// </summary>
public bool TargetOutposts;
class WallTarget
{
public Vector2 Position;
@@ -122,7 +117,6 @@ namespace Barotrauma
private readonly float memoryFadeTime = 0.5f;
public LatchOntoAI LatchOntoAI { get; private set; }
public SwarmBehavior SwarmBehavior { get; private set; }
public bool AttackHumans
{
@@ -216,10 +210,6 @@ namespace Barotrauma
case "latchonto":
LatchOntoAI = new LatchOntoAI(subElement, this);
break;
case "swarm":
case "swarmbehavior":
SwarmBehavior = new SwarmBehavior(subElement, this);
break;
case "targetpriority":
targetingPriorities.Add(subElement.GetAttributeString("tag", "").ToLowerInvariant(), new TargetingPriority(subElement));
break;
@@ -320,9 +310,8 @@ namespace Barotrauma
{
State = AIState.Idle;
}
else if (Character.Health < fleeHealthThreshold && SwarmBehavior == null)
else if (Character.Health < fleeHealthThreshold)
{
// Don't flee from damage if in a swarm.
State = AIState.Escape;
}
else if (targetingPriority != null)
@@ -331,6 +320,8 @@ namespace Barotrauma
}
}
LatchOntoAI?.Update(this, deltaTime);
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
{
State = AIState.Idle;
@@ -369,14 +360,11 @@ namespace Barotrauma
throw new NotImplementedException();
}
LatchOntoAI?.Update(this, deltaTime);
IsSteeringThroughGap = false;
if (SwarmBehavior != null)
{
SwarmBehavior.IsActive = State == AIState.Idle && Character.CurrentHull == null;
SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime);
}
// Just some debug code that makes the characters to follow the mouse cursor
//run = true;
//Vector2 mousePos = ConvertUnits.ToSimUnits(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
//steeringManager.SteeringSeek(mousePos, Character.AnimController.GetCurrentSpeed(run));
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run));
}
@@ -499,7 +487,7 @@ namespace Barotrauma
}
else
{
if (!IsLatchedOnSub)
if (!IsProperlyLatchedOnSub)
{
UpdateWallTarget();
}
@@ -558,7 +546,7 @@ namespace Barotrauma
{
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
if (section?.gap != null && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
if (section?.gap != null && section.gap.IsRoomToRoom && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
{
return;
}
@@ -799,21 +787,9 @@ namespace Barotrauma
}
}
public bool IsSteeringThroughGap { get; private set; }
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
{
IsSteeringThroughGap = true;
SelectedAiTarget = wall.AiTarget;
wallTarget = null;
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
Hull targetHull = section.gap?.FlowTargetHull;
float distance = Vector2.Distance(Character.WorldPosition, targetWorldPos);
float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height);
if (distance > maxDistance)
{
return false;
}
if (targetHull != null)
{
if (wall.IsHorizontal)
@@ -824,7 +800,16 @@ namespace Barotrauma
{
targetWorldPos.X = targetHull.WorldRect.Center.X;
}
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
if (steeringManager is IndoorsSteeringManager)
{
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
}
else
{
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetWorldPos));
}
return true;
}
return false;
@@ -1056,43 +1041,19 @@ namespace Barotrauma
#region Targeting
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
private bool IsProperlyLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub && SelectedAiTarget?.Entity == wallTarget?.Structure;
//goes through all the AItargets, evaluates how preferable it is to attack the target,
//whether the Character can see/hear the target and chooses the most preferable target within
//sight/hearing range
public AITarget UpdateTargets(Character character, out TargetingPriority priority)
{
if ((SelectedAiTarget != null || wallTarget != null) && IsLatchedOnSub)
if (IsProperlyLatchedOnSub)
{
var wall = SelectedAiTarget.Entity as Structure;
if (wall == null)
{
wall = wallTarget?.Structure;
}
// The target is not a wall or it's not the same as we are attached to -> release
bool releaseTarget = wall == null || !wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB);
if (!releaseTarget)
{
for (int i = 0; i < wall.Sections.Length; i++)
{
if (CanPassThroughHole(wall, i))
{
releaseTarget = true;
}
}
}
if (releaseTarget)
{
SelectedAiTarget = null;
wallTarget = null;
LatchOntoAI.DeattachFromBody();
}
else if (SelectedAiTarget?.Entity == wallTarget?.Structure)
{
// If attached to a valid target, just keep the target.
// Priority not used in this case.
priority = null;
return SelectedAiTarget;
}
// If attached to a valid target, just keep the target.
// Priority not used in this case.
priority = null;
return SelectedAiTarget;
}
AITarget newTarget = null;
priority = null;
@@ -1107,10 +1068,9 @@ namespace Barotrauma
continue;
}
if (target.Type == AITarget.TargetType.HumanOnly) { continue; }
if (!TargetOutposts)
{
if (target.Entity.Submarine != null && target.Entity.Submarine.IsOutpost) { continue; }
}
// Don't attack outposts.
if (target.Entity.Submarine != null && target.Entity.Submarine.IsOutpost) { continue; }
Character targetCharacter = target.Entity as Character;
//ignore the aitarget if it is the Character itself
if (targetCharacter == character) continue;
@@ -1175,7 +1135,7 @@ namespace Barotrauma
else if (target.Entity != null)
{
//skip the target if it's a room and the character is already inside a sub
if (character.CurrentHull != null && target.Entity is Hull) { continue; }
if (character.CurrentHull != null && target.Entity is Hull) continue;
Door door = null;
if (target.Entity is Item item)
@@ -1204,37 +1164,23 @@ namespace Barotrauma
// Ignore structures that doesn't have a body (not walls)
continue;
}
if (s.IsPlatform)
{
continue;
}
if (character.CurrentHull != null)
{
// Ignore walls when inside.
continue;
}
valueModifier = 1;
float wallMaxHealth = 400; // Anything more than this is ignored -> 200 = 1
// Prefer weaker targets.
valueModifier *= MathHelper.Lerp(1.5f, 0.5f, MathUtils.InverseLerp(0, 1, s.Health / wallMaxHealth));
// Ignore walls when inside.
valueModifier = character.CurrentHull == null ? 1 : 0;
if (aggressiveBoarding)
{
var hulls = s.Submarine.GetHulls(false);
for (int i = 0; i < s.Sections.Length; i++)
{
var section = s.Sections[i];
if (section.gap != null)
if (CanPassThroughHole(s, i))
{
if (CanPassThroughHole(s, i))
{
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null && hulls.Any(h => h.Rect.Intersects(section.rect));
valueModifier *= leadsInside ? 5 : 0;
}
else
{
// up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
// Ignore walls that can be passed through
valueModifier = 0;
break;
}
else if (section.gap != null)
{
// up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
}
}
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -54,6 +55,28 @@ namespace Barotrauma
objectiveManager = new AIObjectiveManager(c);
objectiveManager.AddObjective(new AIObjectiveFindSafety(c));
objectiveManager.AddObjective(new AIObjectiveIdle(c));
// TODO: do this only when the player hasn't issued an order for a while
foreach (var automaticOrder in c.Info.Job.Prefab.AutomaticOrders)
{
var orderPrefab = Order.PrefabList.Find(o => o.AITag == automaticOrder.aiTag);
// TODO: Similar code is used in CrewManager:815-> DRY
var matchingItems = orderPrefab.ItemIdentifiers.Any() ?
Item.ItemList.FindAll(it => orderPrefab.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(orderPrefab.ItemIdentifiers)) :
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == orderPrefab.ItemComponentType));
matchingItems.RemoveAll(it => it.Submarine != c.Submarine);
var item = matchingItems.GetRandom();
var order = new Order(orderPrefab, item ?? c.CurrentHull as Entity, item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType));
SetOrder(order, automaticOrder.option, c, true);
//#if CLIENT
// GameMain.GameSession?.CrewManager?.SetCharacterOrder(c, order, automaticOrder.option, c);
//#endif
// if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(order, order.FadeOutTime))
// {
// Character.Speak(order.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: true), ChatMessageType.Order);
// }
}
updateObjectiveTimer = Rand.Range(0.0f, UpdateObjectiveInterval);
@@ -267,7 +290,7 @@ 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);
newOrder.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order);
}
}
}
@@ -286,18 +309,18 @@ namespace Barotrauma
if (Character.PressureTimer > 50.0f && Character.CurrentHull != null)
{
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.DisplayName), null, 0, "pressure", 30.0f);
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f);
}
}
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
// Damage from falling etc.
if (Character.LastDamageSource == null) { return; }
float damage = attackResult.Damage;
if (damage <= 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed)
{
// Ignore damage from falling etc that we shouldn't react to.
if (Character.LastDamageSource == null) { return; }
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
}
else if (IsFriendly(attacker))
@@ -223,10 +223,8 @@ namespace Barotrauma
}
}
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
//only humanoids can climb ladders
if (!isDiving && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
if (character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
{
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item &&
currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
@@ -236,7 +234,7 @@ namespace Barotrauma
}
var collider = character.AnimController.Collider;
if (character.IsClimbing && !isDiving)
if (character.IsClimbing)
{
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
@@ -280,12 +278,6 @@ namespace Barotrauma
}
else if (character.AnimController.InWater)
{
// If the character is underwater, we don't need the ladders anymore
if (character.IsClimbing && isDiving)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
}
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
{
currentPath.SkipToNextNode();
@@ -396,18 +388,6 @@ namespace Barotrauma
buttonPressCooldown = ButtonPressInterval;
break;
}
else
{
if (!door.HasRequiredItems(character, false) && shouldBeOpen)
{
currentPath.Unreachable = true;
return;
}
door.Item.TryInteract(character, false, true, true);
buttonPressCooldown = ButtonPressInterval;
break;
}
}
}
}
@@ -428,18 +408,20 @@ namespace Barotrauma
//door closed and the character can't open doors -> node can't be traversed
if (!canOpenDoors || character.LockHands) { return null; }
if (!canBreakDoors)
{
//door closed and the character can't open doors -> node can't be traversed
if (!canOpenDoors || character.LockHands) return null;
var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents<Controller>();
if (!doorButtons.Any())
{
if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; }
}
if (!doorButtons.Any()) return null;
foreach (Controller button in doorButtons)
{
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
Math.Sign(node.Position.X - nextNode.Position.X)) { continue; }
Math.Sign(node.Position.X - nextNode.Position.X)) continue;
if (!button.HasRequiredItems(character, false)) { return null; }
if (!button.HasRequiredItems(character, false)) return null;
}
}
}
@@ -54,7 +54,7 @@ namespace Barotrauma
get { return attachJoints.Count > 0; }
}
public bool IsAttachedToSub => IsAttached && (attachTargetBody?.UserData is Submarine || attachTargetBody?.UserData is Entity entity && entity.Submarine != null);
public bool IsAttachedToSub => IsAttached && attachTargetBody?.UserData is Entity entity && (entity is Submarine sub || entity?.Submarine != null);
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
{
@@ -190,7 +190,7 @@ namespace Barotrauma
case AIController.AIState.Attack:
if (enemyAI.AttackingLimb != null)
{
if (attachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
if (attachToSub && wallAttachPos != Vector2.Zero && attachTargetBody != null)
{
// is not attached or is attached to something else
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
@@ -44,7 +44,6 @@ namespace Barotrauma
private AIObjectiveContainItem reloadWeaponObjective;
private Hull retreatTarget;
private AIObjectiveGoTo retreatObjective;
private AIObjectiveFindSafety findSafety;
private float coolDownTimer;
@@ -61,9 +60,7 @@ namespace Barotrauma
{
Enemy = enemy;
coolDownTimer = CoolDown;
findSafety = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>();
findSafety.Priority = 0;
findSafety.unreachable.Clear();
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 0;
Mode = mode;
if (Enemy == null)
{
@@ -178,7 +175,7 @@ namespace Barotrauma
{
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
{
retreatTarget = findSafety.FindBestHull(new List<Hull>() { character.CurrentHull });
retreatTarget = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(new List<Hull>() { character.CurrentHull });
}
if (retreatTarget != null)
{
@@ -280,6 +277,7 @@ namespace Barotrauma
{
abandon = true;
SteeringManager.Reset();
//HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
public override bool IsCompleted()
@@ -79,44 +79,26 @@ 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;
if (inRange || useExtinquisherTimer > 0.0f)
if (targetHull == character.CurrentHull && (inRange || useExtinquisherTimer > 0.0f))
{
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f)
{
useExtinquisherTimer = 0.0f;
}
if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f;
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
character.SetInput(InputType.Aim, false, true);
}
Limb sightLimb = null;
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
}
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
if (!character.CanSeeTarget(fs, sightLimb))
{
move = true;
}
else
{
move = false;
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
}
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
}
return;
}
if (move)
else
{
//go to the first firesource
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
@@ -127,8 +109,8 @@ namespace Barotrauma
{
gotoObjective.TryComplete(deltaTime);
}
break;
}
break;
}
}
}
@@ -18,7 +18,7 @@ namespace Barotrauma
const float SearchHullInterval = 3.0f;
const float clearUnreachableInterval = 30;
public readonly List<Hull> unreachable = new List<Hull>();
private List<Hull> unreachable = new List<Hull>();
private float currenthullSafety;
private float unreachableClearTimer;
@@ -60,16 +60,11 @@ namespace Barotrauma
else
{
divingGearObjective = null;
// Reduce the timer so that we get a safe hull target faster.
searchHullTimer = Math.Min(1, searchHullTimer);
// Reset the timer so that we get a safe hull target.
searchHullTimer = 0;
}
}
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
{
searchHullTimer = Math.Min(1, searchHullTimer);
}
if (unreachableClearTimer > 0)
{
unreachableClearTimer -= deltaTime;
@@ -84,7 +79,7 @@ namespace Barotrauma
{
searchHullTimer -= deltaTime;
}
else
else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
{
var bestHull = FindBestHull();
if (bestHull != null && bestHull != currentHull)
@@ -193,17 +188,10 @@ namespace Barotrauma
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
hullSafety *= distanceFactor;
//skip the hull if the safety is already less than the best hull
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
if (hullSafety < bestValue) { continue; }
// Each unsafe node reduces the hull safety value.
// Ignore current hull, because otherwise the would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Unreachable)
{
unreachable.Add(hull);
continue;
}
if (path.Unreachable) { continue; }
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
@@ -231,6 +219,7 @@ namespace Barotrauma
}
}
}
// Huge preference for closer targets
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
@@ -101,22 +101,10 @@ namespace Barotrauma
HumanAIController.AnimController.Crouching = true;
}
//float reach = HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range);
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
if (canReach)
{
Limb sightLimb = null;
if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.RightHand))
{
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
}
else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand))
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
canReach = character.CanSeeTarget(leak, sightLimb);
}
else
bool cannotReach = ConvertUnits.ToSimUnits(gapDiff.Length()) > reach;
if (cannotReach)
{
if (gotoObjective != null)
{
@@ -142,6 +130,7 @@ namespace Barotrauma
AddSubObjective(gotoObjective);
}
}
canReach = character.CanSeeTarget(leak, sightLimb);
}
if (gotoObjective == null || gotoObjective.IsCompleted())
{
@@ -26,9 +26,6 @@ namespace Barotrauma
private float standStillTimer;
private float walkDuration;
private readonly List<Hull> targetHulls = new List<Hull>(20);
private readonly List<float> hullWeights = new List<float>(20);
public AIObjectiveIdle(Character character) : base(character, "")
{
standStillTimer = Rand.Range(-10.0f, 10.0f);
@@ -109,7 +106,7 @@ namespace Barotrauma
if (isCurrentHullOK)
{
// Check that there is no unsafe or forbidden hulls on the way to the target
// Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull.
// Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition);
if (path.Unreachable ||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull)))
@@ -131,8 +128,8 @@ namespace Barotrauma
character.AIController.SelectTarget(currentTarget.AiTarget);
string errorMsg = null;
#if DEBUG
bool isRoomNameFound = currentTarget.DisplayName != null;
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
bool isRoomNameFound = currentTarget.RoomName != null;
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")";
#endif
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg);
PathSteering.SetPath(path);
@@ -233,9 +230,13 @@ namespace Barotrauma
}
}
private readonly List<Hull> targetHulls = new List<Hull>(20);
private readonly List<float> hullWeights = new List<float>(20);
private void FindTargetHulls()
{
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
targetHulls.Clear();
hullWeights.Clear();
foreach (var hull in Hull.hullList)
@@ -265,6 +266,7 @@ namespace Barotrauma
hullWeights.Add(weight);
}
}
}
private bool IsForbidden(Hull hull)
@@ -83,12 +83,12 @@ namespace Barotrauma
return;
}
}
if (repairTool == null)
{
FindRepairTool();
}
if (character.CurrentHull == Item.CurrentHull && character.CanInteractWith(Item))
if (character.CanInteractWith(Item))
{
if (repairTool == null)
{
FindRepairTool();
}
if (repairTool != null)
{
OperateRepairTool(deltaTime);
@@ -83,7 +83,7 @@ namespace Barotrauma
if (character.SelectedCharacter == null)
{
character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget")
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.RoomName),
null, 1.0f,
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
}
@@ -3,6 +3,7 @@ using System.Linq;
namespace Barotrauma
{
// TODO: Ensure that this works well enough. Consider using AIObjectiveLoop class.
class AIObjectiveRescueAll : AIObjective
{
public override string DebugTag => "rescue all";
@@ -293,27 +293,6 @@ namespace Barotrauma
return;
}
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
//stop dragging if there's something between the pull limb and the target
Vector2 sourceSimPos = mouthLimb.SimPosition;
Vector2 targetSimPos = target.SimPosition;
if (character.Submarine != null && character.SelectedCharacter.Submarine == null)
{
targetSimPos -= character.Submarine.SimPosition;
}
else if (character.Submarine == null && character.SelectedCharacter.Submarine != null)
{
sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition;
}
var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
if (body != null)
{
character.DeselectCharacter();
return;
}
}
Character targetCharacter = target;
float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f;
eatTimer += deltaTime * eatSpeed;
@@ -609,7 +588,6 @@ namespace Barotrauma
}
}
float prevWalkPos = WalkPos;
WalkPos -= MainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f);
Vector2 transformedStepSize = Vector2.Zero;
@@ -645,11 +623,6 @@ namespace Barotrauma
bool playFootstepSound = false;
if (limb.type == LimbType.LeftFoot)
{
if (Math.Sign(Math.Sin(prevWalkPos)) > 0 && Math.Sign(transformedStepSize.Y) < 0)
{
playFootstepSound = true;
}
limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
limb.DebugTargetPos = footPos + new Vector2(
transformedStepSize.X + movement.X * 0.1f,
@@ -658,20 +631,13 @@ namespace Barotrauma
}
else if (limb.type == LimbType.RightFoot)
{
if (Math.Sign(Math.Sin(prevWalkPos)) < 0 && Math.Sign(transformedStepSize.Y) > 0)
{
playFootstepSound = true;
}
limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
limb.DebugTargetPos = footPos + new Vector2(
-transformedStepSize.X + movement.X * 0.1f,
(-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f);
limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
}
#if CLIENT
if (playFootstepSound) { PlayImpactSound(limb); }
#endif
if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
{
SmoothRotateWithoutWrapping(limb,
@@ -751,6 +717,22 @@ namespace Barotrauma
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
{
//make sure the angle "has the same number of revolutions" as the reference limb
//(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints)
while (referenceLimb.Rotation - angle > MathHelper.TwoPi)
{
angle += MathHelper.TwoPi;
}
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
{
angle -= MathHelper.TwoPi;
}
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
public override void Flip()
{
base.Flip();
@@ -534,12 +534,7 @@ namespace Barotrauma
Limb leftLeg = GetLimb(LimbType.LeftLeg);
Limb rightLeg = GetLimb(LimbType.RightLeg);
float limpAmount =
character.CharacterHealth.GetAfflictionStrength("damage", leftFoot, true) +
character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
limpAmount = MathHelper.Clamp(limpAmount / 100.0f, 0.0f, 1.0f);
float walkCycleMultiplier = 1.0f;
if (Stairs != null)
{
@@ -560,8 +555,7 @@ namespace Barotrauma
//TODO: take into account that the feet aren't necessarily in CurrentHull
//full slowdown (1.5f) when water is up to the torso
surfaceY = ConvertUnits.ToSimUnits(currentHull.Surface);
float bottomPos = Math.Max(colliderPos.Y, currentHull.Rect.Y - currentHull.Rect.Height);
slowdownAmount = MathHelper.Clamp((surfaceY - bottomPos) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
slowdownAmount = MathHelper.Clamp((surfaceY - colliderPos.Y) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
}
float maxSpeed = Math.Max(TargetMovement.Length() - slowdownAmount, 1.0f);
@@ -570,10 +564,11 @@ namespace Barotrauma
float walkPosX = (float)Math.Cos(WalkPos);
float walkPosY = (float)Math.Sin(WalkPos);
Vector2 stepSize = StepSize.Value;
stepSize.X *= walkPosX;
stepSize.Y *= walkPosY;
stepSize.Y *= walkPosY;
float footMid = colliderPos.X;
if (limpAmount > 0.0f)
@@ -594,7 +589,7 @@ namespace Barotrauma
movement.Y = 0.0f;
if (torso == null) { return; }
bool isNotRemote = true;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer;
@@ -698,7 +693,7 @@ namespace Barotrauma
//make the character limp if the feet are damaged
float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true);
footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f));
footPos *= MathHelper.Lerp(1.0f, 0.5f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
if (onSlope && Stairs == null)
{
@@ -788,19 +783,13 @@ namespace Barotrauma
//get the upper arm to point downwards
var arm = GetLimb(armType);
if (Math.Abs(arm.body.AngularVelocity) < 10.0f)
{
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.1f, 0.1f), arm.Mass * 10.0f);
}
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.1f, 0.1f), arm.Mass * 10.0f);
//get the elbow to a neutral rotation
if (Math.Abs(hand.body.AngularVelocity) < 10.0f)
{
LimbJoint elbow =
LimbJoint elbow =
GetJointBetweenLimbs(armType, hand.type) ??
GetJointBetweenLimbs(armType, foreArmType);
hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f);
}
hand.body.ApplyTorque(-elbow.JointAngle * hand.Mass * 10.0f);
}
}
}
@@ -1138,12 +1127,12 @@ namespace Barotrauma
#if CLIENT
if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", leftFoot.WorldPosition, hullGuess: currentHull);
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: leftFoot.WorldPosition);
leftFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
if (Math.Abs(rightFootPos - prevRightFootPos) > stepHeight && rightFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", rightFoot.WorldPosition, hullGuess: currentHull);
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: rightFoot.WorldPosition);
rightFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
#endif
@@ -1194,8 +1183,7 @@ namespace Barotrauma
isClimbing = false;
}
}
else if ((character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right)) &&
(!character.IsKeyDown(InputType.Up) && !character.IsKeyDown(InputType.Down)))
else if (character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right))
{
isClimbing = false;
}
@@ -84,7 +84,6 @@ namespace Barotrauma
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
//DebugConsole.NewMessage("[RagollParams] Using the default folder.");
folder = GetDefaultFolder(speciesName);
}
return folder;
@@ -674,6 +674,8 @@ namespace Barotrauma
velocity -= ((Submarine)f2.Body.UserData).Velocity;
}
if (character.Submarine == null && f2.Body.UserData is Submarine) velocity -= ((Submarine)f2.Body.UserData).Velocity;
float impact = Vector2.Dot(velocity, -normal);
if (f1.Body == Collider.FarseerBody || !Collider.Enabled)
{
@@ -692,11 +694,6 @@ namespace Barotrauma
character.AddDamage(impactPos, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate((impact - ImpactTolerance) * 10.0f) }, 0.0f, true);
strongestImpact = Math.Max(strongestImpact, impact - ImpactTolerance);
character.ApplyStatusEffects(ActionType.OnImpact, 1.0f);
//briefly disable impact damage
//otherwise the character will take damage multiple times when for example falling,
//because we use the velocity of the collider to determine the impact
//(i.e. the character would take damage until the collider hits the floor and stops)
character.DisableImpactDamageTimer = 0.25f;
}
}
}
@@ -704,7 +701,7 @@ namespace Barotrauma
ImpactProjSpecific(impact, f1.Body);
}
public void SeverLimbJoint(LimbJoint limbJoint, bool playSound = true)
public void SeverLimbJoint(LimbJoint limbJoint)
{
if (!limbJoint.CanBeSevered || limbJoint.IsSevered)
{
@@ -733,7 +730,7 @@ namespace Barotrauma
}
}
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound = true);
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint);
private void GetConnectedLimbs(List<Limb> connectedLimbs, List<LimbJoint> checkedJoints, Limb limb)
{
@@ -80,13 +80,13 @@ namespace Barotrauma
public HitDetection HitDetectionType { get; private set; }
[Serialize(AIBehaviorAfterAttack.FallBack, true), Editable(ToolTip = "The preferred AI behavior after the attack.")]
public AIBehaviorAfterAttack AfterAttack { get; set; }
public AIBehaviorAfterAttack AfterAttack { get; private set; }
[Serialize(false, true), Editable(ToolTip = "Should the ai try to reverse when aiming with this attack?")]
public bool Reverse { get; private set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target before the AI tries to attack.")]
public float Range { get; set; }
public float Range { get; private set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target to do damage. In distance based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired.")]
public float DamageRange { get; set; }
@@ -95,19 +95,19 @@ namespace Barotrauma
public float Duration { get; private set; }
[Serialize(5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "How long the AI waits between the attacks.")]
public float CoolDown { get; set; } = 5;
public float CoolDown { get; private set; } = 5;
[Serialize(0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "Used as the attack cooldown between different kind of attacks. Does not have effect, if set to 0.")]
public float SecondaryCoolDown { get; set; } = 0;
public float SecondaryCoolDown { get; private set; } = 0;
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "Random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases).")]
public float CoolDownRandomFactor { get; private set; } = 0;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float StructureDamage { get; set; }
public float StructureDamage { get; private set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float ItemDamage { get; set; }
public float ItemDamage { get; private set; }
/// <summary>
/// Legacy support. Use Afflictions.
@@ -283,7 +283,6 @@ namespace Barotrauma
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
continue;
}
}
else
@@ -293,7 +292,6 @@ namespace Barotrauma
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.");
continue;
}
}
@@ -426,7 +426,7 @@ namespace Barotrauma
}
}
public bool CanSpeak;
private bool canSpeak;
private bool speechImpedimentSet;
@@ -436,7 +436,7 @@ namespace Barotrauma
{
get
{
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
if (!canSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
return speechImpediment;
}
set
@@ -710,7 +710,7 @@ namespace Barotrauma
displayName = TextManager.Get($"Character.{Path.GetFileName(Path.GetDirectoryName(file))}", true);
IsHumanoid = doc.Root.GetAttributeBool("humanoid", false);
CanSpeak = doc.Root.GetAttributeBool("canspeak", false);
canSpeak = doc.Root.GetAttributeBool("canspeak", false);
needsAir = doc.Root.GetAttributeBool("needsair", false);
Noise = doc.Root.GetAttributeFloat("noise", 100f);
@@ -853,7 +853,7 @@ namespace Barotrauma
{
if (characterConfigFiles == null)
{
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character, searchAllContentPackages: true);
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character);
}
return characterConfigFiles;
}
@@ -1103,14 +1103,14 @@ namespace Barotrauma
if (leftFoot != null)
{
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", leftFoot, true);
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
}
var rightFoot = AnimController.GetLimb(LimbType.RightFoot);
if (rightFoot != null)
{
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
}
return speed;
@@ -1428,6 +1428,26 @@ namespace Barotrauma
return (wall == null || !wall.CastShadow) && (door == null || door.IsOpen);
}
public bool CanSeeCharacter(Character character, Vector2 sourceWorldPos)
{
Vector2 diff = ConvertUnits.ToSimUnits(character.WorldPosition - sourceWorldPos);
Body closestBody = null;
if (character.Submarine == null)
{
closestBody = Submarine.CheckVisibility(sourceWorldPos, sourceWorldPos + diff);
if (closestBody == null) return true;
}
else
{
closestBody = Submarine.CheckVisibility(character.WorldPosition, character.WorldPosition - diff);
if (closestBody == null) return true;
}
Structure wall = closestBody.UserData as Structure;
return wall == null || !wall.CastShadow;
}
public bool HasEquippedItem(Item item)
{
for (int i = 0; i < Inventory.Capacity; i++)
@@ -1568,8 +1588,9 @@ namespace Barotrauma
}
}
}
if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; }
if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) return false;
Pickable pickableComponent = item.GetComponent<Pickable>();
if (pickableComponent != null && (pickableComponent.Picker != null && !pickableComponent.Picker.IsDead)) { return false; }
@@ -2023,10 +2044,6 @@ namespace Barotrauma
{
IsRagdolled = IsForceRagdolled;
}
else if (IsRemotePlayer)
{
IsRagdolled = IsKeyDown(InputType.Ragdoll);
}
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f))
{
@@ -2155,8 +2172,7 @@ namespace Barotrauma
public void Speak(string message, ChatMessageType? messageType = null, float delay = 0.0f, string identifier = "", float minDurationBetweenSimilar = 0.0f)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(message)) { return; }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
//already sent a similar message a moment ago
if (!string.IsNullOrEmpty(identifier) && minDurationBetweenSimilar > 0.0f &&
@@ -2165,6 +2181,7 @@ namespace Barotrauma
{
return;
}
aiChatMessageQueue.Add(new AIChatMessage(message, messageType, identifier, delay));
}
@@ -2581,9 +2598,11 @@ namespace Barotrauma
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
#endif
CharacterList.Remove(this);
#if CLIENT
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
#endif
if (Controlled == this) { Controlled = null; }
CharacterList.Remove(this);
if (Inventory != null)
{
@@ -399,7 +399,7 @@ namespace Barotrauma
{
ID = idCounter;
idCounter++;
Name = element.GetAttributeString("name", "");
Name = element.GetAttributeString("name", "unnamed");
string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant();
File = element.GetAttributeString("file", "");
SourceElement = GetConfig(File).Root;
@@ -423,29 +423,6 @@ namespace Barotrauma
element.GetAttributeInt("beardindex", -1),
element.GetAttributeInt("moustacheindex", -1),
element.GetAttributeInt("faceattachmentindex", -1));
if (string.IsNullOrEmpty(Name))
{
if (SourceElement.Element("name") != null)
{
string firstNamePath = SourceElement.Element("name").GetAttributeString("firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
Name = ToolBox.GetRandomLine(firstNamePath);
}
string lastNamePath = SourceElement.Element("name").GetAttributeString("lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
if (Name != "") Name += " ";
Name += ToolBox.GetRandomLine(lastNamePath);
}
}
}
StartItemsGiven = element.GetAttributeBool("startitemsgiven", false);
string personalityName = element.GetAttributeString("personality", "");
ragdollFileName = element.GetAttributeString("ragdoll", string.Empty);
@@ -729,8 +706,6 @@ namespace Barotrauma
partial void LoadAttachmentSprites();
// TODO: change the formula so that it's not linear and so that it takes into account the usefulness of the skill
// -> give a weight to each skill, because some are much more valuable than others?
private int CalculateSalary()
{
if (Name == null || Job == null) return 0;
@@ -231,8 +231,7 @@ namespace Barotrauma
: afflictions.Concat(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter)));
}
private LimbHealth GetMatchingLimbHealth(Limb limb) => limbHealths[limb.HealthIndex];
private LimbHealth GetMathingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb));
private LimbHealth GetMathingLimbHealth(Affliction affliction) => limbHealths[Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb).HealthIndex];
/// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
@@ -266,12 +265,6 @@ namespace Barotrauma
public Affliction GetAffliction(string afflictionType, Limb limb)
{
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
{
DebugConsole.ThrowError("Limb health index out of bounds. Character\"" + Character.Name +
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
return null;
}
foreach (Affliction affliction in limbHealths[limb.HealthIndex].Afflictions)
{
if (affliction.Prefab.AfflictionType == afflictionType) return affliction;
@@ -473,13 +466,7 @@ namespace Barotrauma
private void AddLimbAffliction(Limb limb, Affliction newAffliction)
{
if (!newAffliction.Prefab.LimbSpecific || limb == null) return;
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
{
DebugConsole.ThrowError("Limb health index out of bounds. Character\"" + Character.Name +
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
return;
}
if (!newAffliction.Prefab.LimbSpecific) return;
AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction);
}
@@ -624,12 +611,6 @@ namespace Barotrauma
partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime);
public void SetVitality(float newVitality)
{
maxVitality = newVitality;
CalculateVitality();
}
public void CalculateVitality()
{
Vitality = MaxVitality;
@@ -1,17 +1,32 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
public class AutomaticOrder
{
public string aiTag;
public string option;
public float priority;
public AutomaticOrder(XElement element)
{
aiTag = element.GetAttributeString("aitag", null);
option = element.GetAttributeString("option", null);
priority = element.GetAttributeFloat("priority", 0);
}
}
partial class JobPrefab
{
public static List<JobPrefab> List;
public readonly XElement Items;
public readonly List<string> ItemNames;
public List<SkillPrefab> Skills;
public readonly XElement Items;
public readonly List<string> ItemNames = new List<string>();
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
public readonly List<AutomaticOrder> AutomaticOrders = new List<AutomaticOrder>();
[Serialize("1,1,1,1", false)]
public Color UIColor
@@ -111,10 +126,6 @@ namespace Barotrauma
Name = TextManager.Get("JobName." + Identifier);
Description = TextManager.Get("JobDescription." + Identifier);
ItemNames = new List<string>();
Skills = new List<SkillPrefab>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -155,7 +166,10 @@ namespace Barotrauma
foreach (XElement skillElement in subElement.Elements())
{
Skills.Add(new SkillPrefab(skillElement));
}
}
break;
case "automaticorders":
subElement.Elements().ForEach(order => AutomaticOrders.Add(new AutomaticOrder(order)));
break;
}
}
@@ -94,7 +94,7 @@ namespace Barotrauma
}
}
partial class Limb : ISerializableEntity, ISpatialEntity
partial class Limb : ISerializableEntity
{
// Note: not used
private const float LimbDensity = 15;
@@ -155,8 +155,6 @@ namespace Barotrauma
}
}
public Submarine Submarine => character.Submarine;
public Vector2 WorldPosition
{
get { return character.Submarine == null ? Position : Position + character.Submarine.Position; }
@@ -217,7 +217,6 @@ namespace Barotrauma
{
return corePackageRequiredFiles.All(fileType => Files.Any(file => file.Type == fileType));
}
public bool ContainsRequiredCorePackageFiles(out List<ContentType> missingContentTypes)
{
missingContentTypes = new List<ContentType>();
@@ -231,25 +230,6 @@ namespace Barotrauma
return missingContentTypes.Count == 0;
}
/// <summary>
/// Make sure all the files defined in the content package are present
/// </summary>
/// <returns></returns>
public bool VerifyFiles(out List<string> errorMessages)
{
errorMessages = new List<string>();
foreach (ContentFile file in Files)
{
if (!File.Exists(file.Path))
{
errorMessages.Add("File \"" + file.Path + "\" not found.");
continue;
}
}
return errorMessages.Count == 0;
}
public static ContentPackage CreatePackage(string name, string path, bool corePackage)
{
ContentPackage newPackage = new ContentPackage()
@@ -418,13 +398,6 @@ namespace Barotrauma
return path == "Mods";
}
}
/// <summary>
/// Are mods allowed to install a file into the specified path. If a content package XML includes files
/// with a prohibited path, they are treated as references to external files. For example, a mod could include
/// some vanilla files in the XML, in which case the game will simply use the vanilla files present in the game folder.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool IsModFilePathAllowed(string path)
{
while (true)
@@ -449,6 +422,16 @@ namespace Barotrauma
return contentPackages.SelectMany(f => f.Files).Where(f => f.Type == type).Select(f => f.Path);
}
public IEnumerable<string> GetFilesOfType(ContentType type)
{
return Files.Where(f => f.Type == type).Select(f => f.Path);
}
public static IEnumerable<string> GetFilesOfType(IEnumerable<ContentPackage> contentPackages, ContentType type)
{
return contentPackages.SelectMany(f => f.Files).Where(f => f.Type == type).Select(f => f.Path);
}
public IEnumerable<string> GetFilesOfType(ContentType type)
{
return Files.Where(f => f.Type == type).Select(f => f.Path);
@@ -68,7 +68,7 @@ namespace Barotrauma
if (!CheatsEnabled && IsCheat)
{
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red);
if (Steam.SteamManager.USE_STEAM)
if (GameMain.Config.UseSteam)
{
NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red);
}
@@ -228,7 +228,7 @@ namespace Barotrauma
{
string errorMsg = "Failed to spawn an item. Arguments: \"" + string.Join(" ", args) + "\".";
ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnItem:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace);
GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnItem:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
},
() =>
@@ -615,7 +615,7 @@ namespace Barotrauma
NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White);
}, isCheat: true));
commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) =>
commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) =>
{
Hull.EditFire = !Hull.EditFire;
NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White);
@@ -55,9 +55,9 @@ namespace Barotrauma
get { return true; }
}
public virtual IEnumerable<Vector2> SonarPositions
public virtual Vector2 SonarPosition
{
get { return Enumerable.Empty<Vector2>(); }
get { return Vector2.Zero; }
}
public string SonarLabel
@@ -1,6 +1,4 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -12,55 +10,30 @@ namespace Barotrauma
private int monsterCount;
private readonly List<Character> monsters = new List<Character>();
private readonly List<Vector2> sonarPositions = new List<Vector2>();
private Vector2 sonarPosition;
public override IEnumerable<Vector2> SonarPositions
public override Vector2 SonarPosition
{
get
{
return sonarPositions;
}
get { return monster != null && !monster.IsDead ? sonarPosition : Vector2.Zero; }
}
public MonsterMission(MissionPrefab prefab, Location[] locations)
: base(prefab, locations)
{
monsterFile = prefab.ConfigElement.GetAttributeString("monsterfile", "");
monsterCount = prefab.ConfigElement.GetAttributeInt("monstercount", 1);
}
public override void Start(Level level)
{
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
//bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
//for (int i = 0; i < monsterCount; i++)
//{
// monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false));
//}
//monsters.ForEach(m => m.Enabled = false);
//SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
//sonarPositions.Add(spawnPos);
float offsetAmount = 500;
for (int i = 0; i < monsterCount; i++)
{
CoroutineManager.InvokeAfter(() =>
{
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
var monster = Character.Create(monsterFile, spawnPos + Rand.Vector(offsetAmount, Rand.RandSync.Server), i.ToString(), null, isClient, true, true);
monster.Enabled = false;
monsters.Add(monster);
if (monsters.Count == monsterCount)
{
//this will do nothing if the monsters have no swarm behavior defined,
//otherwise it'll make the spawned characters act as a swarm
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
sonarPositions.Add(spawnPos);
}
}, Rand.Range(0f, monsterCount / 2, Rand.RandSync.Server));
}
bool isClient = false;
#if CLIENT
isClient = GameMain.Client != null;
#endif
monster = Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false);
monster.Enabled = false;
sonarPosition = spawnPos;
}
public override void Update(float deltaTime)
@@ -72,15 +45,7 @@ namespace Barotrauma
var activeMonsters = monsters.Where(m => m != null && !m.Removed && !m.IsDead);
if (activeMonsters.Any())
{
Vector2 centerOfMass = Vector2.Zero;
foreach (var monster in activeMonsters)
{
//don't add another label if there's another monster roughly at the same spot
if (sonarPositions.All(p => Vector2.DistanceSquared(p, monster.Position) > 1000.0f * 1000.0f))
{
sonarPositions.Add(monster.Position);
}
}
sonarPosition = monster.Position;
}
@@ -16,18 +16,11 @@ namespace Barotrauma
private int state;
public override IEnumerable<Vector2> SonarPositions
public override Vector2 SonarPosition
{
get
{
if (state > 0 )
{
Enumerable.Empty<Vector2>();
}
else
{
yield return ConvertUnits.ToDisplayUnits(item.SimPosition);
}
return state > 0 ? Vector2.Zero : ConvertUnits.ToDisplayUnits(item.SimPosition);
}
}
@@ -227,17 +227,17 @@ namespace Barotrauma
monsters = new List<Character>();
float offsetAmount = spawnPosType == Level.PositionType.MainPath ? 1000 : 100;
for (int i = 0; i < amount; i++)
{
{
CoroutineManager.InvokeAfter(() =>
{
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
bool isClient = false;
#if CLIENT
isClient = GameMain.Client != null;
#endif
monsters.Add(Character.Create(characterFile, spawnPos + Rand.Vector(offsetAmount, Rand.RandSync.Server), i.ToString(), null, isClient, true, true));
if (monsters.Count == amount)
{
spawnReady = true;
//this will do nothing if the monsters have no swarm behavior defined,
//otherwise it'll make the spawned characters act as a swarm
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
}
}, Rand.Range(0f, amount / 2, Rand.RandSync.Server));
}
@@ -8,22 +8,6 @@ namespace Barotrauma
{
public static class StringFormatter
{
public static string Replace(this string s, string replacement, Func<char, bool> predicate)
{
var newString = new string[s.Length];
for (int i = 0; i < s.Length; i++)
{
char letter = s[i];
string newLetter = letter.ToString();
if (predicate(letter))
{
newLetter = replacement;
}
newString[i] = newLetter;
}
return new string(newString.SelectMany(str => str.ToCharArray()).ToArray());
}
public static string Remove(this string s, Func<char, bool> predicate)
{
return new string(s.ToCharArray().Where(c => !predicate(c)).ToArray());
@@ -108,7 +108,7 @@ namespace Barotrauma
}
#if CLIENT
new GUIMessageBox("", TextManager.Get("CargoSpawnNotification").Replace("[roomname]", cargoRoom.DisplayName));
new GUIMessageBox("", TextManager.Get("CargoSpawnNotification").Replace("[roomname]", cargoRoom.RoomName));
#endif
Dictionary<ItemContainer, int> availableContainers = new Dictionary<ItemContainer, int>();
@@ -12,8 +12,7 @@ namespace Barotrauma
public bool CheatsEnabled;
const int InitialMoney = 8700;
public const int HullRepairCost = 500, ItemRepairCost = 500;
const int InitialMoney = 4700;
protected bool watchmenSpawned;
protected Character startWatchman, endWatchman;
@@ -21,8 +20,6 @@ namespace Barotrauma
//key = dialog flag, double = Timing.TotalTime when the line was last said
private Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
public bool PurchasedHullRepairs, PurchasedItemRepairs;
protected Map map;
public Map Map
{
@@ -73,37 +70,6 @@ namespace Barotrauma
watchmenSpawned = false;
startWatchman = null;
endWatchman = null;
if (PurchasedHullRepairs)
{
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine == null || wall.Submarine.IsOutpost) { continue; }
if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine))
{
for (int i = 0; i < wall.SectionCount; i++)
{
wall.AddDamage(i, -100000.0f);
}
}
}
PurchasedHullRepairs = false;
}
if (PurchasedItemRepairs)
{
foreach (Item item in Item.ItemList)
{
if (item.Submarine == null || item.Submarine.IsOutpost) { continue; }
if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine))
{
if (item.GetComponent<Items.Components.Repairable>() != null)
{
item.Condition = item.Health;
}
}
}
PurchasedItemRepairs = false;
}
}
public override void Update(float deltaTime)
@@ -184,7 +184,7 @@ namespace Barotrauma
if (CheatsEnabled)
{
DebugConsole.CheatsEnabled = true;
if (Steam.SteamManager.USE_STEAM && !SteamAchievementManager.CheatsEnabled)
if (GameMain.Config.UseSteam && !SteamAchievementManager.CheatsEnabled)
{
SteamAchievementManager.CheatsEnabled = true;
#if CLIENT
@@ -43,7 +43,6 @@ namespace Barotrauma
public bool SpecularityEnabled { get; set; }
public bool ChromaticAberrationEnabled { get; set; }
public bool PauseOnFocusLost { get; set; } = true;
public bool MuteOnFocusLost { get; set; }
public bool UseDirectionalVoiceChat { get; set; }
@@ -78,17 +77,24 @@ namespace Barotrauma
#if DEBUG
//steam functionality can be enabled/disabled in debug builds
public bool UseSteam;
public bool RequireSteamAuthentication
{
get { return requireSteamAuthentication && Steam.SteamManager.USE_STEAM; }
get { return requireSteamAuthentication && UseSteam; }
set { requireSteamAuthentication = value; }
}
public bool UseSteamMatchmaking
{
get { return useSteamMatchmaking && Steam.SteamManager.USE_STEAM; }
get { return useSteamMatchmaking && UseSteam; }
set { useSteamMatchmaking = value; }
}
#else
//steam functionality determined at compile time
public bool UseSteam
{
get { return Steam.SteamManager.USE_STEAM; }
}
public bool RequireSteamAuthentication
{
get { return requireSteamAuthentication && Steam.SteamManager.USE_STEAM; }
@@ -432,6 +438,9 @@ namespace Barotrauma
VerboseLogging = doc.Root.GetAttributeBool("verboselogging", false);
SaveDebugConsoleLogs = doc.Root.GetAttributeBool("savedebugconsolelogs", false);
#if DEBUG
UseSteam = doc.Root.GetAttributeBool("usesteam", true);
#endif
QuickStartSubmarineName = doc.Root.GetAttributeString("quickstartsub", "");
if (doc == null)
@@ -595,14 +604,13 @@ namespace Barotrauma
}
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
bool packageOk = contentPackage.VerifyFiles(out List<string> errorMessages);
if (!packageOk)
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\":\n" + string.Join("\n", errorMessages));
continue;
}
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
@@ -841,16 +849,12 @@ namespace Barotrauma
EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen);
PauseOnFocusLost = doc.Root.GetAttributeBool("pauseonfocuslost", PauseOnFocusLost);
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount);
EnableMouseLook = doc.Root.GetAttributeBool("enablemouselook", EnableMouseLook);
CrewMenuOpen = doc.Root.GetAttributeBool("crewmenuopen", CrewMenuOpen);
ChatOpen = doc.Root.GetAttributeBool("chatopen", ChatOpen);
CampaignDisclaimerShown = doc.Root.GetAttributeBool("campaigndisclaimershown", false);
EditorDisclaimerShown = doc.Root.GetAttributeBool("editordisclaimershown", false);
foreach (XElement subElement in doc.Root.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -963,19 +967,24 @@ namespace Barotrauma
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
bool packageOk = contentPackage.VerifyFiles(out List<string> errorMessages);
if (!packageOk)
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\":\n" + string.Join("\n", errorMessages));
continue;
}
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
EnsureCoreContentPackageSelected();
if (!SelectedContentPackages.Any())
{
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
if (availablePackage != null)
{
SelectedContentPackages.Add(availablePackage);
}
}
//save to get rid of the invalid selected packages in the config file
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
@@ -994,25 +1003,6 @@ namespace Barotrauma
.Replace("[gameversion]", GameMain.Version.ToString()));
}
}
public void EnsureCoreContentPackageSelected()
{
if (SelectedContentPackages.Any(cp => cp.CorePackage)) { return; }
if (GameMain.VanillaContent != null)
{
SelectedContentPackages.Add(GameMain.VanillaContent);
}
else
{
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
if (availablePackage != null)
{
SelectedContentPackages.Add(availablePackage);
}
}
}
#endregion
#region Save PlayerConfig
@@ -1039,13 +1029,10 @@ namespace Barotrauma
new XAttribute("quickstartsub", QuickStartSubmarineName),
new XAttribute("requiresteamauthentication", requireSteamAuthentication),
new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems),
new XAttribute("pauseonfocuslost", PauseOnFocusLost),
new XAttribute("aimassistamount", aimAssistAmount),
new XAttribute("enablemouselook", EnableMouseLook),
new XAttribute("chatopen", ChatOpen),
new XAttribute("crewmenuopen", CrewMenuOpen),
new XAttribute("campaigndisclaimershown", CampaignDisclaimerShown),
new XAttribute("editordisclaimershown", EditorDisclaimerShown));
new XAttribute("crewmenuopen", CrewMenuOpen));
if (!ShowUserStatisticsPrompt)
{
@@ -1148,9 +1135,9 @@ namespace Barotrauma
{
foreach (Tutorial tutorial in Tutorial.Tutorials)
{
if (tutorial.Completed && !CompletedTutorialNames.Contains(tutorial.Identifier))
if (tutorial.Completed && !CompletedTutorialNames.Contains(tutorial.Name))
{
CompletedTutorialNames.Add(tutorial.Identifier);
CompletedTutorialNames.Add(tutorial.Name);
}
}
}
@@ -176,9 +176,6 @@ namespace Barotrauma
{
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] != null && Items[i] != item)
{
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
#endif
if (!Items[i].AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(Items[i], character, new List<InvSlotType> { InvSlotType.Any }, true))
{
free = false;
@@ -198,9 +195,6 @@ namespace Barotrauma
{
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] == null)
{
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
#endif
bool removeFromOtherSlots = item.ParentInventory != this;
if (placedInSlot == -1 && inWrongSlot)
{
@@ -254,9 +248,7 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return false;
}
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[index])) { hidePersonalSlots = false; }
#endif
//there's already an item in the slot
if (Items[index] != null)
{
@@ -281,9 +273,7 @@ namespace Barotrauma
foreach (InvSlotType allowedSlot in allowedSlots)
{
if (!allowedSlot.HasFlag(SlotTypes[index])) continue;
#if CLIENT
if (PersonalSlots.HasFlag(allowedSlot)) { hidePersonalSlots = false; }
#endif
for (int i = 0; i < capacity; i++)
{
if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] != null && Items[i] != item)
@@ -219,29 +219,31 @@ namespace Barotrauma.Items.Components
private bool hasValidIdCard;
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
{
if (item.Condition <= RepairThreshold) return true; //For repairing
var idCard = character.Inventory.FindItemByIdentifier("idcard");
hasValidIdCard = requiredItems.Any(ri => ri.Value.Any(r => r.MatchesItem(idCard)));
Msg = requiredItems.None() || hasValidIdCard ? "ItemMsgOpen" : "ItemMsgForceOpenCrowbar";
Msg = hasValidIdCard ? "ItemMsgOpen" : "ItemMsgForceOpenCrowbar";
ParseMsg();
if (addMessage)
{
msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText);
msg = msg ?? (requiredItems.Any(ri => ri.Value.Any(r => r.Identifiers.Contains("idcard"))) ? accessDeniedTxt : cannotOpenText);
}
if (isBroken) { return true; }
return base.HasRequiredItems(character, addMessage, msg);
//this is a bit pointless atm because if canBePicked is false it won't allow you to do Pick() anyway, however it's still good for future-proofing.
return requiredItems.Any() ? base.HasRequiredItems(character, addMessage, msg) : canBePicked;
}
public override bool Pick(Character picker)
{
if (item.Condition <= RepairThreshold) { return true; }
if (requiredItems.None()) { return false; }
if (HasRequiredItems(picker, false) && hasValidIdCard) { return false; }
return base.Pick(picker);
}
public override bool OnPicked(Character picker)
{
if (item.Condition <= RepairThreshold) { return true; }
if (item.Condition <= RepairThreshold) return true; //repairs
if (requiredItems.Any() && !hasValidIdCard)
{
ForceOpen(ActionType.OnPicked);
@@ -259,24 +261,23 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
if (!isBroken)
//can only be selected if the item is broken
if (item.Condition <= RepairThreshold) return true; //repairs
bool hasRequiredItems = HasRequiredItems(character, false);
if (requiredItems.None() || hasRequiredItems && hasValidIdCard)
{
bool hasRequiredItems = HasRequiredItems(character, false);
if (requiredItems.None() || hasRequiredItems && hasValidIdCard)
{
float originalPickingTime = PickingTime;
PickingTime = 0;
ForceOpen(ActionType.OnUse);
PickingTime = originalPickingTime;
}
else if (hasRequiredItems)
{
#if CLIENT
GUI.AddMessage(accessDeniedTxt, Color.Red);
#endif
}
float originalPickingTime = PickingTime;
PickingTime = 0;
ForceOpen(ActionType.OnUse);
PickingTime = originalPickingTime;
}
return item.Condition <= RepairThreshold;
else if (hasRequiredItems)
{
#if CLIENT
GUI.AddMessage(accessDeniedTxt, Color.Red);
#endif
}
return false;
}
public override void Update(float deltaTime, Camera cam)
@@ -122,12 +122,10 @@ namespace Barotrauma.Items.Components
foreach (Item subItem in containedSubItems)
{
projectile = subItem.GetComponent<Projectile>();
//apply OnUse statuseffects to the container in case it has to react to it somehow
//(play a sound, spawn more projectiles, reduce condition...)
if (subItem.Condition > 0.0f)
{
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime);
}
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime);
if (projectile != null) break;
}
}
@@ -293,35 +293,10 @@ namespace Barotrauma.Items.Components
//steer closer if almost in range
if (dist > Range)
{
Vector2 standPos = new Vector2(Math.Sign(-fromItemToLeak.X), Math.Sign(-fromItemToLeak.Y)) / 2;
if (!character.AnimController.InWater)
{
if (leak.IsHorizontal)
{
standPos.X *= 2;
standPos.Y = 0;
}
else
{
standPos.X = 0;
}
}
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
{
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable)
{
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
}
else
{
character.AIController.SteeringManager.SteeringSeek(standPos);
}
}
else
{
character.AIController.SteeringManager.SteeringSeek(standPos);
}
Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f);
standPos = leak.WorldPosition + standPos * Range;
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
}
else
{
@@ -330,29 +305,30 @@ namespace Barotrauma.Items.Components
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
}
else if (dist <= Range)
{
// In range
character.AIController.SteeringManager.Reset();
}
else
{
return false;
character.AIController.SteeringManager.Reset();
}
}
sinTime += deltaTime;
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
if (item.RequireAimToUse)
{
character.SetInput(InputType.Aim, false, true);
}
// Press the trigger only when the tool is approximately facing the target.
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
if (angle < MathHelper.PiOver4)
// If the character is climbing, ignore the check, because we cannot aim while climbing.
if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
{
character.SetInput(InputType.Shoot, false, true);
Use(deltaTime, character);
}
else
{
sinTime -= deltaTime * 2;
}
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
@@ -362,11 +338,11 @@ namespace Barotrauma.Items.Components
sinTime = 0;
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
{
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.DisplayName), null, 0.0f, "leaksfixed", 10.0f);
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f);
}
else
{
character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.DisplayName), null, 0.0f, "leakfixed", 10.0f);
character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leakfixed", 10.0f);
}
}
@@ -190,7 +190,7 @@ namespace Barotrauma.Items.Components
get { return name; }
}
[Editable, Serialize("", true, translationTextTag: "ItemMsg")]
[Editable, Serialize("", true)]
public string Msg
{
get;
@@ -580,8 +580,8 @@ namespace Barotrauma.Items.Components
public virtual bool HasRequiredItems(Character character, bool addMessage, string msg = null)
{
if (!requiredItems.Any()) { return true; }
if (character.Inventory == null) { return false; }
if (!requiredItems.Any()) return true;
if (character.Inventory == null) return false;
bool hasRequiredItems = false;
bool canContinue = true;
if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped))
@@ -615,15 +615,7 @@ namespace Barotrauma.Items.Components
{
bool Predicate(Item it) => it != null && it.Condition > 0.0f && relatedItem.MatchesItem(it);
bool shouldBreak = false;
bool inEditor = false;
#if CLIENT
inEditor = Screen.Selected == GameMain.SubEditorScreen;
#endif
if (relatedItem.IgnoreInEditor && inEditor)
{
hasRequiredItems = true;
}
else if (relatedItem.IsOptional)
if (relatedItem.IsOptional)
{
if (!hasRequiredItems)
{
@@ -792,7 +784,6 @@ namespace Barotrauma.Items.Components
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
newRequiredItem.Msg = prevRequiredItem.Msg;
newRequiredItem.IsOptional = prevRequiredItem.IsOptional;
newRequiredItem.IgnoreInEditor = prevRequiredItem.IgnoreInEditor;
}
if (!requiredItems.ContainsKey(newRequiredItem.Type))
@@ -810,7 +801,10 @@ namespace Barotrauma.Items.Components
string msg = TextManager.Get(Msg, true);
if (msg != null)
{
msg = TextManager.ParseInputTypes(msg);
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
}
DisplayMsg = msg;
}
else
@@ -13,11 +13,6 @@ namespace Barotrauma.Items.Components
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
{
get { return inputContainer; }
}
public ItemContainer OutputContainer
{
get { return outputContainer; }
@@ -76,10 +71,8 @@ namespace Barotrauma.Items.Components
var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null);
if (targetItem == null) { return; }
float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
progressState = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
if (progressTimer > targetItem.Prefab.DeconstructTime)
{
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
{
@@ -107,24 +100,10 @@ namespace Barotrauma.Items.Components
}
}
if (targetItem.Prefab.DeconstructItems.Any())
{
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
}
else
{
if (outputContainer.Inventory.Items.All(i => i != null))
{
targetItem.Drop(dropper: null);
}
else
{
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
}
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
if (inputContainer.Inventory.Items.Any(i => i != null))
{
@@ -23,16 +23,6 @@ namespace Barotrauma.Items.Components
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
{
get { return inputContainer; }
}
public ItemContainer OutputContainer
{
get { return outputContainer; }
}
private float progressState;
public Fabricator(Item item, XElement element)
@@ -108,23 +98,7 @@ namespace Barotrauma.Items.Components
{
return (picker != null);
}
public void RemoveFabricationRecipes(List<string> allowedIdentifiers)
{
for (int i = 0; i < fabricationRecipes.Count; i++)
{
if (!allowedIdentifiers.Contains(fabricationRecipes[i].TargetItem.Identifier))
{
fabricationRecipes.RemoveAt(i);
i--;
}
}
CreateRecipes();
}
partial void CreateRecipes();
private void StartFabricating(FabricationRecipe selectedItem, Character user)
{
if (selectedItem == null) return;
@@ -473,8 +473,6 @@ namespace Barotrauma.Items.Components
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
IsActive = true;
float degreeOfSuccess = DegreeOfSuccess(character);
//characters with insufficient skill levels don't refuel the reactor
@@ -47,9 +47,8 @@ namespace Barotrauma.Items.Components
//was the last ping sent with directional pinging
private bool isLastPingDirectional;
private Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground;
private Sprite sonarBlip;
private Sprite lineSprite;
private readonly Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground;
private readonly Sprite sonarBlip;
private bool aiPingCheckPending;
@@ -86,7 +85,7 @@ namespace Barotrauma.Items.Components
{
get { return zoom; }
}
public override bool IsActive
{
get
@@ -112,7 +111,29 @@ namespace Barotrauma.Items.Components
: base(item, element)
{
connectedTransducers = new List<ConnectedTransducer>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "pingcircle":
pingCircle = new Sprite(subElement);
break;
case "directionalpingcircle":
directionalPingCircle = new Sprite(subElement);
break;
case "screenoverlay":
screenOverlay = new Sprite(subElement);
break;
case "screenbackground":
screenBackground = new Sprite(subElement);
break;
case "blip":
sonarBlip = new Sprite(subElement);
break;
}
}
IsActive = false;
InitProjSpecific(element);
}
@@ -184,7 +205,6 @@ namespace Barotrauma.Items.Components
directionalPingCircle?.Remove();
screenOverlay?.Remove();
screenBackground?.Remove();
lineSprite?.Remove();
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
@@ -239,24 +259,15 @@ namespace Barotrauma.Items.Components
int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12);
if (clockDir == 0) clockDir = 12;
return TextManager.Get("roomname.subdiroclock").Replace("[dir]", clockDir.ToString());
return TextManager.Get("SubDirOClock").Replace("[dir]", clockDir.ToString());
}
private Vector2 GetTransducerPos()
private Vector2 GetTransducerCenter()
{
if (!UseTransducers || connectedTransducers.Count == 0)
{
//use the position of the sub if the item is static (no body) and inside a sub
return item.Submarine != null && item.body == null ? item.Submarine.WorldPosition : item.WorldPosition;
}
if (!UseTransducers || connectedTransducers.Count == 0) return Vector2.Zero;
Vector2 transducerPosSum = Vector2.Zero;
foreach (ConnectedTransducer transducer in connectedTransducers)
{
if (transducer.Transducer.Item.Submarine != null)
{
return transducer.Transducer.Item.Submarine.WorldPosition;
}
transducerPosSum += transducer.Transducer.Item.WorldPosition;
}
return transducerPosSum / connectedTransducers.Count;
@@ -172,6 +172,19 @@ namespace Barotrauma.Items.Components
return true;
}
public override void OnItemLoaded()
{
sonar = item.GetComponent<Sonar>();
}
public override bool Select(Character character)
{
if (!CanBeSelected) return false;
user = character;
return true;
}
public override void Update(float deltaTime, Camera cam)
{
networkUpdateTimer -= deltaTime;
@@ -474,9 +487,7 @@ namespace Barotrauma.Items.Components
if (!posToMaintain.HasValue)
{
unsentChanges = true;
posToMaintain = controlledSub != null ?
controlledSub.WorldPosition :
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
posToMaintain = controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition;
}
if (!AutoPilot || !MaintainPos) unsentChanges = true;
@@ -201,7 +201,7 @@ namespace Barotrauma.Items.Components
if (sparkSounds.Count > 0)
{
var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)];
SoundPlayer.PlaySound(sparkSound.Sound, pt.item.WorldPosition, sparkSound.Volume, sparkSound.Range, pt.item.CurrentHull);
SoundPlayer.PlaySound(sparkSound.Sound, sparkSound.Volume, sparkSound.Range, pt.item.WorldPosition, pt.item.CurrentHull);
}
Vector2 baseVel = Rand.Vector(300.0f);
@@ -103,7 +103,7 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (!powerOnSoundPlayed && powerOnSound != null)
{
SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, item.CurrentHull);
SoundPlayer.PlaySound(powerOnSound.Sound, powerOnSound.Volume, powerOnSound.Range, item.WorldPosition, item.CurrentHull);
powerOnSoundPlayed = true;
}
}
@@ -241,12 +241,5 @@ namespace Barotrauma.Items.Components
{
character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f));
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
//do nothing
//Repairables should always stay active, so we don't want to use the default behavior
//where set_active/set_state signals can disable the component
}
}
}
@@ -8,19 +8,11 @@ namespace Barotrauma.Items.Components
{
partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable
{
class CustomInterfaceElement : ISerializableEntity
class CustomInterfaceElement
{
public bool ContinuousSignal;
public bool State;
public string Connection;
[Serialize("", false, translationTextTag = "Label.")]
public string Label { get; set; }
[Serialize("1", false)]
public string Signal { get; set; }
public string Name => "CustomInterfaceElement";
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
public string Label, Connection, Signal;
public List<StatusEffect> StatusEffects = new List<StatusEffect>();
@@ -41,7 +33,7 @@ namespace Barotrauma.Items.Components
}
private string[] labels;
[Serialize("", true)]
[Serialize("", true), Editable()]
public string Labels
{
get { return string.Join(",", labels); }
@@ -56,7 +48,7 @@ namespace Barotrauma.Items.Components
}
}
private string[] signals;
[Serialize("", true)]
[Serialize("", true), Editable()]
public string Signals
{
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
@@ -125,7 +117,7 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < labels.Length; i++)
{
labels[i] = i < newLabels.Length ? newLabels[i] : customInterfaceElementList[i].Label;
customInterfaceElementList[i].Label = TextManager.Get(labels[i], returnNull: true) ?? labels[i];
customInterfaceElementList[i].Label = labels[i];
}
UpdateLabelsProjSpecific();
}
@@ -170,12 +162,5 @@ namespace Barotrauma.Items.Components
}
}
}
public override XElement Save(XElement parentElement)
{
labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray();
return base.Save(parentElement);
}
}
}
@@ -219,7 +219,7 @@ namespace Barotrauma.Items.Components
if (voltage > 0.1f && sparkSounds.Count > 0)
{
var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)];
SoundPlayer.PlaySound(sparkSound.Sound, item.WorldPosition, sparkSound.Volume, sparkSound.Range, item.CurrentHull);
SoundPlayer.PlaySound(sparkSound.Sound, sparkSound.Volume, sparkSound.Range, item.WorldPosition, item.CurrentHull);
}
#endif
lightBrightness = 0.0f;
@@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -67,8 +66,6 @@ namespace Barotrauma
public LightComponent LightComponent { get; set; }
public int Variant { get; set; }
private Gender _gender;
/// <summary>
/// None = Any/Not Defined -> no effect.
@@ -115,65 +112,30 @@ namespace Barotrauma
/// <summary>
/// Note: this constructor cannot initialize automatically, because the gender is unknown at this point. We only know it when the item is equipped.
/// </summary>
public WearableSprite(XElement subElement, Wearable wearable, int variant = 0)
public WearableSprite(XElement subElement, Wearable wearable)
{
Type = WearableType.Item;
WearableComponent = wearable;
Variant = Math.Max(variant, 0);
SpritePath = ParseSpritePath(subElement.GetAttributeString("texture", string.Empty));
SourceElement = subElement;
}
private string ParseSpritePath(string texturePath) => texturePath.Contains("/") ? texturePath : $"{Path.GetDirectoryName(WearableComponent.Item.Prefab.ConfigFile)}/{texturePath}";
public void RefreshPath()
{
if (Variant > 0)
{
// Restore the tag so that we can parse it again.
ReplaceNumbersWith("[VARIANT]");
}
ParsePath(true);
}
private void ReplaceNumbersWith(string replacement)
{
var fileName = Path.GetFileName(SpritePath);
var path = Path.GetDirectoryName(SpritePath);
fileName = fileName.Replace(replacement, c => char.IsNumber(c));
SpritePath = Path.Combine(path, fileName);
}
private void ParsePath(bool parseSpritePath)
{
if (_gender != Gender.None)
{
SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male");
}
SpritePath = SpritePath.Replace("[VARIANT]", Variant.ToString());
if (!File.Exists(SpritePath))
{
// If the variant does not exist, parse the path so that it uses first variant.
Variant = 1;
ReplaceNumbersWith(Variant.ToString());
}
if (parseSpritePath)
{
Sprite.ParseTexturePath(file: SpritePath);
}
}
public bool IsInitialized { get; private set; }
public void Init(Gender gender = Gender.None)
{
if (IsInitialized) { return; }
_gender = SpritePath.Contains("[GENDER]") ? gender : Gender.None;
ParsePath(false);
if (_gender != Gender.None)
{
SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male");
}
if (Sprite != null)
{
Sprite.Remove();
}
Sprite = new Sprite(SourceElement, file: SpritePath);
Sprite = new Sprite(SourceElement, "", SpritePath);
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false);
@@ -208,7 +170,7 @@ namespace Barotrauma.Items.Components
get { return damageModifiers; }
}
public Wearable(Item item, XElement element) : base(item, element)
public Wearable (Item item, XElement element) : base(item, element)
{
this.item = item;
@@ -235,7 +197,7 @@ namespace Barotrauma.Items.Components
limbType[i] = (LimbType)Enum.Parse(typeof(LimbType),
subElement.GetAttributeString("limb", "Head"), true);
wearableSprites[i] = new WearableSprite(subElement, this, variant);
wearableSprites[i] = new WearableSprite(subElement, this);
foreach (XElement lightElement in subElement.Elements())
{
@@ -206,16 +206,6 @@ namespace Barotrauma
return true;
}
public bool IsFull()
{
for (int i = 0; i < capacity; i++)
{
if (Items[i] == null) return false;
}
return true;
}
protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent)
{
if (item?.ParentInventory == null || Items[index] == null) return false;
@@ -928,9 +928,14 @@ namespace Barotrauma
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false)
{
if (!hasStatusEffectsOfType[(int)type]) { return; }
foreach (StatusEffect effect in statusEffectLists[type])
if (statusEffectLists == null) return;
if (!statusEffectLists.TryGetValue(type, out List<StatusEffect> statusEffects)) return;
bool broken = condition <= 0.0f;
foreach (StatusEffect effect in statusEffects)
{
if (broken && effect.type != ActionType.OnBroken) continue;
ApplyStatusEffect(effect, type, deltaTime, character, limb, isNetworkEvent, false);
}
}
@@ -1047,8 +1052,6 @@ namespace Barotrauma
aiTarget.SoundRange -= deltaTime * 1000.0f;
}
bool broken = condition <= 0.0f;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
sendConditionUpdateTimer -= deltaTime;
@@ -1124,10 +1127,7 @@ namespace Barotrauma
container = container.Container;
}
}
if (!broken)
{
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
}
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
@@ -1149,10 +1149,6 @@ namespace Barotrauma
{
body.SetTransform(body.SimPosition - Submarine.SimPosition, body.Rotation);
}
else if (Submarine != null && prevSub != null && Submarine != prevSub)
{
body.SetTransform(body.SimPosition + prevSub.SimPosition - Submarine.SimPosition, body.Rotation);
}
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
rect.X = (int)(displayPos.X - rect.Width / 2.0f);
@@ -1210,7 +1206,7 @@ namespace Barotrauma
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return true; }
if (ImpactTolerance > 0.0f && condition > 0.0f && impact > ImpactTolerance)
if (ImpactTolerance > 0.0f && impact > ImpactTolerance)
{
ApplyStatusEffects(ActionType.OnImpact, 1.0f);
#if SERVER
@@ -1269,7 +1265,7 @@ namespace Barotrauma
if (recursive)
{
HashSet<Connection> alreadySearched = new HashSet<Connection>();
List<Item> alreadySearched = new List<Item>() { this };
GetConnectedComponentsRecursive(alreadySearched, connectedComponents);
return connectedComponents;
@@ -1291,7 +1287,7 @@ namespace Barotrauma
return connectedComponents;
}
private void GetConnectedComponentsRecursive<T>(HashSet<Connection> alreadySearched, List<T> connectedComponents) where T : ItemComponent
private void GetConnectedComponentsRecursive<T>(List<Item> alreadySearched, List<T> connectedComponents) where T : ItemComponent
{
ConnectionPanel connectionPanel = GetComponent<ConnectionPanel>();
if (connectionPanel == null) { return; }
@@ -1315,35 +1311,28 @@ namespace Barotrauma
}
}
recipient.Item.GetConnectedComponentsRecursive<T>(alreadySearched, connectedComponents);
}
}
}
public List<T> GetConnectedComponentsRecursive<T>(Connection c) where T : ItemComponent
{
List<T> connectedComponents = new List<T>();
HashSet<Connection> alreadySearched = new HashSet<Connection>();
List<T> connectedComponents = new List<T>();
List<Item> alreadySearched = new List<Item>() { this };
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents);
return connectedComponents;
}
private static readonly Pair<string, string>[] connectionPairs = new Pair<string, string>[]
{
new Pair<string, string>("power_in", "power_out"),
new Pair<string, string>("signal_in1", "signal_out1"),
new Pair<string, string>("signal_in2", "signal_out2"),
new Pair<string, string>("signal_in3", "signal_out3"),
new Pair<string, string>("signal_in4", "signal_out4"),
new Pair<string, string>("signal_in", "signal_out"),
new Pair<string, string>("signal_in1", "signal_out"),
new Pair<string, string>("signal_in2", "signal_out")
};
private void GetConnectedComponentsRecursive<T>(Connection c, HashSet<Connection> alreadySearched, List<T> connectedComponents) where T : ItemComponent
private void GetConnectedComponentsRecursive<T>(Connection c, List<Item> alreadySearched, List<T> connectedComponents) where T : ItemComponent
{
alreadySearched.Add(c);
alreadySearched.Add(this);
var recipients = c.Recipients;
foreach (Connection recipient in recipients)
{
if (alreadySearched.Contains(recipient)) { continue; }
if (alreadySearched.Contains(recipient.Item)) continue;
var component = recipient.Item.GetComponent<T>();
if (component != null)
@@ -1352,29 +1341,7 @@ namespace Barotrauma
}
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents);
}
foreach (Pair<string, string> connectionPair in connectionPairs)
{
if (connectionPair.First == c.Name)
{
var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == connectionPair.Second);
if (pairedConnection != null)
{
if (alreadySearched.Contains(pairedConnection)) { continue; }
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents);
}
}
else if (connectionPair.Second == c.Name)
{
var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == connectionPair.First);
if (pairedConnection != null)
{
if (alreadySearched.Contains(pairedConnection)) { continue; }
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents);
}
}
}
}
}
@@ -138,6 +138,16 @@ namespace Barotrauma
private Dictionary<string, PriceInfo> prices;
//an area next to the construction
//the construction can be Activated() by a Character inside the area
public List<Rectangle> Triggers;
private List<XElement> fabricationRecipeElements = new List<XElement>();
private bool canSpriteFlipX, canSpriteFlipY;
private Dictionary<string, PriceInfo> prices;
/// <summary>
/// Defines areas where the item can be interacted with. If RequireBodyInsideTrigger is set to true, the character
/// has to be within the trigger to interact. If it's set to false, having the cursor within the trigger is enough.
@@ -460,11 +470,11 @@ namespace Barotrauma
DeconstructItems = new List<DeconstructItem>();
FabricationRecipes = new List<FabricationRecipe>();
DeconstructTime = 1.0f;
Tags = new HashSet<string>(element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true));
Tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true).ToHashSet();
if (Tags.None())
{
Tags = new HashSet<string>(element.GetAttributeStringArray("Tags", new string[0], convertToLowerInvariant: true));
Tags = element.GetAttributeStringArray("Tags", new string[0], convertToLowerInvariant: true).ToHashSet();
}
if (element.Attribute("cargocontainername") != null)
@@ -626,25 +636,10 @@ namespace Barotrauma
string treatmentIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
List<AfflictionPrefab> matchingAfflictions = AfflictionPrefab.List.FindAll(a => a.Identifier == treatmentIdentifier || a.AfflictionType == treatmentIdentifier);
if (matchingAfflictions.Count == 0)
var matchingAffliction = AfflictionPrefab.List.Find(a => a.Identifier == treatmentIdentifier);
if (matchingAffliction != null)
{
DebugConsole.ThrowError("Error in item prefab \"" + Name + "\" - couldn't define as a treatment, no treatments with the identifier or type \"" + treatmentIdentifier + "\" were found.");
continue;
}
float suitability = subElement.GetAttributeFloat("suitability", 0.0f);
foreach (AfflictionPrefab matchingAffliction in matchingAfflictions)
{
if (matchingAffliction.TreatmentSuitability.ContainsKey(identifier))
{
matchingAffliction.TreatmentSuitability[identifier] =
Math.Max(matchingAffliction.TreatmentSuitability[identifier], suitability);
}
else
{
matchingAffliction.TreatmentSuitability.Add(identifier, suitability);
}
matchingAffliction.TreatmentSuitability.Add(identifier, subElement.GetAttributeFloat("suitability", 0.0f));
}
break;
}
@@ -17,8 +17,8 @@ namespace Barotrauma
}
public bool IsOptional { get; set; }
public bool IgnoreInEditor { get; set; }
private string[] identifiers;
private string[] excludedIdentifiers;
@@ -35,20 +35,23 @@ namespace Barotrauma
public string JoinedIdentifiers
{
get { return string.Join(",", Identifiers); }
get { return string.Join(",", identifiers); }
set
{
if (value == null) return;
Identifiers = value.Split(',');
for (int i = 0; i < Identifiers.Length; i++)
identifiers = value.Split(',');
for (int i = 0; i < identifiers.Length; i++)
{
Identifiers[i] = Identifiers[i].Trim().ToLowerInvariant();
identifiers[i] = identifiers[i].Trim().ToLowerInvariant();
}
}
}
public string[] Identifiers { get; private set; }
public string[] Identifiers
{
get { return identifiers; }
}
public string JoinedExcludedIdentifiers
{
@@ -69,7 +72,7 @@ namespace Barotrauma
{
if (item == null) return false;
if (excludedIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) return false;
return Identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id));
return identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id));
}
public RelatedItem(string[] identifiers, string[] excludedIdentifiers)
@@ -78,7 +81,7 @@ namespace Barotrauma
{
identifiers[i] = identifiers[i].Trim().ToLowerInvariant();
}
this.Identifiers = identifiers;
this.identifiers = identifiers;
for (int i = 0; i < excludedIdentifiers.Length; i++)
{
@@ -138,8 +141,7 @@ namespace Barotrauma
element.Add(
new XAttribute("identifiers", JoinedIdentifiers),
new XAttribute("type", type.ToString()),
new XAttribute("optional", IsOptional),
new XAttribute("ignoreineditor", IgnoreInEditor));
new XAttribute("optional", IsOptional));
if (excludedIdentifiers.Length > 0)
{
@@ -221,7 +223,6 @@ namespace Barotrauma
}
ri.IsOptional = element.GetAttributeBool("optional", false);
ri.IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false);
return ri;
}
}
@@ -40,7 +40,7 @@ namespace Barotrauma
flames = true;
underwaterBubble = true;
}
public Explosion(XElement element, string parentDebugName)
{
attack = new Attack(element, parentDebugName + ", Explosion");
@@ -62,16 +62,6 @@ namespace Barotrauma
CameraShake = element.GetAttributeFloat("camerashake", attack.Range * 0.1f);
}
public void DisableParticles()
{
sparks = false;
shockwave = false;
smoke = false;
flash = false;
flames = false;
underwaterBubble = false;
}
public List<Triplet<Explosion, Vector2, float>> GetRecentExplosions(float maxSecondsAgo)
{
return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo);
@@ -15,17 +15,16 @@ namespace Barotrauma
{
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 5.0f;
protected Hull hull;
protected Vector2 position;
protected Vector2 size;
private Hull hull;
private readonly Submarine submarine;
public Submarine Submarine => submarine;
protected bool removed;
private bool removed;
#if CLIENT
private List<Decal> burnDecals = new List<Decal>();
#endif
@@ -87,7 +86,7 @@ namespace Barotrauma
position = worldPosition - new Vector2(-5.0f, 5.0f);
if (hull.Submarine != null)
{
submarine = hull.Submarine;
Submarine = hull.Submarine;
position -= Submarine.Position;
}
@@ -186,16 +185,6 @@ namespace Barotrauma
}
}
protected virtual void ReduceOxygen(float deltaTime)
{
hull.Oxygen -= size.X * deltaTime * OxygenConsumption;
}
protected virtual void AdjustXPos(float growModifier, float deltaTime)
{
position.X -= GrowSpeed * growModifier * 0.5f * deltaTime;
}
partial void UpdateProjSpecific(float growModifier);
private void OnChangeHull(Vector2 pos, Hull particleHull)
+23 -6
View File
@@ -63,6 +63,13 @@ namespace Barotrauma
return "Hull";
}
}
[Editable, Serialize("", true)]
public string RoomName
{
get;
set;
}
public string DisplayName
{
@@ -417,6 +424,11 @@ namespace Barotrauma
public void AddFireSource(FireSource fireSource)
{
FireSources.Add(fireSource);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !IdFreed)
{
GameMain.NetworkMember.CreateEntityEvent(this);
}
}
public override void Update(float deltaTime, Camera cam)
@@ -584,6 +596,11 @@ namespace Barotrauma
public void RemoveFire(FireSource fire)
{
FireSources.Remove(fire);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !Removed && !IdFreed)
{
GameMain.NetworkMember.CreateEntityEvent(this);
}
}
public IEnumerable<Hull> GetConnectedHulls(int? searchDepth)
@@ -800,17 +817,17 @@ namespace Barotrauma
}
if (roomItems.Contains("reactor"))
return "RoomName.ReactorRoom";
return TextManager.Get("ReactorRoom");
else if (roomItems.Contains("engine"))
return "RoomName.EngineRoom";
return TextManager.Get("EngineRoom");
else if (roomItems.Contains("steering") && roomItems.Contains("sonar"))
return "RoomName.CommandRoom";
return TextManager.Get("CommandRoom");
else if (roomItems.Contains("ballast"))
return "RoomName.Ballast";
return TextManager.Get("Ballast");
if (ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null))
{
return "RoomName.Airlock";
return TextManager.Get("Airlock");
}
Rectangle subRect = Submarine.CalculateDimensions();
@@ -830,7 +847,7 @@ namespace Barotrauma
else
roomPos |= Alignment.Right;
return "RoomName.Sub" + roomPos.ToString();
return TextManager.Get("Sub" + roomPos.ToString());
}
public static Hull Load(XElement element, Submarine submarine)
@@ -138,9 +138,6 @@ namespace Barotrauma
public Submarine StartOutpost { get; private set; }
public Submarine EndOutpost { get; private set; }
private Submarine preSelectedStartOutpost;
private Submarine preSelectedEndOutpost;
public string Seed
{
get { return seed; }
@@ -212,7 +209,7 @@ namespace Barotrauma
/// </summary>
/// <param name="difficulty">A scalar between 0-100</param>
/// <param name="sizeFactor">A scalar between 0-1 (0 = the minimum width defined in the generation params is used, 1 = the max width is used)</param>
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, Submarine startOutpost = null, Submarine endOutPost = null)
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
: base(null)
{
@@ -228,9 +225,6 @@ namespace Barotrauma
(width / GridCellSize) * GridCellSize,
(generationParams.Height / GridCellSize) * GridCellSize);
preSelectedStartOutpost = startOutpost;
preSelectedEndOutpost = endOutPost;
//remove from entity dictionary
base.Remove();
}
@@ -1516,24 +1510,14 @@ namespace Barotrauma
continue;
}
//only create a starting outpost in campaign and tutorial modes
if (!IsModeStartOutpostCompatible() && ((i == 0) == !Mirrored))
//only create a starting outpost in campaign mode
if (GameMain.GameSession?.GameMode as CampaignMode == null && ((i == 0) == !Mirrored))
{
continue;
}
Submarine outpost = null;
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
{
string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server);
outpost = new Submarine(outpostFile, tryLoad: false);
}
else
{
outpost = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost;
}
string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server);
var outpost = new Submarine(outpostFile, tryLoad: false);
outpost.Load(unloadPrevious: false);
outpost.MakeOutpost();
@@ -1585,15 +1569,6 @@ namespace Barotrauma
}
}
private bool IsModeStartOutpostCompatible()
{
#if CLIENT
return GameMain.GameSession?.GameMode as CampaignMode != null || GameMain.GameSession?.GameMode as TutorialMode != null;
#else
return GameMain.GameSession?.GameMode as CampaignMode != null;
#endif
}
public override void Remove()
{
base.Remove();
@@ -27,13 +27,7 @@ namespace Barotrauma
DisallowedAdjacentLocations = element.GetAttributeStringArray("disallowedadjacentlocations", new string[0]).ToList();
RequiredAdjacentLocations = element.GetAttributeStringArray("requiredadjacentlocations", new string[0]).ToList();
string messageTag = element.GetAttributeString("messagetag", "LocationChange." + currentType + ".ChangeTo." + ChangeToType);
Messages = TextManager.GetAll(messageTag);
if (Messages == null)
{
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
}
Messages = TextManager.GetAll("LocationChange." + currentType + ".ChangeTo." + ChangeToType);
}
}
}
@@ -522,9 +522,15 @@ namespace Barotrauma
}
}
}
// The value should always be copied from the prefab. Editing is enabled only for testing the scale in the sub editor (changes are not saved).
#if DEBUG
[Serialize(1f, false), Editable(0.1f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
public virtual float Scale { get; set; } = 1;
#else
[Serialize(1f, false)]
#endif
public float Scale { get; set; } = 1;
#endregion
}
}
@@ -159,32 +159,6 @@ namespace Barotrauma
private set;
}
private float scale = 1.0f;
public override float Scale
{
get { return scale; }
set
{
if (scale == value) { return; }
scale = MathHelper.Clamp(value, 0.1f, 10.0f);
float relativeScale = scale / prefab.Scale;
if (!ResizeHorizontal || !ResizeVertical)
{
int newWidth = ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale);
int newHeight = ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale);
Rect = new Rectangle(rect.X, rect.Y, newWidth, newHeight);
if (Sections != null)
{
UpdateSections();
}
}
}
}
private Rectangle defaultRect;
public override Rectangle Rect
{
get
@@ -195,13 +169,9 @@ namespace Barotrauma
{
Rectangle oldRect = Rect;
base.Rect = value;
if (Prefab.Body)
{
CreateSections();
}
if (Prefab.Body) CreateSections();
else
{
if (Sections == null) { return; }
foreach (WallSection sec in Sections)
{
Rectangle secRect = sec.rect;
@@ -219,11 +189,11 @@ namespace Barotrauma
public float BodyWidth
{
get { return Prefab.BodyWidth > 0.0f ? Prefab.BodyWidth * scale : rect.Width; }
get { return Prefab.BodyWidth > 0.0f ? Prefab.BodyWidth : rect.Width; }
}
public float BodyHeight
{
get { return Prefab.BodyHeight > 0.0f ? Prefab.BodyHeight * scale : rect.Height; }
get { return Prefab.BodyHeight > 0.0f ? Prefab.BodyHeight : rect.Height; }
}
/// <summary>
@@ -344,8 +314,8 @@ namespace Barotrauma
}
}
// Only add ai targets automatically to submarine/outpost walls
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null)
// Only add ai targets automatically to walls
if (aiTarget == null && HasBody && Tags.Contains("wall"))
{
aiTarget = new AITarget(this);
}
@@ -635,6 +605,24 @@ namespace Barotrauma
var character = ((Limb)f2.Body.UserData).character;
if (character.DisableImpactDamageTimer > 0.0f || ((Limb)f2.Body.UserData).Mass < 100.0f) return true;
}
if (!Prefab.Platform && Prefab.StairDirection == Direction.None)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
int section = FindSectionIndex(pos);
if (section > -1)
{
Vector2 normal = contact.Manifold.LocalNormal;
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal) * f2.Body.Mass * 0.1f;
if (impact < 10.0f) return true;
#if CLIENT
SoundPlayer.PlayDamageSound("StructureBlunt", impact, SectionPosition(section, true), tags: Tags);
#endif
AddDamage(section, impact);
}
}
OnImpactProjSpecific(f1, f2, contact);
@@ -977,7 +965,6 @@ namespace Barotrauma
private void UpdateSections()
{
if (Bodies == null) return;
foreach (Body b in Bodies)
{
GameMain.World.RemoveBody(b);
@@ -1041,9 +1028,9 @@ namespace Barotrauma
if (BodyWidth > 0.0f) rect.Width = (int)BodyWidth;
if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1);
}
if (FlippedX) { diffFromCenter = -diffFromCenter; }
if (FlippedX) diffFromCenter = -diffFromCenter;
Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset) * scale;
Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset);
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
@@ -1063,8 +1050,7 @@ namespace Barotrauma
{
newBody.Position = structureCenter + bodyOffset + new Vector2(
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation))
* ConvertUnits.ToSimUnits(diffFromCenter);
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * ConvertUnits.ToSimUnits(diffFromCenter);
newBody.Rotation = -BodyRotation;
}
else
@@ -1205,9 +1191,6 @@ namespace Barotrauma
{
XElement element = new XElement("Structure");
int width = ResizeHorizontal ? rect.Width : defaultRect.Width;
int height = ResizeVertical ? rect.Height : defaultRect.Height;
element.Add(
new XAttribute("name", prefab.Name),
new XAttribute("identifier", prefab.Identifier),
@@ -417,10 +417,7 @@ namespace Barotrauma
if (me.Submarine != this) { continue; }
if (me is Item item)
{
if (item.GetComponent<Repairable>() != null)
{
item.Indestructible = true;
}
item.Indestructible = true;
foreach (ItemComponent ic in item.Components)
{
if (ic is ConnectionPanel connectionPanel)
@@ -428,17 +425,11 @@ namespace Barotrauma
//prevent rewiring
connectionPanel.Locked = true;
}
else if (ic is Holdable holdable && holdable.Attached)
else if (ic is Pickable pickable)
{
//prevent deattaching items from walls
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode)
{
continue;
}
#endif
holdable.CanBePicked = false;
holdable.CanBeSelected = false;
//prevent picking up (or deattaching) items
pickable.CanBePicked = false;
pickable.CanBeSelected = false;
}
}
}
@@ -542,6 +533,20 @@ namespace Barotrauma
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
else
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
{
//no walls found at either side, just use the initial spawnpos and hope for the best
}
else if (minX < 0)
{
//no wall found at the left side, spawn to the left from the right-side wall
spawnPos.X = maxX - minWidth - 100.0f + subDockingPortOffset;
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
@@ -1123,7 +1128,6 @@ namespace Barotrauma
}
}
savedSubmarines.Add(new Submarine(filePath));
savedSubmarines = savedSubmarines.OrderBy(s => s.filePath ?? "").ToList();
}
public static void RefreshSavedSubs()
@@ -468,7 +468,7 @@ namespace Barotrauma
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
targetPos = character.WorldPosition;
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 500.0f);
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 200.0f);
if (adjacentGap == null) return true;
if (newHull != null)
@@ -102,7 +102,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, condition));
@@ -115,7 +115,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, condition));
@@ -20,7 +20,7 @@ namespace Barotrauma.Networking
public OrderChatMessage(Order order, string orderOption, Entity targetEntity, Character targetCharacter, Character sender)
: this(order, orderOption,
order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.RoomName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
targetEntity, targetCharacter, sender)
{
}
@@ -377,12 +377,6 @@ namespace Barotrauma.Networking
private set;
}
[Serialize(true, true)]
public bool VoipEnabled {
get;
private set;
}
[Serialize(true, true)]
public bool EndRoundAtLevelEnd
{
@@ -401,7 +395,7 @@ namespace Barotrauma.Networking
public bool AllowRagdollButton
{
get;
set;
private set;
}
[Serialize(true, true)]
@@ -7,7 +7,15 @@ namespace Barotrauma.Steam
{
partial class SteamManager
{
#if DEBUG
public static bool USE_STEAM
{
get { return GameMain.Config.UseSteam; }
}
#else
//cannot enable/disable steam in release builds
public const bool USE_STEAM = true;
#endif
public const uint AppID = 602960;
@@ -58,16 +66,6 @@ namespace Barotrauma.Steam
if (!USE_STEAM) return;
instance = new SteamManager();
}
public static void OverlayCustomURL(string url)
{
if (instance == null || !instance.isInitialized || instance.client == null)
{
return;
}
instance.client.Overlay.OpenUrl(url);
}
public static bool UnlockAchievement(string achievementName)
{
@@ -88,7 +88,7 @@ namespace Barotrauma
public override bool Equals(object obj)
{
if (obj is KeyOrMouse keyOrMouse)
if (obj is KeyOrMouse keyOrMouse )
{
if (MouseButton.HasValue)
{
@@ -162,6 +162,21 @@ namespace Barotrauma
get { return binding; }
}
public void SetState()
{
hit = binding.IsHit();
if (hit) hitQueue = true;
held = binding.IsDown();
if (held) heldQueue = true;
}
#endif
public KeyOrMouse State
{
get { return binding; }
}
public void SetState()
{
hit = binding.IsHit();
@@ -46,15 +46,7 @@ namespace Barotrauma
if (File.Exists(filePath))
{
try
{
doc = XDocument.Load(filePath, LoadOptions.SetBaseUri);
}
catch
{
return null;
}
doc = XDocument.Load(filePath, LoadOptions.SetBaseUri);
if (doc.Root == null) return null;
}
@@ -87,6 +87,7 @@ namespace Barotrauma
public string FullPath { get; private set; }
public override string ToString()
{
return FilePath + ": " + sourceRect;
@@ -106,14 +107,27 @@ namespace Barotrauma
{
this.lazyLoad = lazyLoad;
SourceElement = element;
if (!ParseTexturePath(path, file)) { return; }
if (file == "")
{
file = SourceElement.GetAttributeString("texture", "");
}
if (file == "")
{
DebugConsole.ThrowError("Sprite " + SourceElement + " doesn't have a texture specified!");
return;
}
if (!string.IsNullOrEmpty(path))
{
LoadTexture(ref sourceVector, ref shouldReturn, preMultipliedAlpha);
}
FilePath = path + file;
if (!string.IsNullOrEmpty(FilePath))
{
FullPath = Path.GetFullPath(FilePath);
}
Name = SourceElement.GetAttributeString("name", null);
Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero);
var overrideElement = GetLocalizationOverrideElement();
if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
{
sourceVector = overrideElement.GetAttributeVector4("sourcerect", Vector4.Zero);
}
preMultipliedAlpha = preMultiplyAlpha ?? SourceElement.GetAttributeBool("premultiplyalpha", true);
bool shouldReturn = false;
if (!lazyLoad)
@@ -245,12 +259,8 @@ namespace Barotrauma
}
if (SourceElement != null)
{
sourceRect = SourceElement.GetAttributeRect("sourcerect", Rectangle.Empty);
var overrideElement = GetLocalizationOverrideElement();
if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
{
sourceRect = overrideElement.GetAttributeRect("sourcerect", Rectangle.Empty);
}
Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero);
sourceRect = new Rectangle((int)sourceVector.X, (int)sourceVector.Y, (int)sourceVector.Z, (int)sourceVector.W);
size = SourceElement.GetAttributeVector2("size", Vector2.One);
size.X *= sourceRect.Width;
size.Y *= sourceRect.Height;
@@ -259,51 +269,6 @@ namespace Barotrauma
ID = GetID(SourceElement);
}
}
public bool ParseTexturePath(string path = "", string file = "")
{
if (file == "")
{
file = SourceElement.GetAttributeString("texture", "");
var overrideElement = GetLocalizationOverrideElement();
if (overrideElement != null)
{
string overrideFile = overrideElement.GetAttributeString("texture", "");
if (!string.IsNullOrEmpty(overrideFile)) { file = overrideFile; }
}
}
if (file == "")
{
DebugConsole.ThrowError("Sprite " + SourceElement + " doesn't have a texture specified!");
return false;
}
if (!string.IsNullOrEmpty(path))
{
if (!path.EndsWith("/")) path += "/";
}
FilePath = path + file;
if (!string.IsNullOrEmpty(FilePath))
{
FullPath = Path.GetFullPath(FilePath);
}
return true;
}
private XElement GetLocalizationOverrideElement()
{
foreach (XElement subElement in SourceElement.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "override")
{
string language = subElement.GetAttributeString("language", "");
if (TextManager.Language.ToLower() == language.ToLower())
{
return subElement;
}
}
}
return null;
}
}
}
@@ -336,12 +336,9 @@ namespace Barotrauma
UnlockAchievement("survivereactormeltdown");
}
#endif
var charactersInSub = Character.CharacterList.FindAll(c =>
!c.IsDead &&
c.TeamID != Character.TeamType.FriendlyNPC &&
!(c.AIController is EnemyAIController) &&
(c.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost)));
var charactersInSub = Character.CharacterList.FindAll(c => !c.IsDead &&
(c.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost)));
if (charactersInSub.Count == 1)
{
//there must be some non-enemy casualties to get the last mant standing achievement
@@ -349,11 +346,7 @@ namespace Barotrauma
{
UnlockAchievement(charactersInSub[0], "lastmanstanding");
}
//lone sailor achievement if alone in the sub and there are no other characters with the same team ID
else if (!Character.CharacterList.Any(c =>
c != charactersInSub[0] &&
c.TeamID == charactersInSub[0].TeamID &&
!(c.AIController is EnemyAIController)))
else if (!Character.CharacterList.Any(c => !(c.AIController is EnemyAIController)))
{
UnlockAchievement(charactersInSub[0], "lonesailor");
}
+132 -18
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Barotrauma
{
@@ -16,7 +15,7 @@ namespace Barotrauma
private static string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" };
public static string Language;
private static HashSet<string> availableLanguages = new HashSet<string>();
public static IEnumerable<string> AvailableLanguages
{
@@ -79,7 +78,7 @@ namespace Barotrauma
}
}
public static string Get(string textTag, bool returnNull = false, string fallBackTag = null)
public static string Get(string textTag, bool returnNull = false)
{
if (!textPacks.ContainsKey(Language))
{
@@ -94,16 +93,7 @@ namespace Barotrauma
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 != 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
@@ -128,14 +118,138 @@ namespace Barotrauma
}
}
public static string ParseInputTypes(string text)
public static string GetFormatted(string textTag, bool returnNull = false, params object[] args)
{
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
string text = Get(textTag, returnNull);
if (text == null || text.Length == 0)
{
text = text.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
text = text.Replace("[InputType." + inputType.ToString() + "]", GameMain.Config.KeyBind(inputType).ToString());
if (returnNull)
{
return null;
}
else
{
DebugConsole.ThrowError("Text \"" + textTag + "\" not found.");
return textTag;
}
}
return text;
return string.Format(text, args);
}
// Format: ServerMessage.Identifier1/ServerMessage.Indentifier2~[variable1]=value~[variable2]=value
public static string GetServerMessage(string serverMessage)
{
if (!textPacks.ContainsKey(Language))
{
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!");
}
}
string[] messages = serverMessage.Split('/');
try
{
for (int i = 0; i < messages.Length; i++)
{
if (!IsServerMessageWithVariables(messages[i])) // No variables, try to translate
{
if (messages[i].Contains(" ")) continue; // Spaces found, do not translate
string msg = Get(messages[i], true);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
}
}
else
{
string[] messageWithVariables = messages[i].Split('~');
string msg = Get(messageWithVariables[0], true);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
}
else
{
continue; // No translation found, probably caused by player input -> skip variable handling
}
// First index is always the message identifier -> start at 1
for (int j = 1; j < messageWithVariables.Length; j++)
{
string[] variableAndValue = messageWithVariables[j].Split('=');
messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]);
}
}
}
string translatedServerMessage = string.Empty;
for (int i = 0; i < messages.Length; i++)
{
translatedServerMessage += messages[i];
}
return translatedServerMessage;
}
catch (IndexOutOfRangeException exception)
{
string errorMsg = "Failed to translate server message \"" + serverMessage + "\".";
#if DEBUG
DebugConsole.ThrowError(errorMsg, exception);
#endif
GameAnalyticsManager.AddErrorEventOnce("TextManager.GetServerMessage:" + serverMessage, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return errorMsg;
}
}
public static bool IsServerMessageWithVariables(string message)
{
for (int i = 0; i < serverMessageCharacters.Length; i++)
{
if (!message.Contains(serverMessageCharacters[i])) return false;
}
return true;
}
public static List<string> GetAll(string textTag)
{
if (!textPacks.ContainsKey(Language))
{
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;
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"])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
}
return null;
}
public static string GetFormatted(string textTag, bool returnNull = false, params object[] args)
+2 -17
View File
@@ -11,8 +11,8 @@ namespace Barotrauma
public readonly string Language;
private Dictionary<string, List<string>> texts;
private readonly string filePath;
private string filePath;
public TextPack(string filePath)
{
@@ -37,7 +37,6 @@ namespace Barotrauma
text = text.Replace("&amp;", "&");
text = text.Replace("&lt;", "<");
text = text.Replace("&gt;", ">");
text = text.Replace("&quot;", "\"");
infoList.Add(text);
}
}
@@ -63,20 +62,6 @@ namespace Barotrauma
return textList;
}
public List<KeyValuePair<string, string>> GetAllTagTextPairs()
{
var pairs = new List<KeyValuePair<string, string>>();
foreach (KeyValuePair<string, List<string>> kvp in texts)
{
foreach (string line in kvp.Value)
{
pairs.Add(new KeyValuePair<string, string>(kvp.Key, line));
}
}
return pairs;
}
#if DEBUG
public void CheckForDuplicates(int index)
{
@@ -795,18 +795,6 @@ namespace Barotrauma
return new Vector2((float)x, (float)y);
}
/// <summary>
/// Rotates a point in 2d space around the origin
/// </summary>
public static Vector2 RotatePoint(Vector2 point, float radians)
{
var sin = Math.Sin(radians);
var cos = Math.Cos(radians);
var x = (cos * point.X) - (sin * point.Y);
var y = (sin * point.X) + (cos * point.Y);
return new Vector2((float)x, (float)y);
}
/// <summary>
/// Returns the corners of an imaginary rectangle.
/// Unlike the XNA rectangle, this can be rotated with the up parameter.
@@ -222,6 +222,12 @@ namespace Barotrauma
if (fileName.Length == 0) fileName = "Save";
}
if (fileName == "Save_Default")
{
fileName = TextManager.Get("SaveFile.DefaultName", true);
if (fileName.Length == 0) fileName = "Save";
}
if (!Directory.Exists(folder))
{
DebugConsole.ThrowError("Save folder \"" + folder + "\" not found. Created new folder");