Merge pull request #9 from Regalis11/master

0.14.9.0
This commit is contained in:
Evil Factory
2021-08-25 13:18:02 -03:00
committed by GitHub
140 changed files with 1448 additions and 621 deletions
@@ -300,9 +300,9 @@ namespace Barotrauma
if (containedItem == null) { continue; }
if (predicate == null || predicate(containedItem))
{
if (character.Submarine != Submarine.MainSub && avoidDroppingInSea)
if (avoidDroppingInSea && !character.IsInFriendlySub)
{
// If we are outside of main sub, try to put the item in the inventory instead dropping it in the sea.
// If we are not inside a friendly sub (= same team), try to put the item in the inventory instead dropping it.
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot))
{
continue;
@@ -2527,9 +2527,8 @@ namespace Barotrauma
// Don't target items that we own.
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
if (owner == character) { continue; }
if (owner != null && IsFriendly(Character, owner))
if (owner != null && (IsFriendly(Character, owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
{
// If the item is held by a friendly character, ignore it.
continue;
}
}
@@ -2595,7 +2594,7 @@ namespace Barotrauma
{
if ((SelectedAiTarget != null || wallTarget != null) && IsLatchedOnSub)
{
if (!(SelectedAiTarget.Entity is Structure wall))
if (!(SelectedAiTarget?.Entity is Structure wall))
{
wall = wallTarget?.Structure;
}
@@ -475,22 +475,23 @@ namespace Barotrauma
bool needsGear = NeedsDivingGear(Character.CurrentHull, out _);
if (!needsGear || oxygenLow)
{
bool shouldKeepTheGearOn =
bool isCurrentObjectiveFindSafety = ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>();
bool shouldKeepTheGearOn =
isCurrentObjectiveFindSafety ||
Character.AnimController.InWater ||
Character.AnimController.HeadInWater ||
Character.CurrentHull == null ||
(Character.Submarine?.TeamID != Character.TeamID && !Character.IsEscorted) || // these instances should maybe be combined to a method
ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() ||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
if (oxygenLow && Character.CurrentHull.Oxygen > 0)
Character.Submarine == null ||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10;
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
bool removeDivingSuit = !shouldKeepTheGearOn && !IsOrderedToWait();
if (oxygenLow && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
{
shouldKeepTheGearOn = false;
// Remove the suit before we pass out
removeDivingSuit = true;
}
else if (Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10)
{
shouldKeepTheGearOn = true;
}
bool removeDivingSuit = !shouldKeepTheGearOn && Character.Submarine?.TeamID == Character.TeamID && (!(ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo) || goTo.Target != Character);
bool takeMaskOff = !shouldKeepTheGearOn;
if (!shouldKeepTheGearOn && !oxygenLow)
{
@@ -1348,13 +1349,15 @@ namespace Barotrauma
/// <summary>
/// Check whether the character has a diving suit in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true,
predicate: (Item item) => { return character.HasEquippedItem(item, InvSlotType.OuterClothes); });
public static bool HasDivingSuit(Character character, float conditionPercentage = 0)
=> HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true,
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes));
/// <summary>
/// Check whether the character has a diving mask in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true);
public static bool HasDivingMask(Character character, float conditionPercentage = 0)
=> HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true);
private static List<Item> matchingItems = new List<Item>();
@@ -2048,7 +2051,7 @@ namespace Barotrauma
{
var repairItemsObjective = operatingAI.ObjectiveManager.GetObjective<AIObjectiveRepairItems>();
if (repairItemsObjective == null) { continue; }
if (repairItemsObjective.SubObjectives.None(o => o is AIObjectiveRepairItem repairObjective && repairObjective.Item == target))
if (!(repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is AIObjectiveRepairItem activeObjective) || activeObjective.Item != target)
{
// Not targeting the same item.
continue;
@@ -133,7 +133,7 @@ namespace Barotrauma
}
else
{
if (ItemToContain.ParentInventory == character.Inventory && character.Submarine == Submarine.MainSub)
if (ItemToContain.ParentInventory == character.Inventory && character.IsInFriendlySub)
{
ItemToContain.Drop(character);
}
@@ -53,7 +53,7 @@ namespace Barotrauma
if (HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
(needsSuit ?
!HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN) :
!HumanAIController.HasDivingMask(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN)))
!HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN)))
{
Priority = 100;
}
@@ -108,6 +108,7 @@ namespace Barotrauma
void ReportWeldingFuelTankCount()
{
if (character.Submarine != Submarine.MainSub) { return; }
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("weldingfuel") && i.Condition > 1);
if (remainingOxygenTanks == 0)
{
@@ -131,7 +132,7 @@ namespace Barotrauma
Abandon = true;
return;
}
Vector2 toLeak = Leak.WorldPosition - character.WorldPosition;
Vector2 toLeak = Leak.WorldPosition - character.AnimController.AimSourceWorldPos;
// TODO: use the collider size/reach?
if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150)
{
@@ -157,6 +158,7 @@ namespace Barotrauma
{
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager)
{
UseDistanceRelativeToAimSourcePos = true,
CloseEnough = reach,
DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null,
TargetName = Leak.FlowTargetHull?.DisplayName,
@@ -165,7 +167,7 @@ namespace Barotrauma
onAbandon: () =>
{
if (CheckObjectiveSpecific()) { IsCompleted = true; }
else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > MathUtils.Pow(reach * 2, 2))
else if ((Leak.WorldPosition - character.AnimController.AimSourceWorldPos).LengthSquared() > MathUtils.Pow(reach * 2, 2))
{
// Too far
Abandon = true;
@@ -66,6 +66,11 @@ namespace Barotrauma
public bool AlwaysUseEuclideanDistance { get; set; } = true;
/// <summary>
/// If true, the distance to the destination is calculated from the character's AimSourcePos (= shoulder) instead of the collider's position
/// </summary>
public bool UseDistanceRelativeToAimSourcePos { get; set; } = false;
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
public override bool AllowOutsideSubmarine => AllowGoingOutside;
@@ -237,7 +242,7 @@ namespace Barotrauma
Character followTarget = Target as Character;
bool needsDivingSuit = targetIsOutside;
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
if (!needsDivingGear && mimic)
if (mimic)
{
if (HumanAIController.HasDivingSuit(followTarget))
{
@@ -589,7 +594,9 @@ namespace Barotrauma
float xDiff = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
return xDiff <= CloseEnough;
}
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
Vector2 sourcePos = UseDistanceRelativeToAimSourcePos ? character.AnimController.AimSourceWorldPos : character.WorldPosition;
return Vector2.DistanceSquared(Target.WorldPosition, sourcePos) < CloseEnough * CloseEnough;
}
}
@@ -215,7 +215,9 @@ namespace Barotrauma
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
{
// Don't stop in ladders, because we can't interact with other items while holding the ladders.
endNodeFilter = node => node.Waypoint.Ladders == null
endNodeFilter = node => node.Waypoint.Ladders == null,
// Allow repairing hatches and airlock doors.
AllowGoingOutside = HumanAIController.ObjectiveManager.IsCurrentOrder<AIObjectiveRepairItems>() && Item.GetComponent<Door>() != null
};
if (repairTool != null)
{
@@ -82,6 +82,7 @@ namespace Barotrauma
public static bool ViableForRepair(Item item, Character character, HumanAIController humanAIController)
{
if (!IsValidTarget(item, character)) { return false; }
if (item.CurrentHull == null) { return true; }
if (item.CurrentHull.FireSources.Count > 0) { return false; }
// Don't repair items in rooms that have enemies inside.
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !humanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
@@ -150,7 +151,6 @@ namespace Barotrauma
if (item.IgnoreByAI(character)) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (item.IsFullCondition) { return false; }
if (item.CurrentHull == null) { return false; }
if (item.Submarine == null || character.Submarine == null) { return false; }
//player crew ignores items in outposts
if (character.IsOnPlayerTeam && item.Submarine.Info.IsOutpost) { return false; }
@@ -234,8 +234,6 @@ namespace Barotrauma
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f);
}
character.SelectCharacter(targetCharacter);
}
GiveTreatment(deltaTime);
}
@@ -268,6 +266,8 @@ namespace Barotrauma
}
treatmentTimer = TreatmentDelay;
float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f;
//find which treatments are the most suitable to treat the character's current condition
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false);
@@ -282,6 +282,7 @@ namespace Barotrauma
{
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true);
if (matchingItem == null) { continue; }
if (targetCharacter != character) { character.SelectCharacter(targetCharacter); }
ApplyTreatment(affliction, matchingItem);
//wait a bit longer after applying a treatment to wait for potential side-effects to manifest
treatmentTimer = TreatmentDelay * 4;
@@ -292,7 +293,6 @@ namespace Barotrauma
// Find treatments outside of own inventory only if inside the own sub.
if (character.Submarine != null && character.Submarine.TeamID == character.TeamID)
{
float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f;
//didn't have any suitable treatments available, try to find some medical items
if (currentTreatmentSuitabilities.Any(s => s.Value > cprSuitability))
{
@@ -312,7 +312,7 @@ namespace Barotrauma
}
}
}
if (itemNameList.Count > 0)
if (itemNameList.Any())
{
string itemListStr = "";
if (itemNameList.Count == 1)
@@ -329,7 +329,6 @@ namespace Barotrauma
new string[2] { targetCharacter.Name, itemListStr }, new bool[2] { false, true }),
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
}
character.DeselectCharacter();
RemoveSubObjective(ref getItemObjective);
TryAddSubObjective(ref getItemObjective,
constructor: () => new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
@@ -343,11 +342,31 @@ namespace Barotrauma
}
});
}
else if (cprSuitability <= 0)
{
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
Abandon = true;
}
}
}
else if (!targetCharacter.IsUnconscious)
{
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
Abandon = true;
return;
}
if (character != targetCharacter)
{
character.AnimController.Anim = AnimController.Animation.CPR;
if (cprSuitability > 0.0f)
{
character.SelectCharacter(targetCharacter);
character.AnimController.Anim = AnimController.Animation.CPR;
}
else
{
character.DeselectCharacter();
}
}
}
@@ -63,7 +63,7 @@ namespace Barotrauma
allItems = Wreck.GetItems(false);
thalamusItems = allItems.FindAll(i => IsThalamus(i.prefab));
hulls.AddRange(Wreck.GetHulls(false));
var potentialBrainHulls = new Dictionary<Hull, float>();
var potentialBrainHulls = new List<(Hull hull, float weight)>();
brain = new Item(brainPrefab, Vector2.Zero, Wreck);
thalamusItems.Add(brain);
Point minSize = brain.Rect.Size.Multiply(brain.Scale);
@@ -100,10 +100,10 @@ namespace Barotrauma
}
if (weight > 0)
{
potentialBrainHulls.TryAdd(hull, weight);
potentialBrainHulls.Add((hull, weight));
}
}
Hull brainHull = ToolBox.SelectWeightedRandom(potentialBrainHulls.Keys.ToList(), potentialBrainHulls.Values.ToList(), Rand.RandSync.Server);
Hull brainHull = ToolBox.SelectWeightedRandom(potentialBrainHulls.Select(pbh => pbh.hull).ToList(), potentialBrainHulls.Select(pbh => pbh.weight).ToList(), Rand.RandSync.Server);
var thalamusStructurePrefabs = StructurePrefab.Prefabs.Where(p => IsThalamus(p));
if (brainHull == null)
{
@@ -187,8 +187,11 @@ namespace Barotrauma
if (!spawnOrgans.Contains(item))
{
spawnOrgans.Add(item);
// Try to flood the hull so that the spawner won't die.
item.CurrentHull.WaterVolume = item.CurrentHull.Volume;
if (item.CurrentHull != null)
{
// Try to flood the hull so that the spawner won't die.
item.CurrentHull.WaterVolume = item.CurrentHull.Volume;
}
}
}
}
@@ -108,6 +108,16 @@ namespace Barotrauma
public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR };
public Animation Anim;
public Vector2 AimSourceWorldPos
{
get
{
Vector2 sourcePos = character.AnimController.AimSourcePos;
if (character.Submarine != null) { sourcePos += character.Submarine.Position; }
return sourcePos;
}
}
public Vector2 AimSourcePos => ConvertUnits.ToDisplayUnits(AimSourceSimPos);
public virtual Vector2 AimSourceSimPos => Collider.SimPosition;
@@ -1463,7 +1463,7 @@ namespace Barotrauma
target.CharacterHealth.CalculateVitality();
if (wasCritical && target.Vitality > 0.0f && Timing.TotalTime > lastReviveTime + 10.0f)
{
character.Info.IncreaseSkillLevel("medical", SkillSettings.Current.SkillIncreasePerCprRevive, character.Position + Vector2.UnitY * 150.0f);
character.Info?.IncreaseSkillLevel("medical", SkillSettings.Current.SkillIncreasePerCprRevive, character.Position + Vector2.UnitY * 150.0f);
SteamAchievementManager.OnCharacterRevived(target, character);
lastReviveTime = (float)Timing.TotalTime;
#if SERVER
@@ -576,7 +576,7 @@ namespace Barotrauma
get { return pressureProtection; }
set
{
pressureProtection = Math.Max(value, 0.0f);
pressureProtection = Math.Max(value, pressureProtection);
pressureProtectionLastSet = Timing.TotalTime;
}
}
@@ -638,6 +638,8 @@ namespace Barotrauma
public CharacterHealth CharacterHealth { get; private set; }
public bool DisableHealthWindow;
public float Vitality
{
get { return CharacterHealth.Vitality; }
@@ -882,6 +884,8 @@ namespace Barotrauma
}
}
public bool IsInFriendlySub => Submarine != null && Submarine.TeamID == TeamID;
public delegate void OnDeathHandler(Character character, CauseOfDeath causeOfDeath);
public OnDeathHandler OnDeath;
@@ -2065,10 +2069,9 @@ namespace Barotrauma
if (!CanInteract || inventory.Locked) { return false; }
//the inventory belongs to some other character
if (inventory.Owner is Character && inventory.Owner != this)
if (inventory.Owner is Character character && inventory.Owner != this)
{
var owner = (Character)inventory.Owner;
var owner = character;
//can only be accessed if the character is incapacitated and has been selected
return SelectedCharacter == owner && owner.CanInventoryBeAccessed;
}
@@ -2319,14 +2322,13 @@ namespace Barotrauma
public void SelectCharacter(Character character)
{
if (character == null) return;
if (character == null || character == this) { return; }
SelectedCharacter = character;
}
public void DeselectCharacter()
{
if (SelectedCharacter == null) return;
if (SelectedCharacter == null) { return; }
SelectedCharacter.AnimController?.ResetPullJoints();
SelectedCharacter = null;
}
@@ -2361,7 +2363,7 @@ namespace Barotrauma
#if CLIENT
if (isLocalPlayer)
{
if (!IsMouseOnUI)
if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this))
{
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
{
@@ -2386,6 +2388,7 @@ namespace Barotrauma
}
else
{
FocusedCharacter = null;
focusedItem = null;
}
findFocusedTimer -= deltaTime;
@@ -2707,17 +2710,13 @@ namespace Barotrauma
UpdateAIChatMessages(deltaTime);
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true;
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 1f;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{
tooFastToUnragdoll = false;
}
bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true;
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 5.0f * 5.0f;
if (IsForceRagdolled)
{
IsRagdolled = IsForceRagdolled;
}
else if (IsRemotePlayer)
else if (this != Controlled)
{
IsRagdolled = IsKeyDown(InputType.Ragdoll);
}
@@ -2726,6 +2725,7 @@ namespace Barotrauma
{
if (ragdollingLockTimer > 0.0f)
{
SetInput(InputType.Ragdoll, false, true);
ragdollingLockTimer -= deltaTime;
}
else
@@ -2832,7 +2832,7 @@ namespace Barotrauma
{
if (Timing.TotalTime > pressureProtectionLastSet + 0.1)
{
PressureProtection = 0.0f;
pressureProtection = 0.0f;
}
}
if (NeedsWater)
@@ -2907,7 +2907,7 @@ namespace Barotrauma
}
private float despawnTimer;
private void UpdateDespawn(float deltaTime, bool ignoreThresholds = false)
private void UpdateDespawn(float deltaTime, bool ignoreThresholds = false, bool createNetworkEvents = true)
{
if (!EnableDespawn) { return; }
@@ -2978,10 +2978,10 @@ namespace Barotrauma
if (itemContainer == null) { return; }
foreach (Item inventoryItem in Inventory.AllItemsMod)
{
if (!itemContainer.Inventory.TryPutItem(inventoryItem, user: null))
if (!itemContainer.Inventory.TryPutItem(inventoryItem, user: null, createNetworkEvent: createNetworkEvents))
{
//if the item couldn't be put inside the despawn container, just drop it
inventoryItem.Drop(dropper: this);
inventoryItem.Drop(dropper: this, createNetworkEvent: createNetworkEvents);
}
}
}
@@ -2993,11 +2993,8 @@ namespace Barotrauma
public void DespawnNow(bool createNetworkEvents = true)
{
despawnTimer = GameMain.Config.CorpseDespawnDelay;
UpdateDespawn(1.0f, ignoreThresholds: true);
if (createNetworkEvents)
{
Spawner.Update();
}
UpdateDespawn(1.0f, ignoreThresholds: true, createNetworkEvents: createNetworkEvents);
Spawner.Update(createNetworkEvents);
}
public static void RemoveByPrefab(CharacterPrefab prefab)
@@ -3526,6 +3523,7 @@ namespace Barotrauma
}
bool wasDead = IsDead;
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
float prevVitality = CharacterHealth.Vitality;
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration);
CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking);
if (attacker != this)
@@ -3534,7 +3532,7 @@ namespace Barotrauma
OnAttackedProjSpecific(attacker, attackResult, stun);
if (!wasDead)
{
TryAdjustAttackerSkill(attacker, -attackResult.Damage);
TryAdjustAttackerSkill(attacker, CharacterHealth.Vitality - prevVitality);
if (IsDead)
{
attacker?.RecordKill(this);
@@ -3996,7 +3994,7 @@ namespace Barotrauma
//now there's just one, try to put the extra items where they fit (= stack them)
for (int i = 0; i < inventory.Capacity; i++)
{
if (inventory.CanBePut(newItem, i))
if (inventory.CanBePutInSlot(newItem, i))
{
slotIndices[0] = i;
canBePutInOriginalInventory = true;
@@ -4006,7 +4004,7 @@ namespace Barotrauma
}
else
{
canBePutInOriginalInventory = inventory.CanBePut(newItem, slotIndices[0]);
canBePutInOriginalInventory = inventory.CanBePutInSlot(newItem, slotIndices[0], ignoreCondition: true);
}
if (canBePutInOriginalInventory)
@@ -97,7 +97,7 @@ namespace Barotrauma
State = InfectionState.Final;
ActivateHusk();
ApplyDamage(deltaTime, applyForce: true);
character.SetStun(1);
character.SetStun(5);
}
}
@@ -18,10 +18,10 @@
OnFire, InWater, NotInWater,
OnImpact,
OnEating,
OnDeath = OnBroken,
OnDamaged,
OnSevered,
OnProduceSpawned,
OnOpen, OnClose,
OnDeath = OnBroken,
}
}
@@ -10,8 +10,8 @@ namespace Barotrauma
[Serialize("", true)]
public string NPCTag { get; set; }
[Serialize(0, true)]
public int TeamTag { get; set; }
[Serialize(CharacterTeamType.None, true)]
public CharacterTeamType TeamTag { get; set; }
[Serialize(false, true)]
public bool AddToCrew { get; set; }
@@ -29,11 +29,10 @@ namespace Barotrauma
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
foreach (var npc in affectedNpcs)
{
CharacterTeamType newTeam = (CharacterTeamType)TeamTag;
// characters will still remain on friendlyNPC team for rest of the tick
npc.SetOriginalTeam(newTeam);
npc.SetOriginalTeam(TeamTag);
if (AddToCrew && (newTeam == CharacterTeamType.Team1 || newTeam == CharacterTeamType.Team2))
if (AddToCrew && (TeamTag == CharacterTeamType.Team1 || TeamTag == CharacterTeamType.Team2))
{
npc.Info.StartItemsGiven = true;
@@ -44,11 +43,11 @@ namespace Barotrauma
var wifiComponent = item.GetComponent<Items.Components.WifiComponent>();
if (wifiComponent != null)
{
wifiComponent.TeamID = newTeam;
wifiComponent.TeamID = TeamTag;
}
}
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, newTeam, npc.Inventory.AllItems.Select(it => it.ID).ToArray() });
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, TeamTag, npc.Inventory.AllItems.Select(it => it.ID).ToArray() });
#endif
}
}
@@ -142,7 +142,7 @@ namespace Barotrauma
npcOrItem = item;
item.CampaignInteractionType = CampaignMode.InteractionType.Examine;
if (player.SelectedConstruction == item ||
player.Inventory.Contains(item) ||
player.Inventory != null && player.Inventory.Contains(item) ||
(player.FocusedItem == item && player.IsKeyHit(InputType.Use)))
{
Trigger(e1, e2);
@@ -124,13 +124,24 @@ namespace Barotrauma
}
MTRandom rand = new MTRandom(seed);
var initialEventSet = SelectRandomEvents(EventSet.List, rand);
EventSet initialEventSet = SelectRandomEvents(EventSet.List, rand);
EventSet additiveSet = null;
if (initialEventSet != null && initialEventSet.Additive)
{
additiveSet = initialEventSet;
initialEventSet = SelectRandomEvents(EventSet.List.FindAll(e => !e.Additive), rand);
}
if (initialEventSet != null)
{
pendingEventSets.Add(initialEventSet);
CreateEvents(initialEventSet, rand);
}
if (additiveSet != null)
{
pendingEventSets.Add(additiveSet);
CreateEvents(additiveSet, rand);
}
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
{
//if the outpost is connected to a locked connection, create an event to unlock it
@@ -94,6 +94,8 @@ namespace Barotrauma
public readonly bool TriggerEventCooldown;
public readonly bool Additive;
public readonly Dictionary<string, float> Commonness;
public readonly List<(EventPrefab prefab, float commonness, float probability)> EventPrefabs;
@@ -117,6 +119,8 @@ namespace Barotrauma
MinLevelDifficulty = element.GetAttributeFloat("minleveldifficulty", 0);
MaxLevelDifficulty = Math.Max(element.GetAttributeFloat("maxleveldifficulty", 100), MinLevelDifficulty);
Additive = element.GetAttributeBool("additive", false);
string levelTypeStr = element.GetAttributeString("leveltype", "LocationConnection");
if (!Enum.TryParse(levelTypeStr, true, out LevelType))
{
@@ -24,6 +24,8 @@ namespace Barotrauma
private Submarine sub;
private readonly List<CargoMission> previouslySelectedMissions = new List<CargoMission>();
public override string Description
{
get
@@ -42,7 +44,13 @@ namespace Barotrauma
{
this.sub = sub;
itemConfig = prefab.ConfigElement.Element("Items");
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f);
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f);
//this can get called between rounds when the client receives a campaign save
//don't attempt to determine cargo if the sub hasn't been fully loaded
if (sub == null || sub.Loading || sub.Removed || Submarine.Unloading || !Submarine.Loaded.Contains(sub))
{
return;
}
DetermineCargo();
}
@@ -58,6 +66,30 @@ namespace Barotrauma
List<(ItemContainer container, int freeSlots)> containers = sub.GetCargoContainers();
containers.Sort((c1, c2) => { return c2.container.Capacity.CompareTo(c1.container.Capacity); });
previouslySelectedMissions.Clear();
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
{
bool isPriorMission = true;
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
{
if (!(mission is CargoMission otherMission)) { continue; }
if (mission == this) { isPriorMission = false; }
previouslySelectedMissions.Add(otherMission);
if (!isPriorMission) { continue; }
foreach (var (element, container) in otherMission.itemsToSpawn)
{
for (int i = 0; i < containers.Count; i++)
{
if (containers[i].container == container)
{
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
break;
}
}
}
}
}
maxItemCount = 0;
foreach (XElement subElement in itemConfig.Elements())
{
@@ -87,9 +119,9 @@ namespace Barotrauma
}
calculatedReward = 0;
foreach (var itemToSpawn in itemsToSpawn)
foreach (var (element, container) in itemsToSpawn)
{
int price = itemToSpawn.element.GetAttributeInt("reward", Prefab.Reward / itemsToSpawn.Count);
int price = element.GetAttributeInt("reward", Prefab.Reward / itemsToSpawn.Count);
if (rewardPerCrate.HasValue)
{
if (price != rewardPerCrate.Value) { rewardPerCrate = -1; }
@@ -108,7 +140,28 @@ namespace Barotrauma
public override int GetReward(Submarine sub)
{
if (sub != this.sub)
bool missionsChanged = false;
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
{
List<Mission> currentMissions = GameMain.GameSession.StartLocation.SelectedMissions.Where(m => m is CargoMission).ToList();
if (currentMissions.Count != previouslySelectedMissions.Count)
{
missionsChanged = true;
}
else
{
for (int i = 0; i < previouslySelectedMissions.Count; i++)
{
if (previouslySelectedMissions[i] != currentMissions[i])
{
missionsChanged = true;
break;
}
}
}
}
if (sub != this.sub || missionsChanged)
{
this.sub = sub;
DetermineCargo();
@@ -183,7 +183,7 @@ namespace Barotrauma
}
}
public virtual void SetDifficulty(float difficulty) { }
public virtual void SetLevel(LevelData level) { }
public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false)
{
@@ -423,13 +423,16 @@ namespace Barotrauma
protected Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, Rand.RandSync humanPrefabRandSync = Rand.RandSync.Server, bool giveTags = true)
{
if (positionToStayIn == null)
{
positionToStayIn = WayPoint.GetRandom(SpawnType.Human, null, submarine);
}
var characterInfo = humanPrefab.GetCharacterInfo(Rand.RandSync.Server) ?? new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
characterInfo.TeamID = teamType;
if (positionToStayIn == null)
{
positionToStayIn =
WayPoint.GetRandom(SpawnType.Human, characterInfo.Job?.Prefab, submarine) ??
WayPoint.GetRandom(SpawnType.Human, null, submarine);
}
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
spawnedCharacter.Prefab = humanPrefab;
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
@@ -18,7 +18,7 @@ namespace Barotrauma
//string = filename, point = min,max
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
private readonly float itemSpawnRadius = 800.0f;
private float itemSpawnRadius = 800.0f;
private readonly float approachItemsRadius = 1000.0f;
private readonly float nestObjectRadius = 1000.0f;
private readonly float monsterSpawnRadius = 3000.0f;
@@ -107,6 +107,7 @@ namespace Barotrauma
//ruin/cave/wreck items are allowed to spawn close to the sub
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Cave || spawnPositionType == Level.PositionType.Wreck ?
0.0f : Level.Loaded.Size.X * 0.3f;
nestPosition = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
List<GraphEdge> spawnEdges = new List<GraphEdge>();
if (spawnPositionType == Level.PositionType.Cave)
@@ -149,20 +150,21 @@ namespace Barotrauma
if (!spawnEdges.Any())
{
GraphEdge closestEdge = null;
float closestDist = float.PositiveInfinity;
float closestDistSqr = float.PositiveInfinity;
foreach (var edge in nearbyCells.SelectMany(c => c.Edges))
{
if (!edge.NextToCave || !edge.IsSolid) { continue; }
float dist = Vector2.DistanceSquared(edge.Center, nestPosition);
if (dist < closestDist)
if (dist < closestDistSqr)
{
closestEdge = edge;
closestDist = dist;
closestDistSqr = dist;
}
}
if (closestEdge != null)
{
spawnEdges.Add(closestEdge);
itemSpawnRadius = Math.Max(itemSpawnRadius, (float)Math.Sqrt(closestDistSqr) * 1.5f);
}
}
}
@@ -28,6 +28,8 @@ namespace Barotrauma
private float pirateSightingUpdateTimer;
private Vector2? lastSighting;
private LevelData levelData;
public override int TeamCount => 2;
private bool outsideOfSonarRange;
@@ -83,21 +85,24 @@ namespace Barotrauma
characterTypeConfig = prefab.ConfigElement.Element("CharacterTypes");
addedMissionDifficultyPerPlayer = prefab.ConfigElement.GetAttributeFloat("addedmissiondifficultyperplayer", 0);
// for campaign missions, set difficulty at construction
// for campaign missions, set level at construction
LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.LevelData ?? locations[0]?.LevelData;
SetDifficulty(levelData?.Difficulty ?? Level.Loaded?.Difficulty ?? 0f);
if (levelData != null)
{
SetLevel(levelData);
}
}
public override void SetDifficulty(float difficulty)
public override void SetLevel(LevelData level)
{
if (missionDifficulty > 0f)
if (levelData != null)
{
// difficulty already set
//level already set
return;
}
missionDifficulty = difficulty;
levelData = level;
missionDifficulty = level?.Difficulty ?? 0;
XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier);
@@ -123,23 +128,24 @@ namespace Barotrauma
submarineInfo = new SubmarineInfo(contentFile.Path);
}
private float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier)
private float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier, Random rand)
{
return Math.Abs(levelDifficulty - preferredDifficulty + (Rand.Range(-randomnessModifier, randomnessModifier, Rand.RandSync.Server)));
return Math.Abs(levelDifficulty - preferredDifficulty + MathHelper.Lerp(-randomnessModifier, randomnessModifier, (float)rand.NextDouble()));
}
private int GetDifficultyModifiedAmount(int minAmount, int maxAmount, float levelDifficulty)
private int GetDifficultyModifiedAmount(int minAmount, int maxAmount, float levelDifficulty, Random rand)
{
return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * ((levelDifficulty + Rand.Range(-RandomnessModifier, RandomnessModifier, Rand.RandSync.Server)) / MaxDifficulty)), minAmount);
return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * (levelDifficulty + MathHelper.Lerp(-RandomnessModifier, RandomnessModifier, (float)rand.NextDouble())) / MaxDifficulty), minAmount);
}
private XElement GetRandomDifficultyModifiedElement(XElement parentElement, float levelDifficulty, float randomnessModifier)
{
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
// look for the element that is closest to our difficulty, with some randomness
XElement bestElement = null;
float bestValue = float.MaxValue;
foreach (XElement element in parentElement.Elements())
{
float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier);
float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier, rand);
if (applicabilityValue < bestValue)
{
bestElement = element;
@@ -154,11 +160,11 @@ namespace Barotrauma
Vector2 patrolPos = enemySub.WorldPosition;
Point subSize = enemySub.GetDockedBorders().Size;
if (!Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out preferredSpawnPos))
if (!Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out preferredSpawnPos))
{
DebugConsole.ThrowError("Could not spawn pirate submarine in an interesting location! " + this);
}
if (!Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out patrolPos, preferredSpawnPos, minDistFromPoint: 10000f))
if (!Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out patrolPos, preferredSpawnPos, minDistFromPoint: 10000f))
{
DebugConsole.ThrowError("Could not give pirate submarine an interesting location to patrol to! " + this);
}
@@ -182,7 +188,7 @@ namespace Barotrauma
}
}
private void InitPirateShip(Vector2 spawnPos)
private void InitPirateShip()
{
enemySub.NeutralizeBallast();
if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
@@ -193,6 +199,7 @@ namespace Barotrauma
enemySub.TeamID = CharacterTeamType.None;
//make the enemy sub withstand atleast the same depth as the player sub
enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth);
enemySub.ImmuneToBallastFlora = true;
}
private void InitPirates()
@@ -214,12 +221,14 @@ namespace Barotrauma
float enemyCreationDifficulty = missionDifficulty + playerCount * addedMissionDifficultyPerPlayer;
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
bool commanderAssigned = false;
foreach (XElement element in characterConfig.Elements())
{
// it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional
// if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns
int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty);
int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty, rand);
for (int i = 0; i < amountCreated; i++)
{
XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == element.GetAttributeString("typeidentifier", string.Empty)).FirstOrDefault();
@@ -307,7 +316,7 @@ namespace Barotrauma
#endif
if (!IsClient)
{
InitPirateShip(spawnPos);
InitPirateShip();
}
enemySub.SetPosition(spawnPos);
@@ -651,6 +651,7 @@ namespace Barotrauma
location.ClearMissions();
location.Discovered = false;
location.LevelData?.EventHistory?.Clear();
location.UnlockInitialMissions();
}
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1);
@@ -700,7 +701,7 @@ namespace Barotrauma
if (npc == null || interactor == null) { yield return CoroutineStatus.Failure; }
HumanAIController humanAI = npc.AIController as HumanAIController;
if (humanAI == null) { yield return CoroutineStatus.Failure; }
if (humanAI == null) { yield return CoroutineStatus.Success; }
var waitOrder = Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase));
humanAI.SetForcedOrder(waitOrder, string.Empty, null);
@@ -719,8 +720,10 @@ namespace Barotrauma
#if CLIENT
ShowCampaignUI = false;
#endif
humanAI.ClearForcedOrder();
if (!npc.Removed)
{
humanAI.ClearForcedOrder();
}
yield return CoroutineStatus.Success;
}
@@ -729,13 +732,16 @@ namespace Barotrauma
public void AssignNPCMenuInteraction(Character character, InteractionType interactionType)
{
character.CampaignInteractionType = interactionType;
if (interactionType == InteractionType.None)
character.DisableHealthWindow =
interactionType != InteractionType.None &&
interactionType != InteractionType.Examine &&
interactionType != InteractionType.Talk;
if (interactionType == InteractionType.None)
{
character.SetCustomInteract(null, null);
return;
return;
}
character.CharacterHealth.UseHealthWindow = false;
//character.CanInventoryBeAccessed = false;
character.SetCustomInteract(
NPCInteract,
#if CLIENT
@@ -1,4 +1,5 @@
using Barotrauma.Networking;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma
@@ -29,20 +30,30 @@ namespace Barotrauma
public XElement OrderData { get; private set; }
partial void InitProjSpecific(Client client);
public CharacterCampaignData(Client client)
public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false)
{
Name = client.Name;
InitProjSpecific(client);
healthData = new XElement("health");
client.Character.CharacterHealth.Save(healthData);
if (client.Character.Inventory != null)
client.Character?.CharacterHealth?.Save(healthData);
if (giveRespawnPenaltyAffliction)
{
var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction();
healthData.Add(new XElement("Affliction",
new XAttribute("identifier", respawnPenaltyAffliction.Identifier),
new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture))));
}
if (client.Character?.Inventory != null)
{
itemData = new XElement("inventory");
Character.SaveInventory(client.Character.Inventory, itemData);
}
OrderData = new XElement("orders");
CharacterInfo.SaveOrderData(client.Character.Info, OrderData);
if (client.Character != null)
{
CharacterInfo.SaveOrderData(client.Character.Info, OrderData);
}
}
public CharacterCampaignData(XElement element)
@@ -368,8 +368,8 @@ namespace Barotrauma
foreach (Mission mission in GameMode.Missions)
{
// setting difficulty for missions that may involve difficulty-related submarine creation
mission.SetDifficulty(levelData?.Difficulty ?? 0f);
// setting level for missions that may involve difficulty-related submarine creation
mission.SetLevel(levelData);
}
if (Submarine.MainSubs[1] == null)
@@ -296,10 +296,12 @@ namespace Barotrauma
return;
}
var linkedItems = GetLinkedItemsToSwap(itemToRemove);
int price = 0;
if (!itemToRemove.AvailableSwaps.Contains(itemToInstall))
{
price = itemToInstall.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation);
price = itemToInstall.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count;
}
if (force)
@@ -309,7 +311,7 @@ namespace Barotrauma
if (Campaign.Money >= price)
{
PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToRemove);
PurchasedItemSwaps.RemoveAll(p => linkedItems.Contains(p.ItemToRemove));
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
// only make the NPC speak if more than 5 minutes have passed since the last purchased service
@@ -322,23 +324,27 @@ namespace Barotrauma
Campaign.Money -= price;
itemToRemove.AvailableSwaps.Add(itemToRemove.Prefab);
if (itemToInstall != null && !itemToRemove.AvailableSwaps.Contains(itemToInstall))
foreach (Item itemToSwap in linkedItems)
{
itemToRemove.PurchasedNewSwap = true;
itemToRemove.AvailableSwaps.Add(itemToInstall);
itemToSwap.AvailableSwaps.Add(itemToSwap.Prefab);
if (itemToInstall != null && !itemToSwap.AvailableSwaps.Contains(itemToInstall))
{
itemToSwap.PurchasedNewSwap = true;
itemToSwap.AvailableSwaps.Add(itemToInstall);
}
if (itemToSwap.Prefab != itemToInstall && itemToInstall != null)
{
itemToSwap.PendingItemSwap = itemToInstall;
PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToSwap, itemToInstall));
DebugLog($"CLIENT: Swapped item \"{itemToSwap.Name}\" with \"{itemToInstall.Name}\".", Color.Orange);
}
else
{
DebugLog($"CLIENT: Cancelled swapping the item \"{itemToSwap.Name}\" with \"{(itemToSwap.PendingItemSwap?.Name ?? null)}\".", Color.Orange);
}
}
if (itemToRemove.Prefab != itemToInstall && itemToInstall != null)
{
itemToRemove.PendingItemSwap = itemToInstall;
PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
DebugLog($"CLIENT: Swapped item \"{itemToRemove.Name}\" with \"{itemToInstall.Name}\".", Color.Orange);
}
else
{
DebugLog($"CLIENT: Cancelled swapping the item \"{itemToRemove.Name}\" with \"{(itemToRemove.PendingItemSwap?.Name ?? null)}\".", Color.Orange);
}
OnUpgradesChanged?.Invoke();
}
else
@@ -382,30 +388,55 @@ namespace Barotrauma
}
}
if (itemToRemove.PendingItemSwap == null)
var linkedItems = GetLinkedItemsToSwap(itemToRemove);
foreach (Item itemToCancel in linkedItems)
{
var replacement = MapEntityPrefab.Find("", swappableItem.ReplacementOnUninstall) as ItemPrefab;
if (replacement == null)
if (itemToCancel.PendingItemSwap == null)
{
DebugConsole.ThrowError($"Failed to uninstall item \"{itemToRemove.Name}\". Could not find the replacement item \"{swappableItem.ReplacementOnUninstall}\".");
return;
var replacement = MapEntityPrefab.Find("", swappableItem.ReplacementOnUninstall) as ItemPrefab;
if (replacement == null)
{
DebugConsole.ThrowError($"Failed to uninstall item \"{itemToCancel.Name}\". Could not find the replacement item \"{swappableItem.ReplacementOnUninstall}\".");
return;
}
PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToCancel);
PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToCancel, replacement));
DebugLog($"Uninstalled item item \"{itemToCancel.Name}\".", Color.Orange);
itemToCancel.PendingItemSwap = replacement;
}
else
{
PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToCancel);
DebugLog($"Cancelled swapping the item \"{itemToCancel.Name}\" with \"{itemToCancel.PendingItemSwap.Name}\".", Color.Orange);
itemToCancel.PendingItemSwap = null;
}
PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToRemove);
PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, replacement));
DebugLog($"Uninstalled item item \"{itemToRemove.Name}\".", Color.Orange);
itemToRemove.PendingItemSwap = replacement;
}
else
{
PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToRemove);
DebugLog($"Cancelled swapping the item \"{itemToRemove.Name}\" with \"{itemToRemove.PendingItemSwap.Name}\".", Color.Orange);
itemToRemove.PendingItemSwap = null;
}
#if CLIENT
OnUpgradesChanged?.Invoke();
#endif
}
public List<Item> GetLinkedItemsToSwap(Item item)
{
List<Item> linkedItems = new List<Item>() { item };
foreach (MapEntity linkedEntity in item.linkedTo)
{
foreach (MapEntity secondLinkedEntity in linkedEntity.linkedTo)
{
if (!(secondLinkedEntity is Item linkedItem) || linkedItem == item) { continue; }
if (linkedItem.AllowSwapping &&
linkedItem.Prefab.SwappableItem != null && (linkedItem.Prefab.SwappableItem.CanBeBought || item.Prefab.SwappableItem.ReplacementOnUninstall == linkedItem.prefab.Identifier) &&
linkedItem.Prefab.SwappableItem.SwapIdentifier.Equals(item.Prefab.SwappableItem.SwapIdentifier, StringComparison.OrdinalIgnoreCase))
{
linkedItems.Add(linkedItem);
}
}
}
return linkedItems;
}
/// <summary>
/// Applies all our pending upgrades to the submarine.
/// </summary>
@@ -565,7 +596,7 @@ namespace Barotrauma
/// <param name="submarine"></param>
/// <param name="level"></param>
/// <returns>New level that was applied, -1 if no upgrades were applied.</returns>
private static int BuyUpgrade(UpgradePrefab prefab, UpgradeCategory category, Submarine submarine, int level = 1)
private static int BuyUpgrade(UpgradePrefab prefab, UpgradeCategory category, Submarine submarine, int level = 1, Submarine parentSub = null)
{
int? newLevel = null;
if (category.IsWallUpgrade)
@@ -604,6 +635,7 @@ namespace Barotrauma
foreach (Submarine loadedSub in Submarine.Loaded.Where(sub => sub != submarine))
{
if (loadedSub == parentSub) { continue; }
XElement? root = loadedSub.Info?.SubmarineElement;
if (root == null) { continue; }
@@ -615,7 +647,7 @@ namespace Barotrauma
ushort dockingPortID = (ushort) root.GetAttributeInt("originallinkedto", 0);
if (dockingPortID > 0 && submarine.GetItems(true).Any(item => item.ID == dockingPortID))
{
BuyUpgrade(prefab, category, loadedSub, level);
BuyUpgrade(prefab, category, loadedSub, level, submarine);
}
}
}
@@ -132,17 +132,17 @@ namespace Barotrauma
return false;
}
public override bool CanBePut(Item item, int i)
public override bool CanBePutInSlot(Item item, int i, bool ignoreCondition = false)
{
return
base.CanBePut(item, i) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) &&
base.CanBePutInSlot(item, i, ignoreCondition) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) &&
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
}
public override bool CanBePut(ItemPrefab itemPrefab, int i)
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition)
{
return
base.CanBePut(itemPrefab, i) &&
base.CanBePutInSlot(itemPrefab, i, condition) &&
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
}
@@ -214,7 +214,7 @@ namespace Barotrauma
}
}
if (allowedSlots != null && !allowedSlots.Contains(InvSlotType.Any))
if (allowedSlots != null && allowedSlots.Any() && !allowedSlots.Contains(InvSlotType.Any))
{
bool allSlotsTaken = true;
foreach (var allowedSlot in allowedSlots)
@@ -261,7 +261,7 @@ namespace Barotrauma
/// <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, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
public override bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false)
{
if (allowedSlots == null || !allowedSlots.Any()) { return false; }
if (item == null)
@@ -326,7 +326,7 @@ namespace Barotrauma
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
#endif
if (!slots[i].First().AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(slots[i].FirstOrDefault(), character, new List<InvSlotType> { InvSlotType.Any }, true))
if (!slots[i].First().AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(slots[i].FirstOrDefault(), character, new List<InvSlotType> { InvSlotType.Any }, true, ignoreCondition))
{
free = false;
#if CLIENT
@@ -371,7 +371,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) { continue; }
if (!slots[i].Empty() && CanBePut(item, i))
if (!slots[i].Empty() && CanBePutInSlot(item, i))
{
return i;
}
@@ -387,7 +387,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) { continue; }
if (CanBePut(item, i))
if (CanBePutInSlot(item, i))
{
return i;
}
@@ -402,14 +402,14 @@ namespace Barotrauma
}
else
{
if (!CanBePut(item, i)) { continue; }
if (!CanBePutInSlot(item, i)) { continue; }
}
return i;
}
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 ignoreCondition = false)
{
if (index < 0 || index >= slots.Length)
{
@@ -424,7 +424,7 @@ namespace Barotrauma
if (slots[index].Any())
{
if (slots[index].Contains(item)) { return false; }
return base.TryPutItem(item, index, allowSwapping, allowCombine, user, createNetworkEvent);
return base.TryPutItem(item, index, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition);
}
if (SlotTypes[index] == InvSlotType.Any)
@@ -460,7 +460,7 @@ namespace Barotrauma
if (!slotsFree) { return false; }
return TryPutItem(item, user, new List<InvSlotType>() { placeToSlots }, createNetworkEvent);
return TryPutItem(item, user, new List<InvSlotType>() { placeToSlots }, createNetworkEvent, ignoreCondition);
}
}
}
@@ -167,10 +167,9 @@ namespace Barotrauma.Items.Components
{
foreach (DockingPort port in list)
{
if (port == this || port.item.Submarine == item.Submarine) continue;
if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > DistanceTolerance.X) continue;
if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > DistanceTolerance.Y) continue;
if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; }
if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > DistanceTolerance.X) { continue; }
if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > DistanceTolerance.Y) { continue; }
return port;
}
@@ -82,13 +82,13 @@ namespace Barotrauma.Items.Components
get { return nodes; }
}
private readonly List<Pair<Character,Node>> charactersInRange = new List<Pair<Character, Node>>();
private readonly List<(Character character, Node node)> charactersInRange = new List<(Character character, Node node)>();
private bool charging;
private float timer;
private Attack attack;
private readonly Attack attack;
public ElectricalDischarger(Item item, XElement element) :
base(item, element)
@@ -114,8 +114,8 @@ namespace Barotrauma.Items.Components
{
//already active, do nothing
if (IsActive) { return false; }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
if (character != null && !CharacterUsable) { return false; }
CurrPowerConsumption = powerConsumption;
charging = true;
@@ -182,9 +182,9 @@ namespace Barotrauma.Items.Components
FindNodes(item.WorldPosition, Range);
if (attack != null)
{
foreach (Pair<Character, Node> characterInRange in charactersInRange)
foreach ((Character character, Node node) in charactersInRange)
{
characterInRange.First.ApplyAttack(null, characterInRange.Second.WorldPosition, attack, 1.0f);
character.ApplyAttack(null, node.WorldPosition, attack, 1.0f);
}
}
DischargeProjSpecific();
@@ -315,7 +315,6 @@ namespace Barotrauma.Items.Components
if (closestIndex == -1 || closestDist > currentRange)
{
int originalParentNodeIndex = parentNodeIndex;
//nothing in range, create some arcs to random directions
for (int i = 0; i < Rand.Int(4); i++)
{
@@ -455,7 +454,7 @@ namespace Barotrauma.Items.Components
AddNodesBetweenPoints(currPos, targetPos, 0.25f, ref parentNodeIndex);
nodes.Add(new Node(targetPos, parentNodeIndex));
entitiesInRange.RemoveAt(closestIndex);
charactersInRange.Add(new Pair<Character, Node>(character, nodes[parentNodeIndex]));
charactersInRange.Add((character, nodes[parentNodeIndex]));
FindNodes(entitiesInRange, targetPos, nodes.Count - 1, currentRange);
}
}
@@ -342,7 +342,7 @@ namespace Barotrauma.Items.Components
allowInsideFixture: true);
hitBodies.Clear();
hitBodies.AddRange(bodies);
hitBodies.AddRange(bodies.Distinct());
lastPickedFraction = Submarine.LastPickedFraction;
Type lastHitType = null;
@@ -678,7 +678,7 @@ namespace Barotrauma.Items.Components
Reset();
previousGap = leak;
}
Vector2 fromCharacterToLeak = leak.WorldPosition - character.WorldPosition;
Vector2 fromCharacterToLeak = leak.WorldPosition - character.AnimController.AimSourceWorldPos;
float dist = fromCharacterToLeak.Length();
float reach = AIObjectiveFixLeak.CalculateReach(this, character);
@@ -692,10 +692,10 @@ namespace Barotrauma.Items.Components
if (!character.AnimController.InWater)
{
// TODO: use the collider size?
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController humanAnim &&
Math.Abs(fromCharacterToLeak.X) < 100.0f && fromCharacterToLeak.Y < 0.0f && fromCharacterToLeak.Y > -150.0f)
{
((HumanoidAnimController)character.AnimController).Crouching = true;
humanAnim.Crouching = true;
}
}
if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.inWater))
@@ -12,6 +12,9 @@ namespace Barotrauma.Items.Components
private bool midAir;
//continuous collision detection is used while the item is moving faster than this
const float ContinuousCollisionThreshold = 5.0f;
public Character CurrentThrower
{
get;
@@ -61,6 +64,13 @@ namespace Barotrauma.Items.Components
if (!item.body.Enabled) { return; }
if (midAir)
{
if (item.body.FarseerBody.IsBullet)
{
if (item.body.LinearVelocity.LengthSquared() < ContinuousCollisionThreshold * ContinuousCollisionThreshold)
{
item.body.FarseerBody.IsBullet = false;
}
}
if (item.body.LinearVelocity.LengthSquared() < 0.01f)
{
CurrentThrower = null;
@@ -156,6 +166,7 @@ namespace Barotrauma.Items.Components
//disable platform collisions until the item comes back to rest again
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
item.body.FarseerBody.IsBullet = true;
midAir = true;
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
@@ -165,6 +176,7 @@ namespace Barotrauma.Items.Components
item.body.AngularVelocity = rightHand.body.AngularVelocity;
throwPos = 0;
throwDone = true;
IsActive = true;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
@@ -44,7 +44,7 @@ namespace Barotrauma.Items.Components
protected bool canBeCombined;
protected bool removeOnCombined;
public bool WasUsed;
public bool WasUsed, WasSecondaryUsed;
public readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
namespace Barotrauma.Items.Components
{
@@ -60,7 +61,21 @@ namespace Barotrauma.Items.Components
Drawable = !hideItems;
}
}
#if DEBUG
[Editable]
#endif
[Serialize("0.0,0.0", false, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")]
public Vector2 ItemPos { get; set; }
#if DEBUG
[Editable]
#endif
[Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")]
public Vector2 ItemInterval { get; set; }
[Serialize(100, false, description: "How many items are placed in a row before starting a new row.")]
public int ItemsPerRow { get; set; }
[Serialize(true, false, description: "Should the inventory of this item be visible when the item is selected.")]
public bool DrawInventory
{
@@ -118,6 +133,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false, description: "Should the items configured using SpawnWithId spawn if this item is broken.")]
public bool SpawnWithIdWhenBroken
{
get;
set;
}
[Serialize(false, false)]
public bool RemoveContainedItemsOnDeconstruct { get; set; }
@@ -335,20 +357,66 @@ namespace Barotrauma.Items.Components
public void SetContainedItemPositions()
{
Vector2 simPos = item.SimPosition;
Vector2 displayPos = item.Position;
Vector2 transformedItemPos = ItemPos * item.Scale;
Vector2 transformedItemInterval = ItemInterval * item.Scale;
Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
if (item.body == null)
{
if (item.FlippedX)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemPos.X += item.Rect.Width;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
}
if (item.FlippedY)
{
transformedItemPos.Y = -transformedItemPos.Y;
transformedItemPos.Y -= item.Rect.Height;
transformedItemInterval.Y = -transformedItemInterval.Y;
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
}
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (Math.Abs(item.Rotation) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation));
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
}
}
else
{
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
if (item.body.Dir == -1.0f)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
}
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.Position;
}
float currentRotation = itemRotation;
if (item.body != null)
{
currentRotation += item.body.Rotation;
}
int i = 0;
Vector2 currentItemPos = transformedItemPos;
foreach (Item contained in Inventory.AllItems)
{
if (contained.body != null)
{
try
{
Vector2 simPos = ConvertUnits.ToSimUnits(currentItemPos);
contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, currentRotation);
contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation);
contained.body.UpdateDrawPosition();
@@ -365,14 +433,29 @@ namespace Barotrauma.Items.Components
contained.Rect =
new Rectangle(
(int)(displayPos.X - contained.Rect.Width / 2.0f),
(int)(displayPos.Y + contained.Rect.Height / 2.0f),
(int)(currentItemPos.X - contained.Rect.Width / 2.0f),
(int)(currentItemPos.Y + contained.Rect.Height / 2.0f),
contained.Rect.Width, contained.Rect.Height);
contained.Submarine = item.Submarine;
contained.CurrentHull = item.CurrentHull;
contained.SetContainedItemPositions();
i++;
if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
{
//interval set on both axes -> use a grid layout
currentItemPos += transformedItemIntervalHorizontal;
if (i % ItemsPerRow == 0)
{
currentItemPos = transformedItemPos;
currentItemPos += transformedItemIntervalVertical * (i / ItemsPerRow);
}
}
else
{
currentItemPos += transformedItemInterval;
}
}
}
@@ -400,7 +483,7 @@ namespace Barotrauma.Items.Components
foreach (ushort id in itemIds[i])
{
if (!(Entity.FindEntityByID(id) is Item item)) { continue; }
Inventory.TryPutItem(item, i, false, false, null, false);
Inventory.TryPutItem(item, i, false, false, null, createNetworkEvent: false, ignoreCondition: true);
}
}
itemIds = null;
@@ -410,7 +493,7 @@ namespace Barotrauma.Items.Components
private void SpawnAlwaysContainedItems()
{
if (SpawnWithId.Length > 0)
if (SpawnWithId.Length > 0 && (item.Condition > 0.0f || SpawnWithIdWhenBroken))
{
string[] splitIds = SpawnWithId.Split(',');
foreach (string id in splitIds)
@@ -102,6 +102,12 @@ namespace Barotrauma.Items.Components
get { return limbPositions.Count > 0; }
}
public bool UserInCorrectPosition
{
get;
private set;
}
public bool AllowAiming
{
get;
@@ -149,6 +155,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
this.cam = cam;
UserInCorrectPosition = false;
if (IsToggle)
{
@@ -189,6 +196,7 @@ namespace Barotrauma.Items.Components
else
{
user.AnimController.TargetMovement = Vector2.Zero;
UserInCorrectPosition = true;
}
}
else
@@ -197,7 +205,7 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
{
if (Math.Abs(diff.X) > 20.0f)
{
{
//wait for the character to walk to the correct position
return;
}
@@ -218,7 +226,8 @@ namespace Barotrauma.Items.Components
return;
}
}
user.AnimController.TargetMovement = Vector2.Zero;
user.AnimController.TargetMovement = Vector2.Zero;
UserInCorrectPosition = true;
}
}
@@ -365,7 +374,7 @@ namespace Barotrauma.Items.Components
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
{
if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f) { continue; }
if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f || item.LastSentSignalRecipients[i].IsPower) { continue; }
if (item.LastSentSignalRecipients[i].Item.Prefab.FocusOnSelected)
{
return item.LastSentSignalRecipients[i].Item;
@@ -211,7 +211,7 @@ namespace Barotrauma.Items.Components
{
for (int i = inputContainer.Inventory.Capacity - 2; i >= 0; i--)
{
while (inputContainer.Inventory.GetItemAt(i) is Item item1 && inputContainer.Inventory.CanBePut(item1, i + 1))
while (inputContainer.Inventory.GetItemAt(i) is Item item1 && inputContainer.Inventory.CanBePutInSlot(item1, i + 1))
{
if (!inputContainer.Inventory.TryPutItem(item1, i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true))
{
@@ -170,7 +170,7 @@ namespace Barotrauma.Items.Components
private void StartFabricating(FabricationRecipe selectedItem, Character user, bool addToServerLog = true)
{
if (selectedItem == null) { return; }
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem)) { return; }
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
#if CLIENT
itemList.Enabled = false;
@@ -308,7 +308,7 @@ namespace Barotrauma.Items.Components
}
Character tempUser = user;
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem);
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health);
for (int i = 0; i < fabricatedItem.Amount; i++)
{
if (i < amountFittingContainer)
@@ -334,7 +334,7 @@ namespace Barotrauma.Items.Components
}
}
if (user != null && !user.Removed)
if (user?.Info != null && !user.Removed)
{
foreach (Skill skill in fabricatedItem.RequiredSkills)
{
@@ -50,7 +50,7 @@ namespace Barotrauma.Items.Components
set { maxFlow = value; }
}
[Editable, Serialize(true, false, alwaysUseInstanceValues: true)]
[Editable, Serialize(true, true, alwaysUseInstanceValues: true)]
public bool IsOn
{
get { return IsActive; }
@@ -234,7 +234,10 @@ namespace Barotrauma.Items.Components
//use a smoothed "correct output" instead of the actual correct output based on the load
//so the player doesn't have to keep adjusting the rate impossibly fast when the load fluctuates heavily
correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime;
if (!MathUtils.NearlyEqual(MaxPowerOutput, 0.0f))
{
correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime;
}
//calculate tolerances of the meters based on the skills of the user
//more skilled characters have larger "sweet spots", making it easier to keep the power output at a suitable level
@@ -320,7 +323,7 @@ namespace Barotrauma.Items.Components
//reset the fission rate, turbine output and
//temperature to optimal levels to prevent fires
//at the start of the round
correctTurbineOutput = currentLoad / MaxPowerOutput * 100.0f;
correctTurbineOutput = MathUtils.NearlyEqual(MaxPowerOutput, 0.0f) ? 0.0f : currentLoad / MaxPowerOutput * 100.0f;
tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
@@ -795,6 +795,7 @@ namespace Barotrauma.Items.Components
steeringInput = XMLExtensions.ParseVector2(signal.value, errorMessages: false);
steeringInput.X = MathHelper.Clamp(steeringInput.X, -100.0f, 100.0f);
steeringInput.Y = MathHelper.Clamp(-steeringInput.Y, -100.0f, 100.0f);
TargetVelocity = steeringInput;
}
else
{
@@ -74,8 +74,6 @@ namespace Barotrauma.Items.Components
[Serialize(100f, true, "How much fertilizer can the planter hold.")]
public float FertilizerCapacity { get; set; }
public string LastAction { get; set; } = "";
public Growable?[] GrowableSeeds = new Growable?[0];
private readonly List<RelatedItem> SuitableFertilizer = new List<RelatedItem>();
@@ -119,7 +117,7 @@ namespace Barotrauma.Items.Components
GrowableSeeds = new Growable[container.Capacity];
}
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
public override bool HasRequiredItems(Character character, bool addMessage, string? msg = null)
{
if (container?.Inventory == null) { return false; }
@@ -137,9 +135,16 @@ namespace Barotrauma.Items.Components
return true;
}
Msg = MsgHarvest;
if (GrowableSeeds.Any(s => s != null))
{
Msg = MsgHarvest;
ParseMsg();
return true;
}
Msg = string.Empty;
ParseMsg();
return true;
return false;
}
public override bool Pick(Character character)
@@ -163,9 +168,16 @@ namespace Barotrauma.Items.Components
switch (plantItem.Type)
{
case PlantItemType.Seed:
LastAction = "PlantSeed";
ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
return container.Inventory.TryPutItem(plantItem.Item, character, new List<InvSlotType> { InvSlotType.Any });
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
return container.Inventory.TryPutItem(plantItem.Item, character);
}
else
{
//let the server handle moving the item
return false;
}
case PlantItemType.Fertilizer when plantItem.Item != null:
float canAdd = FertilizerCapacity - Fertilizer;
float maxAvailable = plantItem.Item.Condition;
@@ -175,7 +187,6 @@ namespace Barotrauma.Items.Components
#if CLIENT
character.UpdateHUDProgressBar(this, Item.DrawPosition, Fertilizer / FertilizerCapacity, Color.SaddleBrown, Color.SaddleBrown, "entityname.fertilizer");
#endif
LastAction = "ApplyFertilizer";
ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
return false;
}
@@ -203,7 +214,6 @@ namespace Barotrauma.Items.Components
container?.Inventory.RemoveItem(seed.Item);
Entity.Spawner?.AddToRemoveQueue(seed.Item);
GrowableSeeds[i] = null;
LastAction = "Harvest";
ApplyStatusEffects(ActionType.OnPicked, 1.0f, character);
return true;
}
@@ -133,6 +133,12 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (item.Connections == null)
{
IsActive = false;
return;
}
isRunning = true;
float chargeRatio = charge / capacity;
float gridPower = 0.0f;
@@ -173,7 +179,13 @@ namespace Barotrauma.Items.Components
}
else
{
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f);
float missingCharge = capacity - charge;
float targetRechargeSpeed = rechargeSpeed;
if (missingCharge < 1.0f)
{
targetRechargeSpeed *= missingCharge;
}
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, targetRechargeSpeed, 0.05f);
Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f;
}
@@ -408,21 +408,25 @@ namespace Barotrauma.Items.Components
}
}
bool hitSomething = false;
int hitCount = 0;
Vector2 lastHitPos = item.WorldPosition;
hits = hits.OrderBy(h => h.Fraction).ToList();
foreach (HitscanResult h in hits)
for (int i = 0; i < hits.Count; i++)
{
var h = hits[i];
item.SetTransform(h.Point, rotation);
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
{
LaunchProjSpecific(rayStartWorld, item.WorldPosition);
hitSomething = true;
break;
hitCount++;
if (hitCount >= MaxTargetsToHit || i == hits.Count - 1)
{
LaunchProjSpecific(rayStartWorld, item.WorldPosition);
break;
}
}
}
//the raycast didn't hit anything -> the projectile flew somewhere outside the level and is permanently lost
if (!hitSomething)
//the raycast didn't hit anything (or didn't hit enough targets to stop the projectile) -> the projectile flew somewhere outside the level and is permanently lost
if (hitCount < MaxTargetsToHit)
{
item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation);
LaunchProjSpecific(rayStartWorld, rayEndWorld);
@@ -467,7 +471,7 @@ namespace Barotrauma.Items.Components
}
if (fixture.Body.UserData is VineTile) { return true; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
if (fixture.Body.UserData as string == "ruinroom") { return true; }
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; }
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
if (submarine != null)
@@ -505,7 +509,7 @@ namespace Barotrauma.Items.Components
if (fixture.Body.UserData is VineTile) { return -1; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
if (fixture.Body?.UserData as string == "ruinroom") { return -1; }
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
//ignore everything else than characters, sub walls and level walls
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
@@ -640,7 +644,7 @@ namespace Barotrauma.Items.Components
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
collisionCategory: Physics.CollisionWall);
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure &&
if (wallBody?.FixtureList?.First() != null && (wallBody.UserData is Structure || wallBody.UserData is Item) &&
//ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
{
@@ -738,7 +742,15 @@ namespace Barotrauma.Items.Components
}
else if (target.Body.UserData is IDamageable damageable)
{
if (Attack != null) { attackResult = Attack.DoDamage(User ?? Attacker, damageable, item.WorldPosition, 1.0f); }
if (Attack != null)
{
Vector2 pos = item.WorldPosition;
if (item.Submarine == null && damageable is Structure structure && structure.Submarine != null && Vector2.DistanceSquared(item.WorldPosition, structure.WorldPosition) > 10000.0f * 10000.0f)
{
item.Submarine = structure.Submarine;
}
attackResult = Attack.DoDamage(User ?? Attacker, damageable, pos, 1.0f);
}
}
else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.LevelWallDamage) > 0.0f)
{
@@ -139,7 +139,7 @@ namespace Barotrauma.Items.Components
//backwards compatibility
var repairThresholdAttribute =
element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase)) ??
element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairth44reshold", StringComparison.OrdinalIgnoreCase));
element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairthreshold", StringComparison.OrdinalIgnoreCase));
if (repairThresholdAttribute != null)
{
if (float.TryParse(repairThresholdAttribute.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out float repairThreshold))
@@ -349,7 +349,7 @@ namespace Barotrauma.Items.Components
foreach (Skill skill in requiredSkills)
{
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier,
SkillSettings.Current.SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f),
CurrentFixer.Position + Vector2.UnitY * 100.0f);
}
@@ -379,7 +379,7 @@ namespace Barotrauma.Items.Components
foreach (Skill skill in requiredSkills)
{
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier,
SkillSettings.Current.SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f),
CurrentFixer.Position + Vector2.UnitY * 100.0f);
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -23,7 +24,7 @@ namespace Barotrauma.Items.Components
private int signalQueueSize;
private int delayTicks;
private Queue<DelayedSignal> signalQueue;
private readonly Queue<DelayedSignal> signalQueue;
private DelayedSignal prevQueuedSignal;
@@ -37,7 +38,7 @@ namespace Barotrauma.Items.Components
if (value == delay) { return; }
delay = value;
delayTicks = (int)(delay / Timing.Step);
signalQueueSize = delayTicks * 2;
signalQueueSize = Math.Max(delayTicks, 1) * 2;
}
}
@@ -74,7 +75,14 @@ namespace Barotrauma.Items.Components
var signalOut = signalQueue.Peek();
signalOut.SendDuration -= 1;
item.SendSignal(new Signal(signalOut.Signal.value, strength: signalOut.Signal.strength), "signal_out");
if (signalOut.SendDuration <= 0) { signalQueue.Dequeue(); } else { break; }
if (signalOut.SendDuration <= 0)
{
signalQueue.Dequeue();
}
else
{
break;
}
}
}
@@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components
return retVal;
}
public static bool operator==(Signal a, Signal b) =>
public static bool operator ==(Signal a, Signal b) =>
a.value == b.value &&
a.stepsTaken == b.stepsTaken &&
a.sender == b.sender &&
@@ -35,6 +35,6 @@ namespace Barotrauma.Items.Components
MathUtils.NearlyEqual(a.power, b.power) &&
MathUtils.NearlyEqual(a.strength, b.strength);
public static bool operator!=(Signal a, Signal b) => !(a == b);
public static bool operator !=(Signal a, Signal b) => !(a == b);
}
}
@@ -77,11 +77,10 @@ namespace Barotrauma.Items.Components
//item in water -> we definitely want to send the True output
isInWater = true;
}
else if (item.CurrentHull != null)
else if (item.CurrentHull != null && item.CurrentHull.WaterPercentage > 0.0f)
{
//item in not water -> check if there's water anywhere within the rect of the item
if (item.CurrentHull.Surface > item.CurrentHull.Rect.Y - item.CurrentHull.Rect.Height + 1 &&
item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
//(center of the) item in not water -> check if the water surface is below the bottom of the item's rect
if (item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
{
isInWater = true;
}
@@ -844,10 +844,11 @@ namespace Barotrauma.Items.Components
ClearConnections();
base.RemoveComponentSpecific();
#if CLIENT
if (DraggingWire == this) { draggingWire = null; }
overrideSprite?.Remove();
overrideSprite = null;
wireSprite = null;
#endif
}
}
}
}
@@ -562,6 +562,7 @@ namespace Barotrauma.Items.Components
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) { continue; }
if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; }
if (linkedItem.Condition <= 0.0f)
{
loaderBroken = true;
@@ -971,6 +972,7 @@ namespace Barotrauma.Items.Components
foreach (MapEntity e in item.linkedTo)
{
if (!item.IsInteractable(character)) { continue; }
if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; }
if (e is Item projectileContainer)
{
var container = projectileContainer.GetComponent<ItemContainer>();
@@ -1323,6 +1325,7 @@ namespace Barotrauma.Items.Components
CheckProjectileContainer(item, projectiles, out bool _);
foreach (MapEntity e in item.linkedTo)
{
if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; }
if (e is Item projectileContainer)
{
CheckProjectileContainer(projectileContainer, projectiles, out bool stopSearching);
@@ -28,22 +28,25 @@ namespace Barotrauma
get { return items.Count; }
}
public bool CanBePut(Item item)
public bool CanBePut(Item item, bool ignoreCondition = false)
{
if (item == null) { return false; }
if (items.Count > 0)
{
if (item.IsFullCondition)
if (!ignoreCondition)
{
if (items.Any(it => !it.IsFullCondition)) { return false; }
}
else if (MathUtils.NearlyEqual(item.Condition, 0.0f))
{
if (items.Any(it => !MathUtils.NearlyEqual(it.Condition, 0.0f))) { return false; }
}
else
{
return false;
if (item.IsFullCondition)
{
if (items.Any(it => !it.IsFullCondition)) { return false; }
}
else if (MathUtils.NearlyEqual(item.Condition, 0.0f))
{
if (items.Any(it => !MathUtils.NearlyEqual(it.Condition, 0.0f))) { return false; }
}
else
{
return false;
}
}
if (items[0].Prefab.Identifier != item.Prefab.Identifier ||
items.Count + 1 > item.Prefab.MaxStackSize)
@@ -54,12 +57,31 @@ namespace Barotrauma
return true;
}
public bool CanBePut(ItemPrefab itemPrefab)
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null)
{
if (itemPrefab == null) { return false; }
if (items.Count > 0)
{
if (items.Any(it => !it.IsFullCondition)) { return false; }
if (condition.HasValue)
{
if (MathUtils.NearlyEqual(condition.Value, 0.0f))
{
if (items.Any(it => it.Condition > 0.0f)) { return false; }
}
else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.Health))
{
if (items.Any(it => !it.IsFullCondition)) { return false; }
}
else
{
return false;
}
}
else
{
if (items.Any(it => !it.IsFullCondition)) { return false; }
}
if (items[0].Prefab.Identifier != itemPrefab.Identifier ||
items.Count + 1 > itemPrefab.MaxStackSize)
{
@@ -70,13 +92,31 @@ namespace Barotrauma
}
/// <param name="maxStackSize">Defaults to <see cref="ItemPrefab.MaxStackSize"/> if null</param>
public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null)
public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null, float? condition = null)
{
if (itemPrefab == null) { return 0; }
maxStackSize ??= itemPrefab.MaxStackSize;
if (items.Count > 0)
{
if (items.Any(it => !it.IsFullCondition)) { return 0; }
if (condition.HasValue)
{
if (MathUtils.NearlyEqual(condition.Value, 0.0f))
{
if (items.Any(it => it.Condition > 0.0f)) { return 0; }
}
else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.Health))
{
if (items.Any(it => !it.IsFullCondition)) { return 0; }
}
else
{
return 0;
}
}
else
{
if (items.Any(it => !it.IsFullCondition)) { return 0; }
}
if (items[0].Prefab.Identifier != itemPrefab.Identifier) { return 0; }
return maxStackSize.Value - items.Count;
}
@@ -340,7 +380,7 @@ namespace Barotrauma
return ownerItem.ParentInventory.ItemOwnsSelf(item);
}
public virtual int FindAllowedSlot(Item item)
public virtual int FindAllowedSlot(Item item, bool ignoreCondition = false)
{
if (ItemOwnsSelf(item)) { return -1; }
@@ -352,7 +392,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++)
{
if (slots[i].CanBePut(item)) { return i; }
if (slots[i].CanBePut(item, ignoreCondition)) { return i; }
}
return -1;
@@ -365,7 +405,7 @@ namespace Barotrauma
{
for (int i = 0; i < capacity; i++)
{
if (CanBePut(item, i)) { return true; }
if (CanBePutInSlot(item, i)) { return true; }
}
return false;
}
@@ -373,57 +413,57 @@ namespace Barotrauma
/// <summary>
/// Can the item be put in the specified slot.
/// </summary>
public virtual bool CanBePut(Item item, int i)
public virtual bool CanBePutInSlot(Item item, int i, bool ignoreCondition = false)
{
if (ItemOwnsSelf(item)) { return false; }
if (i < 0 || i >= slots.Length) { return false; }
return slots[i].CanBePut(item);
return slots[i].CanBePut(item, ignoreCondition);
}
public bool CanBePut(ItemPrefab itemPrefab)
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null)
{
for (int i = 0; i < capacity; i++)
{
if (CanBePut(itemPrefab, i)) { return true; }
if (CanBePutInSlot(itemPrefab, i, condition)) { return true; }
}
return false;
}
public virtual bool CanBePut(ItemPrefab itemPrefab, int i)
public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null)
{
if (i < 0 || i >= slots.Length) { return false; }
return slots[i].CanBePut(itemPrefab);
return slots[i].CanBePut(itemPrefab, condition);
}
public int HowManyCanBePut(ItemPrefab itemPrefab)
public int HowManyCanBePut(ItemPrefab itemPrefab, float? condition = null)
{
int count = 0;
for (int i = 0; i < capacity; i++)
{
count += HowManyCanBePut(itemPrefab, i);
count += HowManyCanBePut(itemPrefab, i, condition);
}
return count;
}
public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i)
public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition)
{
if (i < 0 || i >= slots.Length) { return 0; }
return slots[i].HowManyCanBePut(itemPrefab);
return slots[i].HowManyCanBePut(itemPrefab, condition: condition);
}
/// <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, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
public virtual bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false)
{
int slot = FindAllowedSlot(item);
int slot = FindAllowedSlot(item, ignoreCondition);
if (slot < 0) { return false; }
PutItem(item, slot, user, true, createNetworkEvent);
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 ignoreCondition = false)
{
if (i < 0 || i >= slots.Length)
{
@@ -441,12 +481,12 @@ namespace Barotrauma
//item in the slot removed as a result of combining -> put this item in the now free slot
if (!slots[i].Any())
{
return TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent);
return TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition);
}
return true;
}
}
if (CanBePut(item, i))
if (CanBePutInSlot(item, i, ignoreCondition))
{
PutItem(item, i, user, true, createNetworkEvent);
return true;
@@ -150,8 +150,10 @@ namespace Barotrauma
set
{
parentInventory = value;
if (parentInventory != null) Container = parentInventory.Owner as Item;
if (parentInventory != null) { Container = parentInventory.Owner as Item; }
#if SERVER
PreviousParentInventory = value;
#endif
}
}
@@ -241,7 +243,7 @@ namespace Barotrauma
private float rotationRad;
[Editable(0.0f, 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, true)]
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowRotating, MinValueFloat = 0.0f, MaxValueFloat = 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, true)]
public float Rotation
{
get
@@ -723,11 +725,7 @@ namespace Barotrauma
public override string ToString()
{
#if CLIENT
return (GameMain.DebugDraw) ? Name + " (ID: " + ID + ")" : Name;
#elif SERVER
return Name + " (ID: " + ID + ")";
#endif
}
private readonly List<ISerializableEntity> allPropertyObjects = new List<ISerializableEntity>();
@@ -1593,14 +1591,12 @@ namespace Barotrauma
{
ic.PlaySound(ActionType.Always);
ic.UpdateSounds();
if (!ic.WasUsed)
{
ic.StopSounds(ActionType.OnUse);
ic.StopSounds(ActionType.OnSecondaryUse);
}
if (!ic.WasUsed) { ic.StopSounds(ActionType.OnUse); }
if (!ic.WasSecondaryUsed) { ic.StopSounds(ActionType.OnSecondaryUse); }
}
#endif
ic.WasUsed = false;
ic.WasSecondaryUsed = false;
if (ic.IsActive)
{
@@ -2031,7 +2027,7 @@ namespace Barotrauma
//if there's an equal signal waiting to be sent
//to the same connection, don't add a new one
signal.stepsTaken = 0;
if (!delayedSignals.Contains((signal, connection)))
if (!delayedSignals.Any(s => s.Connection == connection && s.Signal.source == signal.source && s.Signal.value == signal.value && s.Signal.sender == signal.sender))
{
delayedSignals.Add((signal, connection));
CoroutineManager.StartCoroutine(DelaySignal(signal, connection));
@@ -2053,8 +2049,11 @@ namespace Barotrauma
private IEnumerable<object> DelaySignal(Signal signal, Connection connection)
{
//wait one frame
yield return CoroutineStatus.Running;
do
{
//wait at least one frame
yield return CoroutineStatus.Running;
} while (CoroutineManager.DeltaTime <= 0.0f);
delayedSignals.Remove((signal, connection));
@@ -2288,7 +2287,7 @@ namespace Barotrauma
if (!ic.HasRequiredContainedItems(character, isControlled)) { continue; }
if (ic.SecondaryUse(deltaTime, character))
{
ic.WasUsed = true;
ic.WasSecondaryUsed = true;
#if CLIENT
ic.PlaySound(ActionType.OnSecondaryUse, character);
@@ -2829,7 +2828,7 @@ namespace Barotrauma
int level = subElement.GetAttributeInt("level", 1);
if (upgradePrefab != null)
{
item.AddUpgrade(new Upgrade(item, upgradePrefab, level, subElement));
item.AddUpgrade(new Upgrade(item, upgradePrefab, level, appliedSwap != null ? null : subElement));
}
else
{
@@ -21,7 +21,7 @@ namespace Barotrauma
this.container = container;
}
public override int FindAllowedSlot(Item item)
public override int FindAllowedSlot(Item item, bool ignoreCondition = false)
{
if (ItemOwnsSelf(item)) { return -1; }
@@ -32,38 +32,38 @@ namespace Barotrauma
//try to stack first
for (int i = 0; i < capacity; i++)
{
if (slots[i].Any() && CanBePut(item, i)) { return i; }
if (slots[i].Any() && CanBePutInSlot(item, i, ignoreCondition)) { return i; }
}
for (int i = 0; i < capacity; i++)
{
if (CanBePut(item, i)) { return i; }
if (CanBePutInSlot(item, i, ignoreCondition)) { return i; }
}
return -1;
}
public override bool CanBePut(Item item, int i)
public override bool CanBePutInSlot(Item item, int i, bool ignoreCondition = false)
{
if (ItemOwnsSelf(item)) { return false; }
if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(item)) { return false; }
return item != null && slots[i].CanBePut(item) && slots[i].ItemCount < container.MaxStackSize;
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.MaxStackSize;
}
public override bool CanBePut(ItemPrefab itemPrefab, int i)
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition)
{
if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(itemPrefab)) { return false; }
return itemPrefab != null && slots[i].CanBePut(itemPrefab) && slots[i].ItemCount < container.MaxStackSize;
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.MaxStackSize;
}
public override int HowManyCanBePut(ItemPrefab itemPrefab, int i)
public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition)
{
if (itemPrefab == null) { return 0; }
if (i < 0 || i >= slots.Length) { return 0; }
if (!container.CanBeContained(itemPrefab)) { return 0; }
return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.MaxStackSize));
return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.MaxStackSize), condition);
}
public override bool IsFull(bool takeStacksIntoAccount = false)
@@ -88,9 +88,9 @@ namespace Barotrauma
return true;
}
public override bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
public override bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false)
{
bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent);
bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent, ignoreCondition);
if (wasPut)
{
@@ -108,9 +108,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 ignoreCondition = false)
{
bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent);
bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition);
if (wasPut && item.ParentInventory == this)
{
foreach (Character c in Character.CharacterList)
@@ -942,6 +942,7 @@ namespace Barotrauma.MapCreatures.Behavior
Anger += 0.01f;
bool wasRemoved = branch.Removed;
Branches.Remove(branch);
branch.Removed = true;
@@ -986,9 +987,11 @@ namespace Barotrauma.MapCreatures.Behavior
Kill();
return;
}
#if SERVER
SendNetworkMessage(this, NetworkHeader.BranchRemove, branch);
if (!wasRemoved)
{
SendNetworkMessage(this, NetworkHeader.BranchRemove, branch);
}
#endif
}
@@ -42,7 +42,7 @@ namespace Barotrauma
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f)
{
Attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
Attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, Math.Min(range, 1000000))
{
SeverLimbsProbability = 1.0f
};
@@ -57,6 +57,7 @@ namespace Barotrauma
get { return open; }
set
{
if (float.IsNaN(value)) { return; }
if (value > open) { openedTimer = 1.0f; }
open = MathHelper.Clamp(value, 0.0f, 1.0f);
}
@@ -525,7 +525,13 @@ namespace Barotrauma
public override MapEntity Clone()
{
return new Hull(MapEntityPrefab.Find(null, "hull"), rect, Submarine);
var clone = new Hull(MapEntityPrefab.Find(null, "hull"), rect, Submarine);
foreach (KeyValuePair<string, SerializableProperty> property in SerializableProperties)
{
if (!property.Value.Attributes.OfType<Editable>().Any()) { continue; }
clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
}
return clone;
}
public static EntityGrid GenerateEntityGrid(Rectangle worldRect)
@@ -1837,7 +1837,7 @@ namespace Barotrauma
foreach (RuinShape ruinShape in ruin.RuinShapes)
{
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height));
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height) * 4);
foreach (VoronoiCell cell in tooClose)
{
@@ -1984,7 +1984,7 @@ namespace Barotrauma
private DestructibleLevelWall CreateIceSpire(List<GraphEdge> usedSpireEdges)
{
const float maxLength = 15000.0f;
float minEdgeLength = 100.0f;
var mainPathPos = PositionsOfInterest.Where(pos => pos.PositionType == PositionType.MainPath).GetRandom(Rand.RandSync.Server);
double closestDistSqr = double.PositiveInfinity;
GraphEdge closestEdge = null;
@@ -1999,8 +1999,9 @@ namespace Barotrauma
if (edge.Center.Y > Size.Y / 2 && (edge.Center.X < Size.X * 0.3f || edge.Center.X > Size.X * 0.7f)) { continue; }
if (Vector2.DistanceSquared(edge.Center, StartPosition) < maxLength * maxLength) { continue; }
if (Vector2.DistanceSquared(edge.Center, EndPosition) < maxLength * maxLength) { continue; }
//don't spawn on very long edges
if (Vector2.DistanceSquared(edge.Point1, edge.Point2) > 1000.0f * 1000.0f) { continue; }
//don't spawn on very long or very short edges
float edgeLengthSqr = Vector2.DistanceSquared(edge.Point1, edge.Point2);
if (edgeLengthSqr > 1000.0f * 1000.0f || edgeLengthSqr < minEdgeLength * minEdgeLength) { continue; }
//don't spawn on edges facing away from the main path
if (Vector2.Dot(Vector2.Normalize(mainPathPos.Position.ToVector2()) - edge.Center, edge.GetNormal(cell)) < 0.5f) { continue; }
double distSqr = MathUtils.DistanceSquared(edge.Center.X, edge.Center.Y, mainPathPos.Position.X, mainPathPos.Position.Y);
@@ -2723,7 +2724,7 @@ namespace Barotrauma
if (tries == 10)
{
position = EndPosition - Vector2.UnitY * 300.0f;
position = startPos;
}
} while (tries < 10);
@@ -3048,8 +3049,10 @@ namespace Barotrauma
var waypoints = WayPoint.WayPointList.Where(wp =>
wp.Submarine == null &&
wp.SpawnType == SpawnType.Path &&
wp.WorldPosition.X < EndExitPosition.X &&
!IsCloseToStart(wp.WorldPosition, minDistance) &&
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
!IsCloseToEnd(wp.WorldPosition, minDistance)
).ToList();
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
@@ -3134,7 +3137,7 @@ namespace Barotrauma
}
tempSW.Stop();
Debug.WriteLine($"Sub {sub.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)");
sub.SetPosition(spawnPoint);
sub.SetPosition(spawnPoint, forceUndockFromStaticSubmarines: false);
wreckPositions.Add(sub, positions);
blockedRects.Add(sub, rects);
return sub;
@@ -75,6 +75,10 @@ namespace Barotrauma
}
Cells = new List<VoronoiCell>() { wallCell };
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
if (triangles.Count == 0)
{
throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString())));
}
#if CLIENT
GenerateVertices();
#endif
@@ -192,6 +192,7 @@ namespace Barotrauma
if (!SelectedMissions.Contains(mission) && mission != null)
{
selectedMissions.Add(mission);
selectedMissions.Sort((m1, m2) => availableMissions.IndexOf(m1).CompareTo(availableMissions.IndexOf(m2)));
}
}
@@ -461,6 +462,18 @@ namespace Barotrauma
CreateStore(force: true);
}
public void UnlockInitialMissions()
{
if (Type.MissionIdentifiers.Any())
{
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(Rand.RandSync.Server));
}
if (Type.MissionTags.Any())
{
UnlockMissionByTag(Type.MissionTags.GetRandom(Rand.RandSync.Server));
}
}
public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
{
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
@@ -484,14 +484,7 @@ namespace Barotrauma
{
Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f)
};
if (location.Type.MissionIdentifiers.Any())
{
location.UnlockMissionByIdentifier(location.Type.MissionIdentifiers.GetRandom());
}
if (location.Type.MissionTags.Any())
{
location.UnlockMissionByTag(location.Type.MissionTags.GetRandom());
}
location.UnlockInitialMissions();
}
foreach (LocationConnection connection in Connections)
{
@@ -1078,7 +1078,7 @@ namespace Barotrauma
#endif
}
float gapOpen = (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
float gapOpen = MaxHealth <= 0.0f ? 0.0f : (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
Sections[sectionIndex].gap.Open = gapOpen;
}
@@ -1095,7 +1095,7 @@ namespace Barotrauma
{
if (damageDiff < 0.0f)
{
attacker.Info.IncreaseSkillLevel("mechanical",
attacker.Info?.IncreaseSkillLevel("mechanical",
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f),
SectionPosition(sectionIndex));
}
@@ -282,9 +282,11 @@ namespace Barotrauma
}
private float ballastFloraTimer;
public bool ImmuneToBallastFlora { get; set; }
public void AttemptBallastFloraInfection(string identifier, float deltaTime, float probability)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (ImmuneToBallastFlora) { return; }
if (ballastFloraTimer < 1f)
{
@@ -1264,7 +1266,7 @@ namespace Barotrauma
{
List<(ItemContainer container, int freeSlots)> containers = new List<(ItemContainer container, int freeSlots)>();
var connectedSubs = GetConnectedSubs();
foreach (Item item in Item.ItemList)
foreach (Item item in Item.ItemList.ToList())
{
if (!connectedSubs.Contains(item.Submarine)) { continue; }
if (!item.HasTag("cargocontainer")) { continue; }
@@ -671,17 +671,20 @@ namespace Barotrauma
Body.LinearVelocity -= velChange;
float damageAmount = contactDot * Body.Mass / limb.character.Mass;
limb.character.LastDamageSource = submarine;
limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb,
AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f);
if (limb.character.IsDead)
if (contactDot > 0.1f)
{
foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints)
float damageAmount = contactDot * Body.Mass / limb.character.Mass;
limb.character.LastDamageSource = submarine;
limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb,
AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f);
if (limb.character.IsDead)
{
if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) continue;
limb.character.AnimController.SeverLimbJoint(limbJoint);
foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints)
{
if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) continue;
limb.character.AnimController.SeverLimbJoint(limbJoint);
}
}
}
}
@@ -404,6 +404,8 @@ namespace Barotrauma
{
var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine)
.Concat(vanilla.GetFilesOfType(ContentType.Wreck))
.Concat(vanilla.GetFilesOfType(ContentType.BeaconStation))
.Concat(vanilla.GetFilesOfType(ContentType.EnemySubmarine))
.Concat(vanilla.GetFilesOfType(ContentType.Outpost))
.Concat(vanilla.GetFilesOfType(ContentType.OutpostModule));
string pathToCompare = FilePath.Replace(@"\", @"/").ToLowerInvariant();
@@ -779,6 +779,7 @@ namespace Barotrauma
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
{
List<WayPoint> subWayPoints = WayPointList.FindAll(wp => wp.Submarine == submarine);
subWayPoints.Shuffle();
List<WayPoint> unassignedWayPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human);
@@ -789,7 +790,7 @@ namespace Barotrauma
//try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job
for (int n = 0; n < unassignedWayPoints.Count; n++)
{
if (crew[i].Job.Prefab != unassignedWayPoints[n].AssignedJob) continue;
if (crew[i].Job.Prefab != unassignedWayPoints[n].AssignedJob) { continue; }
assignedWayPoints[i] = unassignedWayPoints[n];
unassignedWayPoints.RemoveAt(n);
@@ -800,17 +801,17 @@ namespace Barotrauma
//go through the crewmembers that don't have a spawnpoint yet (if any)
for (int i = 0; i < crew.Count; i++)
{
if (assignedWayPoints[i] != null) continue;
if (assignedWayPoints[i] != null) { continue; }
//try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else
foreach (WayPoint wp in subWayPoints)
{
if (wp.spawnType != SpawnType.Human || wp.AssignedJob != crew[i].Job.Prefab) continue;
if (wp.spawnType != SpawnType.Human || wp.AssignedJob != crew[i].Job.Prefab) { continue; }
assignedWayPoints[i] = wp;
break;
}
if (assignedWayPoints[i] != null) continue;
if (assignedWayPoints[i] != null) { continue; }
//try to assign a spawnpoint that isn't meant for any specific job
var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.AssignedJob == null);
@@ -819,7 +820,7 @@ namespace Barotrauma
assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.Server)];
}
if (assignedWayPoints[i] != null) continue;
if (assignedWayPoints[i] != null) { continue; }
//everything else failed -> just give a random spawnpoint inside the sub
assignedWayPoints[i] = GetRandom(SpawnType.Human, null, submarine, useSyncedRand: true);
@@ -264,9 +264,17 @@ namespace Barotrauma.Networking
{
radio = null;
if (sender?.Inventory == null || sender.Removed) { return false; }
radio = sender.Inventory.AllItems.FirstOrDefault(i => i.GetComponent<WifiComponent>() != null)?.GetComponent<WifiComponent>();
if (radio?.Item == null) { return false; }
return sender.HasEquippedItem(radio.Item) && radio.CanTransmit();
foreach (Item item in sender.Inventory.AllItems)
{
var wifiComponent = item.GetComponent<WifiComponent>();
if (wifiComponent == null || !wifiComponent.CanTransmit() || !sender.HasEquippedItem(item)) { continue; }
if (radio == null || wifiComponent.Range > radio.Range)
{
radio = wifiComponent;
}
}
return radio?.Item != null;
}
}
}
@@ -377,7 +377,7 @@ namespace Barotrauma
return removeQueue.Contains(entity);
}
public void Update()
public void Update(bool createNetworkEvents = true)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
while (spawnQueue.Count > 0)
@@ -387,10 +387,13 @@ namespace Barotrauma
var spawnedEntity = entitySpawnInfo.Spawn();
if (spawnedEntity != null)
{
CreateNetworkEventProjSpecific(spawnedEntity, false);
if (spawnedEntity is Item)
if (createNetworkEvents)
{
CreateNetworkEventProjSpecific(spawnedEntity, false);
}
if (spawnedEntity is Item item)
{
((Item)spawnedEntity).Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition;
item.Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition;
}
entitySpawnInfo.OnSpawned(spawnedEntity);
}
@@ -403,7 +406,10 @@ namespace Barotrauma
{
item.SendPendingNetworkUpdates();
}
CreateNetworkEventProjSpecific(removedEntity, true);
if (createNetworkEvents)
{
CreateNetworkEventProjSpecific(removedEntity, true);
}
removedEntity.Remove();
}
}
@@ -318,7 +318,22 @@ namespace Barotrauma.Networking
{
RespawnCharactersProjSpecific(shuttlePos);
}
public static Affliction GetRespawnPenaltyAffliction()
{
var respawnPenaltyAffliction = AfflictionPrefab.List.FirstOrDefault(a => a.AfflictionType.Equals("respawnpenalty", StringComparison.OrdinalIgnoreCase));
return respawnPenaltyAffliction?.Instantiate(10.0f);
}
public static void GiveRespawnPenaltyAffliction(Character character)
{
var respawnPenaltyAffliction = GetRespawnPenaltyAffliction();
if (respawnPenaltyAffliction != null)
{
character.CharacterHealth.ApplyAffliction(targetLimb: null, respawnPenaltyAffliction);
}
}
public Vector2 FindSpawnPos()
{
if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; }
@@ -76,7 +76,8 @@ namespace Barotrauma
//These need to exist at compile time, so it is a little awkward
//I would love to see a better way to do this
AllowLinkingWifiToChat,
IsSwappableItem
IsSwappableItem,
AllowRotating
}
public bool IsEditable(ISerializableEntity entity)
@@ -86,7 +87,13 @@ namespace Barotrauma
case ConditionType.AllowLinkingWifiToChat:
return GameMain.NetworkMember?.ServerSettings?.AllowLinkingWifiToChat ?? true;
case ConditionType.IsSwappableItem:
return entity is Item item && item.Prefab.SwappableItem != null;
{
return entity is Item item && item.Prefab.SwappableItem != null;
}
case ConditionType.AllowRotating:
{
return entity is Item item && item.Prefab.AllowRotatingInEditor && Screen.Selected == GameMain.SubEditorScreen;
}
}
return false;
}
@@ -685,7 +685,7 @@ namespace Barotrauma
if (HasTargetType(TargetType.NearbyItems))
{
//optimization for powered components that can be easily fetched from Powered.PoweredList
if (targetIdentifiers.Count == 1 &&
if (targetIdentifiers?.Count == 1 &&
(targetIdentifiers.Contains("powered") || targetIdentifiers.Contains("junctionbox") || targetIdentifiers.Contains("relaycomponent")))
{
foreach (Powered powered in Powered.PoweredList)
@@ -971,6 +971,13 @@ namespace Barotrauma
{
position = targetLimb.WorldPosition;
}
else if (HasTargetType(TargetType.Contained))
{
if (targets.FirstOrDefault(t => t is Item) is Item targetItem)
{
position = targetItem.WorldPosition;
}
}
}
}
position += Offset;