(99feac023) Unstable 0.9.704.0
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
+7
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,47 @@
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.9.704.0 (Unstable)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Numerous UI fixes again.
|
||||
- Inventory remake.
|
||||
- Fixed yet another bug that caused inventory items to get mixed up in the campaign.
|
||||
- Fixed a bug that caused clients to get disconnected with an "invalid object header" error when a character has a large amount of different afflictions.
|
||||
- Fixed monster missions occasionally causing "entity not found" errors in the multiplayer campaign.
|
||||
- Rebalanced medical items: stat-boosting effects last much longer.
|
||||
- Modified vigor buffs from steroids and hyperzine to be intantaneous instead of delayed.
|
||||
- Nerfed opiate spawning.
|
||||
- Updated localizations (new texts are now translated to all languages).
|
||||
- Reduced the number of monsters inside ruins in high-difficulty levels, improving performance in those levels.
|
||||
- New loading screen.
|
||||
- Health interface improvements.
|
||||
- Improvements to the command interface.
|
||||
- Fixed clients failing to select a submarine file downloaded from the server if it replaced an older version of the sub.
|
||||
- Fixed performance issues when two items with editable properties are in the inventory (for example a headset and a diving suit).
|
||||
- Fixed header texts not being visible in Russian.
|
||||
- Skill gain values are now moddable (see Content/SkillSettings.xml).
|
||||
- Balanced skill levels and skill gains.
|
||||
- Job variant descriptions and items are now shown when hovering the cursor over the job variant button.
|
||||
- Fix bots occasionally getting stuck on water waypoints.
|
||||
- Fixed bots occasionally letting go of ladders too soon when going to operate an item, causing them to fall down.
|
||||
- The filter in the sub editor only searches from the currently selected category.
|
||||
- Made Unstable watermark less intrusive in sub editor.
|
||||
- Fixed chatbox disappearing when any character is accessing another character's inventory.
|
||||
- Chatbox is hidden after the message has been sent if it was hidden when starting to enter the message.
|
||||
- Fixed wifi components in the respawn shuttle being able to communicate with the main sub in non-combat missions.
|
||||
- Fixed engine sound range being up to 20 times larger than it should be.
|
||||
- Prevent dragging items while dragging inventories.
|
||||
- Fixed monsters occasionally being able to attack characters inside the sub.
|
||||
- Screwdrivers can be used as shivs!
|
||||
- Fixed last wire node appearing to be outside the submarine if a character gets electrocuted while connecting the first end of the wire.
|
||||
- Fixes to bugs that occasionally caused the client hosting a server to get silently disconnected from the server, getting stuck in a "limbo" where they can't interact with anything.
|
||||
- Fix crash with command UI when there is no reactor on the sub.
|
||||
- Fixed all items being highlighted in the multiplayer campaign store menu when another player buys something.
|
||||
- Fixed setting the number of items to buy by typing a number in the text box being practically impossible in the multiplayer campaign store.
|
||||
- Fixed grenades exploding multiple times if you throw one, pick it up before it explodes and rethrow it.
|
||||
- Fixed equipped wire being shown twice in the wiring interface if you connect and disconnect it when the other end of the wire is not connected to anything.
|
||||
- Fixed antibiotic glue being marked as a suitable treatment for gunshot wounds and internal damage, even though it only treats burns and bleeding.
|
||||
- Fixed a waypoint issue in Orca's engineering compartment that prevented the bots from reaching the oxygen generator.
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.9.703.0 (Unstable)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
@@ -11,7 +55,7 @@ v0.9.703.0 (Unstable)
|
||||
- Fixed engines causing crashes if MinVoltage is set to 0.
|
||||
- Fixed multiplayer campaign saves appearing in the single player "load game" menu if they're placed in the singleplayer save folder (leading to a crash if a player starts to load the save).
|
||||
- Fixed fabricator cancellation failing to be communicated under certain circumstances.
|
||||
- Fixed fabricator an deconstructor operating faster when run on overvoltage, making it possible to fabricate/deconstruct things almost instantaneously by using relays.
|
||||
- Fixed fabricator and deconstructor operating faster when run on overvoltage, making it possible to fabricate/deconstruct things almost instantaneously by using relays.
|
||||
- Fixed campaign store filter doing nothing.
|
||||
- Potentially a fixed race condition when autoupdating Workshop items during loading screen.
|
||||
- Fixed subinventories not opening when trying to heal an unconscious character.
|
||||
@@ -59,7 +103,7 @@ v0.9.701.0 (Unstable)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Fixed SteamP2P server hosting.
|
||||
- Fixed a bug that caused "missing entity" errors when joining mid-round. Occurred if entities were created or removed while the client was in the process of getting in sync with the server, for example if the other players were firing turrets while the client was joining. However, there may still other bugs left that can cause the same "missing entity" error message.
|
||||
- Fixed a bug that caused "missing entity" errors when joining mid-round. Occurred if entities were created or removed while the client was in the process of getting in sync with the server, for example if the other players were firing turrets while the client was joining. However, there may still be other bugs left that can cause the same "missing entity" error message.
|
||||
- Cap the framerate to 200 FPS when VSync is off to prevent overloading the GPU. The cap can be adjusted by changing the "framelimit" attribute in config_player.xml.
|
||||
- Rebalanced random events.
|
||||
- Numerous fixes and improvements to Berilia.
|
||||
@@ -136,7 +180,7 @@ Additions and changes:
|
||||
- The job gear variants are not just visually different versions of the same item, but completely separate items. The job variants now allow the players to choose what kind of gear they want to spawn with, not just the look of the uniform.
|
||||
- Attachable items and wire nodes can now be freely placed around the character instead of always being placed at the position of the character. When attaching items/wires, there's a placement grid that makes it much easier to neatly attach/wire things mid-round.
|
||||
- The submarines now get automatically outfitted with a semi-random selection of supplies when starting a campaign. The items that have been manually placed in the submarine editor are kept as-is.
|
||||
- Splitted internal damage into multiple subtypes: blunt force trauma, lacerations and bite wounds. The new afflictions are functionally identical to the default internal damage affliction, but can be used to identify the source of the injuries.
|
||||
- Split internal damage into multiple subtypes: blunt force trauma, lacerations and bite wounds. The new afflictions are functionally identical to the default internal damage affliction, but can be used to identify the source of the injuries.
|
||||
- Humans are more resistant to gunshot wounds, lacerations and blunt force trauma than monsters. The intention is to allow making weapons more effective towards monsters without making killing your crewmates with them too easy.
|
||||
- Added a "terminal" item that can be used to send and display textual signals. Could be used for things such as terminals that send commands to devices or display some data received from devices.
|
||||
- Added muzzle flashes to small firearms.
|
||||
@@ -221,7 +265,7 @@ Monsters:
|
||||
- Fixed Tigerthresher and other creatures that can't attack the submarine bumping on the doors.
|
||||
- Fixed characters occasionally getting stuck while trying to reach the last known position of the previous target.
|
||||
- Fixed a crash when the creature is set to attack when provoked and when the damage source is null.
|
||||
- Fixed extra creature being spawned when using the <monster> elements in the monster misson definitions.
|
||||
- Fixed extra creature being spawned when using the <monster> elements in the monster mission definitions.
|
||||
- Fixed Hammerhead Matriarch's skirt going throught the walls.
|
||||
- Fixed performance issues when creatures are trying to find a path out from the submarine while escaping. Also improved the escaping behavior in general.
|
||||
- Fixed Mudraptors sometimes squeezing themselves towards doors without being able to attack them.
|
||||
@@ -231,7 +275,7 @@ Monsters:
|
||||
Multiplayer:
|
||||
- Fixed players not getting notified in any way when their connection to the server has timed out, allowing them to keep playing without being able to interact with anything.
|
||||
- Fixed clients only being informed of the reason for their ban the moment they're banned, but not if they try to rejoin.
|
||||
- Fixed clients not attempting to reconnect to the server automatically when if the connection is lost, forcing the client to rejoin the server manually.
|
||||
- Fixed clients not attempting to reconnect to the server automatically when the connection is lost, forcing the client to rejoin the server manually.
|
||||
- Karma system can be enabled/disabled in the "host server" menu.
|
||||
- Option to set the number of password retries before a ban.
|
||||
- Fixed voice chat indicators not working in the in-game crew list.
|
||||
|
||||
Reference in New Issue
Block a user