(99feac023) Unstable 0.9.704.0

This commit is contained in:
Joonas Rikkonen
2020-02-07 20:47:03 +02:00
parent 590619459b
commit 6754b9d5a2
104 changed files with 2224 additions and 1091 deletions
@@ -142,16 +142,8 @@ namespace Barotrauma
}
}
public AITarget(Entity e, XElement element) : this(e)
public void Reset()
{
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
SoundRange = element.GetAttributeFloat("soundrange", 0.0f);
MinSightRange = element.GetAttributeFloat("minsightrange", 0f);
MinSoundRange = element.GetAttributeFloat("minsoundrange", 0f);
MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange);
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange);
FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime);
Static = element.GetAttributeBool("static", Static);
if (Static)
{
SightRange = MaxSightRange;
@@ -163,7 +155,18 @@ namespace Barotrauma
SightRange = MinSightRange;
SoundRange = MinSoundRange;
}
}
public AITarget(Entity e, XElement element) : this(e)
{
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
SoundRange = element.GetAttributeFloat("soundrange", 0.0f);
MinSightRange = element.GetAttributeFloat("minsightrange", 0f);
MinSoundRange = element.GetAttributeFloat("minsoundrange", 0f);
MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange);
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange);
FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime);
Static = element.GetAttributeBool("static", Static);
SonarDisruption = element.GetAttributeFloat("sonardisruption", 0.0f);
SonarLabel = element.GetAttributeString("sonarlabel", "");
SonarIconIdentifier = element.GetAttributeString("sonaricon", "");
@@ -172,6 +175,7 @@ namespace Barotrauma
{
Type = t;
}
Reset();
}
public AITarget(Entity e)
@@ -270,6 +270,17 @@ namespace Barotrauma
}
}
if (isStateChanged)
{
if (State == AIState.Idle)
{
stateResetTimer -= deltaTime;
if (stateResetTimer <= 0)
{
ResetOriginalState();
}
}
}
if (targetIgnoreTimer > 0)
{
targetIgnoreTimer -= deltaTime;
@@ -662,7 +673,7 @@ namespace Barotrauma
attackSimPos = Character.GetRelativeSimPosition(SelectedAiTarget.Entity);
}
if (CanEnterSubmarine)
if (Character.AnimController.CanEnterSubmarine)
{
//targeting a wall section that can be passed through -> steer manually through the hole
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex))
@@ -674,21 +685,20 @@ namespace Barotrauma
return;
}
}
// Don't think we need this
//else if (wallTarget == null && SelectedAiTarget.Entity is Structure wall)
//{
// for (int i = 0; i < wall.Sections.Length; i++)
// {
// WallSection section = wall.Sections[i];
// if (CanPassThroughHole(wall, i) && section?.gap != null)
// {
// if (SteerThroughGap(wall, section, section.gap.WorldPosition, deltaTime))
// {
// return;
// }
// }
// }
//}
else if (SelectedAiTarget.Entity is Structure wall)
{
for (int i = 0; i < wall.Sections.Length; i++)
{
WallSection section = wall.Sections[i];
if (CanPassThroughHole(wall, i) && section?.gap != null)
{
if (SteerThroughGap(wall, section, section.gap.WorldPosition, deltaTime))
{
return;
}
}
}
}
else if (SelectedAiTarget.Entity is Item i)
{
var door = i.GetComponent<Door>();
@@ -1024,7 +1034,7 @@ namespace Barotrauma
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
{
if (SelectedAiTarget != door.Item.AiTarget)
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
{
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
@@ -1076,15 +1086,15 @@ namespace Barotrauma
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
Hull targetHull = section.gap?.FlowTargetHull;
SelectedAiTarget = targetHull != null ? targetHull.AiTarget : wall.AiTarget;
float distance = Vector2.DistanceSquared(Character.WorldPosition, targetWorldPos);
float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height);
if (distance * distance > maxDistance * maxDistance)
if (Vector2.DistanceSquared(Character.WorldPosition, targetWorldPos) > maxDistance * maxDistance)
{
return false;
}
if (targetHull != null)
{
// If already inside, target the hull, else target the wall.
SelectedAiTarget = Character.CurrentHull != null ? targetHull.AiTarget : wall.AiTarget;
if (wall.IsHorizontal)
{
targetWorldPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2;
@@ -1094,6 +1104,7 @@ namespace Barotrauma
targetWorldPos.X = targetHull.WorldRect.Center.X;
}
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
return true;
}
return false;
@@ -1102,9 +1113,16 @@ namespace Barotrauma
private bool CanAttack(Entity target)
{
if (target == null) { return false; }
if (target is Character ch)
if (target is Character c)
{
if (Character.CurrentHull == null && ch.CurrentHull != null || Character.CurrentHull != null && ch.CurrentHull == null)
if (Character.CurrentHull == null && c.CurrentHull != null || Character.CurrentHull != null && c.CurrentHull == null)
{
return false;
}
}
else if (target is Item i && i.GetComponent<Door>() == null)
{
if (Character.CurrentHull == null && i.CurrentHull != null || Character.CurrentHull != null && i.CurrentHull == null)
{
return false;
}
@@ -1175,7 +1193,7 @@ namespace Barotrauma
{
if (wall.SectionBodyDisabled(i))
{
if (CanEnterSubmarine && CanPassThroughHole(wall, i))
if (Character.AnimController.CanEnterSubmarine && CanPassThroughHole(wall, i))
{
sectionIndex = i;
break;
@@ -1204,12 +1222,12 @@ namespace Barotrauma
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
}
LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
if (CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
{
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
}
}
if (!CanEnterSubmarine && wallTarget == null)
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null)
{
if (closestBody.UserData is Structure w && w.Submarine != null || closestBody.UserData is Item i && i.Submarine != null)
{
@@ -1251,12 +1269,18 @@ namespace Barotrauma
return;
}
if (attackResult.Damage > 0.0f && Character.Params.AI.AttackWhenProvoked)
if (attackResult.Damage > 0.0f)
{
if (attacker.Submarine == Character.Submarine && canAttackCharacters ||
attacker.Submarine != null && canAttackSub)
if (Character.Params.AI.AttackWhenProvoked)
{
ChangeTargetState(attacker, AIState.Attack, 100);
if (attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub)
{
ChangeTargetState(attacker, AIState.Attack, 100);
}
}
else if (!AIParams.HasTag(attacker.SpeciesName))
{
ChangeTargetState(attacker, AIState.Flee, 100);
}
}
@@ -1306,7 +1330,8 @@ namespace Barotrauma
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget).Priority);
}
}
if (SelectedAiTarget.Entity is IDamageable damageTarget)
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
if (damageTarget != null)
{
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{
@@ -1480,9 +1505,9 @@ namespace Barotrauma
}
else
{
// Don't attack targets that are not in the same submarine
continue;
}
}
if (targetCharacter.IsDead)
{
@@ -1578,7 +1603,7 @@ namespace Barotrauma
continue;
}
valueModifier = 1;
if (!CanEnterSubmarine && IsWallDisabled(s))
if (!Character.AnimController.CanEnterSubmarine && IsWallDisabled(s))
{
continue;
}
@@ -1587,7 +1612,7 @@ namespace Barotrauma
var section = s.Sections[i];
if (section.gap == null) { continue; }
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
if (CanEnterSubmarine)
if (Character.AnimController.CanEnterSubmarine)
{
if (CanPassThroughHole(s, i))
{
@@ -1766,7 +1791,7 @@ namespace Barotrauma
removals.ForEach(r => targetMemories.Remove(r));
}
private const float targetIgnoreTime = 5;
private readonly float targetIgnoreTime = 5;
private float targetIgnoreTimer;
private readonly HashSet<AITarget> ignoredTargets = new HashSet<AITarget>();
public void IgnoreTarget(AITarget target)
@@ -1775,25 +1800,16 @@ namespace Barotrauma
ignoredTargets.Add(target);
targetIgnoreTimer = targetIgnoreTime * Rand.Range(0.75f, 1.25f);
}
protected override void OnTargetChanged(AITarget previousTarget, AITarget newTarget)
{
if (previousTarget == null || newTarget == null) { return; }
var previousCharacter = previousTarget.Entity as Character;
var newCharacter = newTarget.Entity as Character;
if (previousCharacter == null && newCharacter == null)
{
return;
}
if (previousCharacter != null && newCharacter != null && previousCharacter.SpeciesName == newCharacter.SpeciesName)
{
return;
}
modifiedParams.Keys.ForEachMod(tag => TryResetOriginalState(tag));
}
#endregion
#region State switching
/// <summary>
/// How long do we hold on to the current state after losing a target before we reset back to the original state.
/// In other words, how long do we have to idle before the original state is restored.
/// </summary>
private readonly float stateResetCooldown = 10;
private float stateResetTimer;
private bool isStateChanged;
/// <summary>
/// Resets the target's state to the original value defined in the xml.
@@ -1806,6 +1822,7 @@ namespace Barotrauma
modifiedParams.Remove(tag);
if (tempParams.ContainsKey(tag))
{
tempParams.Values.ForEach(t => AIParams.RemoveTarget(t));
tempParams.Remove(tag);
}
targetParams.Reset();
@@ -1829,23 +1846,39 @@ namespace Barotrauma
/// </summary>
private void ChangeTargetState(Character target, AIState state, float? priority = null)
{
isStateChanged = true;
SetStateResetTimer();
ChangeParams(target.SpeciesName);
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
// --> Target the submarine too.
if (target.Submarine != null && state == AIState.Attack && canAttackSub)
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
if (state == AIState.Attack || state == AIState.Flee)
{
ChangeParams("room");
ChangeParams("wall");
ChangeParams("door");
ChangeParams("weapon");
ChangeParams("tool");
}
if (state == AIState.Attack)
{
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
// --> Target the submarine too.
if (target.Submarine != null && canAttackSub)
{
ChangeParams("room");
ChangeParams("wall");
ChangeParams("door");
}
ChangeParams("provocative", onlyExisting: true);
ChangeParams("light", onlyExisting: true);
}
void ChangeParams(string tag)
void ChangeParams(string tag, bool onlyExisting = false)
{
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
{
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
if (!onlyExisting && !tempParams.ContainsKey(tag))
{
tempParams.Add(tag, targetParams);
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
{
tempParams.Add(tag, targetParams);
}
}
}
if (targetParams != null)
@@ -1862,6 +1895,12 @@ namespace Barotrauma
}
}
}
private void ResetOriginalState()
{
isStateChanged = false;
modifiedParams.Keys.ForEachMod(tag => TryResetOriginalState(tag));
}
#endregion
protected override void OnStateChanged(AIState from, AIState to)
@@ -1873,8 +1912,14 @@ namespace Barotrauma
escapeMargin = 0;
allGapsSearched = false;
unreachableGaps.Clear();
if (isStateChanged && to == AIState.Idle && from != to)
{
SetStateResetTimer();
}
}
private void SetStateResetTimer() => stateResetTimer = stateResetCooldown * Rand.Range(0.75f, 1.25f);
private float GetPerceivingRange(AITarget target) => Math.Max(target.SightRange * Sight, target.SoundRange * Hearing);
private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1)
@@ -711,6 +711,7 @@ namespace Barotrauma
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
{
SetOrderProjSpecific(order, option);
CurrentOrderOption = option;
CurrentOrder = order;
objectiveManager.SetOrder(order, option, orderGiver);
@@ -749,10 +750,9 @@ namespace Barotrauma
Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
}
}
SetOrderProjSpecific(order);
}
partial void SetOrderProjSpecific(Order order);
partial void SetOrderProjSpecific(Order order, string option);
public override void SelectTarget(AITarget target)
{
@@ -970,8 +970,8 @@ namespace Barotrauma
{
bool isValidTarget(Character e) => IsActive(e) && !IsFriendly(character, e);
int enemyCount = visibleHulls == null ?
Character.CharacterList.Count(e => e.CurrentHull == hull && isValidTarget(e)) :
Character.CharacterList.Count(e => visibleHulls.Contains(e.CurrentHull) && isValidTarget(e));
Character.CharacterList.Count(e => isValidTarget(e) && e.CurrentHull == hull) :
Character.CharacterList.Count(e => isValidTarget(e) && visibleHulls.Contains(e.CurrentHull));
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
}
@@ -988,7 +988,7 @@ namespace Barotrauma
return sameSpecies && !differentTeam;
}
public static bool IsActive(Character other) => !other.Removed && !other.IsDead && !other.IsUnconscious;
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;
public static bool IsTrueForAllCrewMembers(Character character, Func<HumanAIController, bool> predicate)
{
@@ -145,9 +145,14 @@ namespace Barotrauma
},
onAbandon: () =>
{
// Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again.
// If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub.
if (currentHull != null && goToObjective != null)
{
HumanAIController.UnreachableHulls.Add(goToObjective.Target as Hull);
if (goToObjective.Target is Hull hull)
{
HumanAIController.UnreachableHulls.Add(hull);
}
}
RemoveSubObjective(ref goToObjective);
});
@@ -172,7 +177,7 @@ namespace Barotrauma
}
foreach (Character enemy in Character.CharacterList)
{
if (HumanAIController.IsFriendly(enemy) || !HumanAIController.IsActive(enemy)) { continue; }
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy)) { continue; }
if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull))
{
Vector2 dir = character.Position - enemy.Position;
@@ -265,6 +265,14 @@ namespace Barotrauma
{
get
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath?.CurrentNode?.Ladders != null)
{
//don't consider the character to be close enough to the target while climbing ladders,
//UNLESS the last node in the path has been reached
//otherwise characters can let go of the ladders too soon once they're close enough to the target
if (PathSteering.CurrentPath.NextNode != null) { return false; }
}
bool closeEnough = Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
if (closeEnough)
{
@@ -25,6 +25,7 @@ namespace Barotrauma
public static Sprite ShortcutNode { get; private set; }
public static Sprite ExpandNode { get; private set; }
public static Sprite NodeContainer { get; private set; }
public static Sprite HotkeyContainer { get; private set; }
public static Sprite CommandBackground { get; private set; }
public static List<Order> PrefabList { get; private set; }
public static Order GetPrefab(string identifier)
@@ -163,6 +164,9 @@ namespace Barotrauma
case "nodecontainer":
NodeContainer = new Sprite(spriteElement, lazyLoad: true);
break;
case "hotkeycontainer":
HotkeyContainer = new Sprite(spriteElement, lazyLoad: true);
break;
case "commandbackground":
CommandBackground = new Sprite(spriteElement, lazyLoad: true);
break;
@@ -312,22 +312,15 @@ namespace Barotrauma
selectedCharacter.selectedBy = null;
selectedCharacter = value;
if (selectedCharacter != null)
{
selectedCharacter.selectedBy = this;
#if CLIENT
if (GameMain.GameSession == null) return;
// Quick & dirty hiding of the chat whenever a character with an accessible inventory is selected to prevent overlaps
if (GameMain.GameSession.CrewManager.IsSinglePlayer)
{
if (GameMain.GameSession.CrewManager.ChatBox == null) return;
GameMain.GameSession.CrewManager.ChatBox.SetVisibility(!(IsHumanoid && value != null && value.Inventory != null && value.CanInventoryBeAccessed));
}
else
{
if (GameMain.Client?.ChatBox == null) return;
GameMain.Client.ChatBox.SetVisibility(!(IsHumanoid && value != null && value.Inventory != null && value.CanInventoryBeAccessed));
}
if (Inventory != null)
{
Inventory.ToggleInventory(true);
}
#endif
}
}
}
@@ -2315,14 +2308,21 @@ namespace Barotrauma
aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000);
}
public bool CanHearCharacter(Character speaker)
{
if (speaker == null || speaker.SpeechImpediment > 100.0f) { return false; }
ChatMessageType messageType = ChatMessage.CanUseRadio(speaker) && ChatMessage.CanUseRadio(this) ?
ChatMessageType.Radio :
ChatMessageType.Default;
return !string.IsNullOrEmpty(ChatMessage.ApplyDistanceEffect("message", messageType, speaker, this));
}
public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true)
{
if (orderGiver != null)
{
//set the character order only if the character is close enough to hear the message
ChatMessageType messageType = ChatMessage.CanUseRadio(orderGiver) && ChatMessage.CanUseRadio(this) ?
ChatMessageType.Radio : ChatMessageType.Default;
if (string.IsNullOrEmpty(ChatMessage.ApplyDistanceEffect("message", messageType, orderGiver, this))) return;
if (!CanHearCharacter(orderGiver)) { return; }
}
HumanAIController humanAI = AIController as HumanAIController;
@@ -13,6 +13,7 @@ namespace Barotrauma
class LimbHealth
{
public Sprite IndicatorSprite;
public Sprite HighlightSprite;
public Rectangle HighlightArea;
@@ -44,6 +45,9 @@ namespace Barotrauma
IndicatorSprite = new Sprite(subElement);
HighlightArea = subElement.GetAttributeRect("highlightarea", new Rectangle(0, 0, (int)IndicatorSprite.size.X, (int)IndicatorSprite.size.Y));
break;
case "highlightsprite":
HighlightSprite = new Sprite(subElement);
break;
case "vitalitymultiplier":
if (subElement.Attribute("name") != null)
{
@@ -61,7 +61,8 @@ namespace Barotrauma
}
public readonly Dictionary<int, XElement> ItemSets = new Dictionary<int, XElement>();
public readonly Dictionary<int, List<string>> ItemNames = new Dictionary<int, List<string>>();
public readonly Dictionary<int, List<string>> ItemIdentifiers = new Dictionary<int, List<string>>();
public readonly Dictionary<int, Dictionary<string, bool>> ShowItemPreview = new Dictionary<int, Dictionary<string, bool>>();
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
public readonly List<AutonomousObjective> AutomaticOrders = new List<AutonomousObjective>();
public readonly List<string> AppropriateOrders = new List<string>();
@@ -184,9 +185,10 @@ namespace Barotrauma
{
case "itemset":
ItemSets.Add(variant, subElement);
var itemNames = new List<string>();
loadItemNames(subElement, itemNames);
ItemNames.Add(variant++, itemNames);
ItemIdentifiers[variant] = new List<string>();
ShowItemPreview[variant] = new Dictionary<string, bool>();
loadItemIdentifiers(subElement, variant);
variant++;
break;
case "skills":
foreach (XElement skillElement in subElement.Elements())
@@ -201,20 +203,19 @@ namespace Barotrauma
case "appropriateorders":
subElement.Elements().ForEach(order => AppropriateOrders.Add(order.GetAttributeString("identifier", "").ToLowerInvariant()));
break;
case "icon":
case "jobicon":
Icon = new Sprite(subElement.FirstElement());
break;
}
}
void loadItemNames(XElement parentElement, List<string> itemNames)
void loadItemIdentifiers(XElement parentElement, int variant)
{
foreach (XElement itemElement in parentElement.GetChildElements("Item"))
{
if (itemElement.Element("name") != null)
{
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - use identifiers instead of names to configure the items.");
itemNames.Add(itemElement.GetAttributeString("name", ""));
continue;
}
@@ -222,22 +223,13 @@ namespace Barotrauma
if (string.IsNullOrWhiteSpace(itemIdentifier))
{
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item with no identifier.");
itemNames.Add("");
}
else
{
var prefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
if (prefab == null)
{
DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item prefab \"" + itemIdentifier + "\" not found.");
itemNames.Add("");
}
else
{
itemNames.Add(prefab.Name);
}
ItemIdentifiers[variant].Add(itemIdentifier);
ShowItemPreview[variant][itemIdentifier] = itemElement.GetAttributeBool("showpreview", true);
}
loadItemNames(itemElement, itemNames);
loadItemIdentifiers(itemElement, variant);
}
}
@@ -536,7 +536,6 @@ namespace Barotrauma
private readonly List<Body> contactBodies = new List<Body>();
private List<Body> ignoredBodies;
/// <summary>
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
/// </summary>
@@ -556,20 +555,11 @@ namespace Barotrauma
case HitDetection.Distance:
if (dist < attack.DamageRange)
{
if (ignoredBodies == null)
{
ignoredBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
}
structureBody = Submarine.PickBody(
SimPosition, attackSimPos,
ignoredBodies, Physics.CollisionWall);
if (damageTarget is Item)
structureBody = Submarine.PickBody(SimPosition, attackSimPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true);
if (damageTarget is Item i && i.GetComponent<Items.Components.Door>() != null)
{
// If the attack is aimed to an item and hits an item, it's successful.
// Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example.
// Ignore blocking checks on doors, because it causes cases where a Mudraptor cannot hit the hatch, for example.
wasHit = true;
}
else if (damageTarget is Structure wall && structureBody != null &&
@@ -464,7 +464,7 @@ namespace Barotrauma
private bool TryAddTarget(XElement targetElement, out TargetParams target)
{
string tag = targetElement.GetAttributeString("tag", null);
if (!CheckTag(tag))
if (!HasTag(tag))
{
target = null;
DebugConsole.ThrowError($"Multiple targets with the same tag ('{tag}') defined! Only the first will be used!");
@@ -487,15 +487,18 @@ namespace Barotrauma
if (TryAddTarget(element, out targetParams))
{
Element.Add(element);
return true;
}
else
{
return false;
}
return targetParams != null;
}
private bool CheckTag(string tag)
public bool HasTag(string tag)
{
if (tag == null) { return false; }
tag = tag.ToLowerInvariant();
return targets.None(t => t.Tag == tag);
return targets.None(t => t.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase));
}
public bool RemoveTarget(TargetParams target) => RemoveSubParam(target, targets);
@@ -511,7 +511,7 @@ namespace Barotrauma
try
{
int count = args.Length == 0 ? 10 : int.Parse(args[0]);
Entity.DumpIds(count);
Entity.DumpIds(count, args.Length >= 2 ? args[1] : null);
}
catch (Exception e)
{
@@ -1550,6 +1550,7 @@ namespace Barotrauma
if (args != null && args.Length > argCount)
{
onAnswered(args[argCount]);
return;
}
#if CLIENT
@@ -5,11 +5,11 @@ using System.Xml.Linq;
namespace Barotrauma
{
class CargoMission : Mission
partial class CargoMission : Mission
{
private XElement itemConfig;
private readonly XElement itemConfig;
private List<Item> items;
private readonly List<Item> items = new List<Item>();
private int requiredDeliveryAmount;
@@ -22,7 +22,7 @@ namespace Barotrauma
private void InitItems()
{
items = new List<Item>();
items.Clear();
if (itemConfig == null)
{
@@ -35,7 +35,7 @@ namespace Barotrauma
LoadItemAsChild(subElement, null);
}
if (requiredDeliveryAmount == 0) requiredDeliveryAmount = items.Count;
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
}
private void LoadItemAsChild(XElement element, Item parent)
@@ -90,8 +90,6 @@ namespace Barotrauma
var item = new Item(itemPrefab, position, cargoRoom.Submarine);
item.FindHull();
items.Add(item);
if (parent != null) parent.Combine(item, user: null);
@@ -108,7 +106,10 @@ namespace Barotrauma
public override void Start(Level level)
{
InitItems();
if (!IsClient)
{
InitItems();
}
}
public override void End()
@@ -127,8 +128,9 @@ namespace Barotrauma
foreach (Item item in items)
{
if (!item.Removed) item.Remove();
if (!item.Removed) { item.Remove(); }
}
items.Clear();
}
}
}
@@ -1,11 +1,12 @@
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Barotrauma
{
partial class Mission
abstract partial class Mission
{
public readonly MissionPrefab Prefab;
protected bool completed;
@@ -192,9 +193,7 @@ namespace Barotrauma
public void GiveReward()
{
var mode = GameMain.GameSession.GameMode as CampaignMode;
if (mode == null) return;
if (!(GameMain.GameSession.GameMode is CampaignMode mode)) { return; }
mode.Money += Reward;
}
}
@@ -3,14 +3,18 @@ using System.Collections.Generic;
using System.Linq;
using System;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma
{
class MonsterMission : Mission
partial class MonsterMission : Mission
{
private readonly string monsterFile;
private readonly int monsterCount;
private readonly HashSet<Tuple<string, int>> monsterFiles = new HashSet<Tuple<string, int>>();
//string = filename, point = min,max
private readonly HashSet<Tuple<string, Point>> monsterFiles = new HashSet<Tuple<string, Point>>();
private readonly List<Character> monsters = new List<Character>();
private readonly List<Vector2> sonarPositions = new List<Vector2>();
@@ -50,7 +54,7 @@ namespace Barotrauma
maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f);
monsterCount = prefab.ConfigElement.GetAttributeInt("monstercount", 1);
monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255);
string monsterFileName = monsterFile;
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
{
@@ -64,9 +68,9 @@ namespace Barotrauma
{
defaultCount = monsterElement.GetAttributeInt("amount", 1);
}
int min = monsterElement.GetAttributeInt("min", defaultCount);
int max = Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount));
monsterFiles.Add(new Tuple<string, int>(monster, Rand.Range(min, max + 1, Rand.RandSync.Server)));
int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
monsterFiles.Add(new Tuple<string, Point>(monster, new Point(min, max)));
}
description = description.Replace("[monster]",
TextManager.Get("character." + System.IO.Path.GetFileNameWithoutExtension(monsterFileName)));
@@ -74,45 +78,50 @@ namespace Barotrauma
public override void Start(Level level)
{
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
bool isClient = IsClient;
if (monsters.Count > 0)
{
throw new Exception($"monsters.Count > 0 ({monsters.Count})");
}
if (!string.IsNullOrEmpty(monsterFile))
{
for (int i = 0; i < monsterCount; i++)
{
monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false));
}
}
foreach (var monster in monsterFiles)
{
for (int i = 0; i < monster.Item2; i++)
{
monsters.Add(Character.Create(monster.Item1, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false));
}
}
if (tempSonarPositions.Count > 0)
{
throw new Exception($"tempSonarPositions.Count > 0 ({tempSonarPositions.Count})");
}
if (!IsClient)
{
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
if (!string.IsNullOrEmpty(monsterFile))
{
for (int i = 0; i < monsterCount; i++)
{
monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
}
}
foreach (var monster in monsterFiles)
{
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
for (int i = 0; i < amount; i++)
{
monsters.Add(Character.Create(monster.Item1, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
}
}
InitializeMonsters(monsters);
}
}
private void InitializeMonsters(IEnumerable<Character> monsters)
{
monsters.ForEach(m => m.Enabled = false);
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
for (int i = 0; i < monsters.Count; i++)
foreach (Character monster in monsters)
{
tempSonarPositions.Add(spawnPos + Rand.Vector(maxSonarMarkerDistance));
tempSonarPositions.Add(monster.WorldPosition + Rand.Vector(maxSonarMarkerDistance));
}
if (monsters.Count != tempSonarPositions.Count)
if (monsters.Count() != tempSonarPositions.Count)
{
throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count} != {tempSonarPositions.Count})");
throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count()} != {tempSonarPositions.Count})");
}
}
@@ -183,5 +192,6 @@ namespace Barotrauma
}
public bool IsEliminated(Character enemy) => enemy.Removed || enemy.IsDead || enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee;
}
}
@@ -6,13 +6,13 @@ using System.Linq;
namespace Barotrauma
{
class SalvageMission : Mission
partial class SalvageMission : Mission
{
private readonly ItemPrefab itemPrefab;
private Item item;
private Level.PositionType spawnPositionType;
private readonly Level.PositionType spawnPositionType;
public override IEnumerable<Vector2> SonarPositions
{
@@ -62,23 +62,26 @@ namespace Barotrauma
public override void Start(Level level)
{
//ruin items are allowed to spawn close to the sub
float minDistance = spawnPositionType == Level.PositionType.Ruin ? 0.0f : Level.Loaded.Size.X * 0.3f;
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
item = new Item(itemPrefab, position, null);
item.body.FarseerBody.BodyType = BodyType.Kinematic;
if (item.HasTag("alien"))
if (!IsClient)
{
//try to find an artifact holder and place the artifact inside it
foreach (Item it in Item.ItemList)
{
if (it.Submarine != null || !it.HasTag("artifactholder")) continue;
//ruin items are allowed to spawn close to the sub
float minDistance = spawnPositionType == Level.PositionType.Ruin ? 0.0f : Level.Loaded.Size.X * 0.3f;
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
item = new Item(itemPrefab, position, null);
item.body.FarseerBody.BodyType = BodyType.Kinematic;
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
if (itemContainer == null) continue;
if (itemContainer.Combine(item, user: null)) break; // Placement successful
if (item.HasTag("alien"))
{
//try to find an artifact holder and place the artifact inside it
foreach (Item it in Item.ItemList)
{
if (it.Submarine != null || !it.HasTag("artifactholder")) continue;
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
if (itemContainer == null) { continue; }
if (itemContainer.Combine(item, user: null)) { break; } // Placement successful
}
}
}
}
@@ -108,7 +111,8 @@ namespace Barotrauma
{
if (item.CurrentHull?.Submarine == null || !item.CurrentHull.Submarine.AtEndPosition || item.Removed) { return; }
item.Remove();
item?.Remove();
item = null;
GiveReward();
completed = true;
}
@@ -49,14 +49,13 @@ namespace Barotrauma
{
PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.ItemPrefab == item);
if (purchasedItem != null && quantity == 1)
campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity;
if (purchasedItem != null)
{
campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice;
purchasedItem.Quantity += 1;
purchasedItem.Quantity += quantity;
}
else
{
campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity;
purchasedItem = new PurchasedItem(item, quantity);
purchasedItems.Add(purchasedItem);
}
@@ -159,6 +159,10 @@ namespace Barotrauma
public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false, bool mirrorLevel = false)
{
//make sure no status effects have been carried on from the next round
//(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully)
StatusEffect.StopAll();
#if CLIENT
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode;
@@ -260,7 +264,18 @@ namespace Barotrauma
if (GameMode.Mission != null) { Mission = GameMode.Mission; }
if (GameMode != null) { GameMode.Start(); }
if (GameMode.Mission != null) { Mission.Start(Level.Loaded); }
if (GameMode.Mission != null)
{
int prevEntityCount = Entity.GetEntityList().Count;
Mission.Start(Level.Loaded);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntityList().Count != prevEntityCount)
{
DebugConsole.ThrowError(
"Entity count has changed after starting a mission as a client. " +
"The clients should not instantiate entities themselves when starting the mission," +
" but instead the server should inform the client of the spawned entities using Mission.ServerWriteInitial.");
}
}
EventManager.StartRound(level);
SteamAchievementManager.OnStartRound();
@@ -16,6 +16,7 @@ namespace Barotrauma
partial class CharacterInventory : Inventory
{
private const int hotkeyCount = 5;
private Character character;
public InvSlotType[] SlotTypes
@@ -52,16 +53,7 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Error in the inventory config of \"" + character.SpeciesName + "\" - " + slotTypeNames[i] + " is not a valid inventory slot type.");
}
SlotTypes[i] = parsedSlotType;
switch (SlotTypes[i])
{
//case InvSlotType.Head:
//case InvSlotType.OuterClothes:
case InvSlotType.LeftHand:
case InvSlotType.RightHand:
hideEmptySlot[i] = true;
break;
}
SlotTypes[i] = parsedSlotType;
}
InitProjSpecific(element);
@@ -130,25 +122,25 @@ namespace Barotrauma
/// <summary>
/// If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equipped into its respective limbslot
/// </summary>
public bool TryPutItemWithAutoEquipCheck(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
public bool TryPutItemWithAutoEquipCheck(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool preferNonHotkeys = false)
{
// Does not auto-equip the item if specified and no suitable any slot found (for example handcuffs are not auto-equipped)
if (item.AllowedSlots.Contains(InvSlotType.Any))
{
var wearable = item.GetComponent<Wearable>();
if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false) == -1)
if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false, preferNonHotkeys) == -1)
{
return false;
}
}
return TryPutItem(item, user, allowedSlots, createNetworkEvent);
return TryPutItem(item, user, allowedSlots, createNetworkEvent, preferNonHotkeys);
}
/// <summary>
/// If there is room, puts the item in the inventory and returns true, otherwise returns false
/// </summary>
public override bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
public override bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool preferNonHotkeys = false)
{
if (allowedSlots == null || !allowedSlots.Any()) return false;
@@ -172,7 +164,7 @@ namespace Barotrauma
//try to place the item in a LimbSlot.Any slot if that's allowed
if (allowedSlots.Contains(InvSlotType.Any) && item.AllowedSlots.Contains(InvSlotType.Any))
{
int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot);
int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot, preferNonHotkeys);
if (freeIndex > -1)
{
PutItem(item, freeIndex, user, true, createNetworkEvent);
@@ -215,60 +207,30 @@ namespace Barotrauma
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
#endif
bool removeFromOtherSlots = item.ParentInventory != this;
if (placedInSlot == -1 && inWrongSlot)
{
if (!hideEmptySlot[i] || SlotTypes[currentSlot] != InvSlotType.Any) removeFromOtherSlots = true;
}
bool removeFromOtherSlots = item.ParentInventory != this || (placedInSlot == -1 && inWrongSlot);
PutItem(item, i, user, removeFromOtherSlots, createNetworkEvent);
item.Equip(character);
placedInSlot = i;
}
}
if (placedInSlot > -1)
{
if (item.AllowedSlots.Contains(InvSlotType.Any) && hideEmptySlot[placedInSlot])
{
bool isInAnySlot = false;
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] == InvSlotType.Any && Items[i]==item)
{
isInAnySlot = true;
break;
}
}
if (!isInAnySlot)
{
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] == InvSlotType.Any && Items[i] == null)
{
Items[i] = item;
break;
}
}
}
}
return true;
}
}
}
return placedInSlot > -1;
}
public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot)
public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot, bool preferNonHotkeys)
{
for (int i = 0; i < capacity; i++)
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
if (Items[i] == item)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
if (Items[i] == item)
{
return i;
}
return i;
}
}
if (!preferNonHotkeys)
{
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
@@ -283,11 +245,45 @@ namespace Barotrauma
return i;
}
}
else
{
int hotkeysCounted = 0;
// First go through non-hotkeyed slots
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
hotkeysCounted++;
if (hotkeysCounted <= hotkeyCount) continue;
if (inWrongSlot)
{
if (Items[i] != item && Items[i] != null) continue;
}
else
{
if (Items[i] != null) continue;
}
#if CLIENT
if (!inventoryOpen)
{
ToggleInventory();
}
#endif
return i;
}
// Then redo with no preference
return CheckIfAnySlotAvailable(item, inWrongSlot, false);
}
return -1;
}
public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool avoidHotkeys = false)
{
if (index < 0 || index >= Items.Length)
{
@@ -110,6 +110,12 @@ namespace Barotrauma.Items.Components
hitTargets.Clear();
IsActive = true;
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange;
item.AiTarget.SightRange = item.AiTarget.MaxSightRange;
}
return false;
}
@@ -82,7 +82,7 @@ namespace Barotrauma.Items.Components
public virtual bool OnPicked(Character picker)
{
if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots))
if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots, true, true))
{
if (!picker.HasSelectedItem(item) && item.body != null) item.body.Enabled = false;
this.picker = picker;
@@ -36,7 +36,6 @@ namespace Barotrauma.Items.Components
public Propulsion(Item item, XElement element)
: base(item,element)
{
ResetSoundRange();
}
public override bool Use(float deltaTime, Character character = null)
@@ -106,19 +105,5 @@ namespace Barotrauma.Items.Components
item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange;
}
}
public override void Unequip(Character character)
{
base.Unequip(character);
ResetSoundRange();
}
private void ResetSoundRange()
{
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = item.AiTarget.MinSoundRange;
}
}
}
}
@@ -82,14 +82,21 @@ namespace Barotrauma.Items.Components
degreeOfFailure *= degreeOfFailure;
return MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure));
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character.Removed) return false;
if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || reloadTimer > 0.0f) return false;
if (character == null || character.Removed) { return false; }
if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || reloadTimer > 0.0f) { return false; }
IsActive = true;
reloadTimer = reload;
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange;
item.AiTarget.SightRange = item.AiTarget.MaxSightRange;
}
List<Body> limbBodies = new List<Body>();
foreach (Limb l in character.AnimController.Limbs)
{
@@ -95,8 +95,10 @@ namespace Barotrauma.Items.Components
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, 0.1f);
if (Math.Abs(Force) > 1.0f)
{
//arbitrary multiplier that was added to changes in submarine mass without having to readjust all engines
float forceMultiplier = 0.1f;
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage / MinVoltage, 1.0f);
Vector2 currForce = new Vector2((force / 10.0f) * maxForce * voltageFactor, 0.0f);
Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f);
//less effective when in a bad condition
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition);
@@ -107,12 +109,12 @@ namespace Barotrauma.Items.Components
if (item.AiTarget != null)
{
var aiTarget = item.AiTarget;
aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, currForce.Length() / maxForce);
aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, Math.Min(currForce.Length() * forceMultiplier / maxForce, 1.0f));
}
if (item.CurrentHull != null)
{
var aiTarget = item.CurrentHull.AiTarget;
float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, currForce.Length() / maxForce);
float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, Math.Min(currForce.Length() * forceMultiplier / maxForce, 1.0f));
aiTarget.SoundRange = Math.Max(noise, aiTarget.SoundRange);
}
#if CLIENT
@@ -79,7 +79,14 @@ namespace Barotrauma.Items.Components
public float Range
{
get { return range; }
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
set
{
range = MathHelper.Clamp(value, 0.0f, 100000.0f);
if (item?.AiTarget != null && item.AiTarget.MaxSoundRange <= 0)
{
item.AiTarget.MaxSoundRange = range;
}
}
}
[Serialize(false, false, description: "Should the sonar display the walls of the submarine it is inside.")]
@@ -84,13 +84,10 @@ namespace Barotrauma.Items.Components
{
if (sender == null || sender.channel != channel) { return false; }
if (sender.TeamID != Character.TeamType.None && TeamID != Character.TeamType.None)
if (sender.TeamID != TeamID)
{
if (sender.TeamID != TeamID)
{
return false;
}
}
return false;
}
if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
@@ -264,7 +264,7 @@ namespace Barotrauma.Items.Components
Character user = item.ParentInventory?.Owner as Character;
removeNodeDelay = (user?.SelectedConstruction == null) ? removeNodeDelay - deltaTime : 0.5f;
Submarine sub = null;
Submarine sub = item.Submarine;
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
@@ -14,7 +14,6 @@ namespace Barotrauma
protected readonly int capacity;
public Item[] Items;
protected bool[] hideEmptySlot;
public bool Locked;
@@ -32,21 +31,16 @@ namespace Barotrauma
this.Owner = owner;
Items = new Item[capacity];
hideEmptySlot = new bool[capacity];
#if CLIENT
this.slotsPerRow = slotsPerRow;
if (SlotSpriteSmall == null)
if (DraggableIndicator == null)
{
//TODO: define these in xml
SlotSpriteSmall = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(0, 0, 128, 128), null, 0);
// Adjustment to match the old size of 75,71
SlotSpriteSmall.size = new Vector2(SlotSpriteSmall.SourceRect.Width * 0.5859375f, SlotSpriteSmall.SourceRect.Height * 0.5546875f);
DraggableIndicator = GUI.Style.GetComponentStyle("GUIDragIndicator").Sprites[GUIComponent.ComponentState.None][0].Sprite;
slotHotkeySprite = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(128, 0, 128, 128), null, 0);
EquipIndicator = new Sprite("Content/UI/inventoryAtlas.png", new Rectangle(673, 182, 73, 27), new Vector2(0.5f, 0.5f), 0);
EquipIndicatorHighlight = new Sprite("Content/UI/inventoryAtlas.png", new Rectangle(679, 108, 67, 21), new Vector2(0.5f, 0.5f), 0);
EquipIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(137, 10, 112, 25), new Vector2(0.5f, 1f), 0);
EquipIndicator.size = new Vector2(EquipIndicator.SourceRect.Width * 0.682f, EquipIndicator.SourceRect.Height * 0.682f);
}
#endif
}
@@ -108,7 +102,7 @@ namespace Barotrauma
/// <summary>
/// If there is room, puts the item in the inventory and returns true, otherwise returns false
/// </summary>
public virtual bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
public virtual bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool avoidHotkeys = false)
{
int slot = FindAllowedSlot(item);
if (slot < 0) return false;
@@ -117,7 +111,7 @@ namespace Barotrauma
return true;
}
public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool avoidHotkeys = false)
{
if (i < 0 || i >= Items.Length)
{
@@ -47,9 +47,9 @@ namespace Barotrauma
return (item != null && Items[i] == null && container.CanBeContained(item));
}
public override bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
public override bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool preferNonHotkeys = false)
{
bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent);
bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent, preferNonHotkeys);
if (wasPut)
{
@@ -68,9 +68,9 @@ namespace Barotrauma
return wasPut;
}
public override bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
public override bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool preferNonHotkeys = false)
{
bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent);
bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, preferNonHotkeys);
if (wasPut)
{
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@@ -283,16 +284,23 @@ namespace Barotrauma
Removed = true;
}
public static void DumpIds(int count)
public static void DumpIds(int count, string filename)
{
List<Entity> entities = dictionary.Values.OrderByDescending(e => e.id).ToList();
count = Math.Min(entities.Count, count);
List<string> lines = new List<string>();
for (int i = 0; i < count; i++)
{
lines.Add(entities[i].id + ": " + entities[i].ToString());
DebugConsole.ThrowError(entities[i].id + ": " + entities[i].ToString());
}
if (!string.IsNullOrWhiteSpace(filename))
{
File.WriteAllLines(filename, lines);
}
}
}
}
@@ -70,7 +70,7 @@ namespace Barotrauma
return false;
}
public void SaveToCache(string filename, long? time=null)
public void SaveToCache(string filename, long? time = null)
{
if (!string.IsNullOrWhiteSpace(filename))
{
@@ -191,9 +191,26 @@ namespace Barotrauma
}
public static string GetShortHash(string fullHash)
{
{
if (string.IsNullOrEmpty(fullHash)) { return ""; }
return fullHash.Length < 7 ? fullHash : fullHash.Substring(0, 7);
}
public static bool RemoveFromCache(string filename)
{
if (!string.IsNullOrWhiteSpace(filename))
{
filename = filename.CleanUpPath();
lock (cache)
{
if (cache.ContainsKey(filename))
{
cache.Remove(filename);
return true;
}
}
}
return false;
}
}
}
@@ -173,6 +173,7 @@ namespace Barotrauma
XDocument doc = OpenFile(filePath);
StartHashDocTask(doc);
hashTask.Wait();
hashTask = null;
}
return hash;
@@ -343,7 +344,10 @@ namespace Barotrauma
public Submarine(string filePath, string hash = "", bool tryLoad = true) : base(null)
{
this.filePath = filePath;
LastModifiedTime = File.GetLastWriteTime(filePath);
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
{
LastModifiedTime = File.GetLastWriteTime(filePath);
}
try
{
name = displayName = Path.GetFileNameWithoutExtension(filePath);
@@ -1631,6 +1635,10 @@ namespace Barotrauma
return false;
}
hash = null;
hashTask = null;
Md5Hash.RemoveFromCache(filePath);
return true;
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -81,43 +82,67 @@ namespace Barotrauma.Networking
readCancellationToken = null;
readStream?.Dispose(); readStream = null;
writeStream?.Dispose(); writeStream = null;
msgsToRead?.Clear(); msgsToWrite?.Clear();
}
private static int ReadIncomingMsgs()
{
Task<int> readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token);
TimeSpan ts = TimeSpan.FromMilliseconds(100);
for (int i = 0; i < 150; i++)
{
if (shutDown)
{
readCancellationToken?.Cancel();
shutDown = true;
return -1;
}
if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(ts) ?? true))
{
break;
}
}
if (readTask == null || !readTask.IsCompleted)
{
readCancellationToken?.Cancel();
shutDown = true;
return -1;
}
if (readTask.Status != TaskStatus.RanToCompletion)
{
shutDown = true;
return -1;
}
return readTask.Result;
}
private static void UpdateRead()
{
while (!shutDown)
{
Task<int> readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token);
TimeSpan ts = TimeSpan.FromMilliseconds(100);
for (int i=0;i<150;i++)
{
if (shutDown)
{
readCancellationToken?.Cancel();
shutDown = true;
return;
}
if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(ts) ?? true))
{
break;
}
}
if (readTask == null || !readTask.IsCompleted)
{
readCancellationToken?.Cancel();
shutDown = true;
return;
}
if (readTask.Status != TaskStatus.RanToCompletion)
#if SERVER
if (!((readStream as AnonymousPipeClientStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#else
if (!((readStream as AnonymousPipeServerStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#endif
int readLen = readTask.Result;
int readLen = ReadIncomingMsgs();
if (readLen < 0) { shutDown = true; return; }
int procIndex = 0;
@@ -125,8 +150,20 @@ namespace Barotrauma.Networking
{
if (readState == ReadState.WaitingForPacketStart)
{
readIncTotal = tempBytes[procIndex] | (tempBytes[procIndex + 1] << 8);
procIndex += 2;
readIncTotal = tempBytes[procIndex];
procIndex++;
if (procIndex >= readLen)
{
readLen = ReadIncomingMsgs();
if (readLen < 0) { shutDown = true; return; }
procIndex = 0;
}
readIncTotal |= (tempBytes[procIndex] << 8);
procIndex++;
if (readIncTotal <= 0) { continue; }
@@ -166,6 +203,19 @@ namespace Barotrauma.Networking
{
while (!shutDown)
{
#if SERVER
if (!((writeStream as AnonymousPipeClientStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#else
if (!((writeStream as AnonymousPipeServerStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#endif
bool msgAvailable; byte[] msg;
lock (msgsToWrite)
{
@@ -176,9 +226,11 @@ namespace Barotrauma.Networking
byte[] lengthBytes = new byte[2];
lengthBytes[0] = (byte)(msg.Length & 0xFF);
lengthBytes[1] = (byte)((msg.Length >> 8) & 0xFF);
msg = lengthBytes.Concat(msg).ToArray();
try
{
writeStream?.Write(lengthBytes, 0, 2);
writeStream?.Write(msg, 0, msg.Length);
}
catch (IOException e)
@@ -199,11 +251,23 @@ namespace Barotrauma.Networking
writeManualResetEvent.Reset();
if (!writeManualResetEvent.WaitOne(1000))
{
//heartbeat to keep the other end alive
byte[] lengthBytes = new byte[2];
lengthBytes[0] = (byte)0;
lengthBytes[1] = (byte)0;
writeStream?.Write(lengthBytes, 0, 2);
if (shutDown)
{
return;
}
try
{
//heartbeat to keep the other end alive
byte[] lengthBytes = new byte[2];
lengthBytes[0] = (byte)0;
lengthBytes[1] = (byte)0;
writeStream?.Write(lengthBytes, 0, 2);
}
catch (IOException e)
{
shutDown = true;
break;
}
}
}
}
@@ -19,7 +19,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?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
targetEntity, targetCharacter, sender)
{
}
@@ -30,10 +30,11 @@ namespace Barotrauma
Init(columnCount, rowCount);
}
public SpriteSheet(string filePath, int columnCount, int rowCount, Vector2 origin)
public SpriteSheet(string filePath, int columnCount, int rowCount, Vector2 origin, Rectangle? sourceRect = null)
: base(filePath, origin)
{
this.origin = origin;
if (sourceRect.HasValue) { SourceRect = sourceRect.Value; }
Init(columnCount, rowCount);
}