@@ -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;
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+4
-2
@@ -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;
|
||||
|
||||
+9
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -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)
|
||||
{
|
||||
|
||||
+1
-1
@@ -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; }
|
||||
|
||||
+25
-6
@@ -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;
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+15
-4
@@ -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;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,121 @@
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.14.9.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Changes:
|
||||
- If multiple turrets have been linked to the same loader, the turrets can't be swapped individually (both need to be swapped at the same time to prevent turrets from getting linked to an incorrect type of loader).
|
||||
- Emergency lights don't cast shadows by default (they have such a short range that disabling the shadows usually doesn't make a difference visually, but improves the performance of the subs).
|
||||
- Allow closing the submarine previews with esc.
|
||||
- Made ballast pumps in Kastrull's drone indestructible (because they're impossible to access without breaking the walls/floors).
|
||||
- Made exploding coilgun ammo more expensive and increased the capacity of the ammo boxes a bit.
|
||||
- Made pirate subs immune to ballast flora.
|
||||
- Security's stun guns spawn fully loaded.
|
||||
- Reduced escort missions' base commonness. Doesn't have an effect in the campaign because the commonness is defined in the mission events, but fixes escort missions being much more common than other mission types in mission mode.
|
||||
- Destroying a reactor with explosives depletes the fuel rods.
|
||||
- Hide the equipment slots and character portait in wiring mode.
|
||||
- Added platforms behind Remora Drone's hatches.
|
||||
|
||||
Fixes:
|
||||
- Fixed enemies sometimes not spawning at all during a mission. Happened when the game selected the "stowaway" event to occur during the mission.
|
||||
- Fixed pirate and escort missions not being available from late-game outposts.
|
||||
- Fixed occasional "unauthorized multithreaded access to RandSync.Server" errors when starting a pirate mission in multiplayer.
|
||||
- Fixed crashing with a "E_INVALIDARG" SharpDXException if an ice spire happens to generate on a very short level wall edge.
|
||||
- Fixed crashing when trying to spawn inventory items for a character who left before respawning during the previous round.
|
||||
- Fixed batteries always recharging at "full speed" when not full, regardless of how much power is being drawn from the battery. E.g. a battery that's only connected to a lamp drawing 5 kW of power would always recharge at the full 500 kWmin.
|
||||
- Fixed traitor items sometimes spawning in non-interactable or hidden containers.
|
||||
- Fixed turret rotation limit widgets working unreliably in the sub editor.
|
||||
- Fixed barrels being misaligned on scaled turrets in the sub preview.
|
||||
- Stores don't offer/request sealed supply crates as daily specials.
|
||||
- Fixed campaign settings (radiation enabled, max missions) resetting when reopening the campaign settings menu.
|
||||
- Fixed hitscan projectiles not hitting structures outside hulls when the turret itself is inside a hull.
|
||||
- Fixed inability to swap items between the character's inventory and a container in some situations in multiplayer (when the container was right at the edge of the character's interaction range).
|
||||
- Fixed bots being unable to repair the pump in Typhon's bottom airlock.
|
||||
- Fixed crashing when repairing or fabricating something as a husk (or some other character with no CharacterInfo).
|
||||
- Fixed crashing when reviving someone using CPR as a character with no CharacterInfo (e.g. husk).
|
||||
- Fixed upgrades using the original value from a previous item when swapping to a new one (e.g. swapping a railgun to a coilgun sets the power consumption to the railgun's power consumption). More specifically, this happened when the campaign was saved and reloaded after purchasing the upgrade, and the item was swapped after that.
|
||||
- Fixed wrecks sometimes being positioned right side of the exit position.
|
||||
- Fixed characters not getting knocked down at the last stage of husk infection if they have the Vigor buff.
|
||||
- Fixed discharge coil triggering when trying to rewire it.
|
||||
- Fixed some ActionTypes having the same value (e.g. OnFire == OnDamaged). Caused wrong type of status effects triggering in some cases, for example OnDamage effects when an indestructible thalamus organ is on fire.
|
||||
- Fixed items that are set to be hidden in-game being visible in the sub preview.
|
||||
- Fixed bots being unable to weld leaks that are too high above them, even if they're actually in reach. Happened because the distance to the leak was calculated relative to the character's position, even though the range of the welding tool is relative to the character's shoulder.
|
||||
- Fixed cargo mission rewards being displayed incorrectly when there's multiple cargo missions selected and not enough capacity for all the cargo.
|
||||
- Fixed caret getting misplaced when clicking on a textbox with padding.
|
||||
- Fixed headset only being able to send signals to each wifi component once.
|
||||
- Fixed bots sometimes ignoring broken devices. Happened when another bot had selected the device as their repair target, even if the bot was currently repairing something else.
|
||||
- Fixed nav terminal labels sometimes being draw under linked status monitor HUDs.
|
||||
- Fixed bots sometimes not fixing airlock doors/hatches. More specifically, doors whose center point was outside a hull.
|
||||
- Fixed medic bots grabbing the target and never letting go when there's no suitable treatments available anywhere in the sub, or when they're in an outpost and not carrying any suitable treatments.
|
||||
- Fixed bots suffocating when they run out of oxygen tanks if ordered to wait wearing a suit.
|
||||
- Fixed ability to select other items when operating periscopes. Didn't cause problems in vanilla subs, but in custom subs where the turret was placed close to the periscope and other interactable items, it was possible to accidentally select something else when trying to fire.
|
||||
- Made nav terminal's "velocity_in" input change the target velocity, not just the steering input, making it possible to adjust the velocity with signals when using autopilot.
|
||||
- Fixed text fields in a component's editing menu not refreshing until you've interacted with another component.
|
||||
- Fixed ruins sometimes overlapping with level walls (when they happen to spawn next to a very large cell).
|
||||
- Fixed ability to "partially open" the health interface in the sub editor, causing the inventory layout to get messed up when clicking on the character portrait even though the health interface isn't drawn.
|
||||
- Fixed ability to keep dragging an item from a stunned/ragdolled character's inventory after they get back up.
|
||||
- Fixed nuclear weapons not damaging ballast flora.
|
||||
- Fixed normal uniforms deflecting projectiles even though they shouldn't.
|
||||
- Fixed clients who've opted to spawn with reaper's tax getting a new character if the round ends before the client respawns.
|
||||
- Fixed occasional crash with the error message "collection was modified; enumeration operation may not execute" when loading mods on startup.
|
||||
- Fixed ability to keep dragging the previously equipped wire in the sub editor after you equip another wire with the wire hotkeys.
|
||||
- Fixed characters' sprite depth changing during the "autowalk" towards a bed or chair.
|
||||
- Require the sub to move a bit faster to crush characters between the sub and a wall. Previously essentially any non-zero velocity was enough to crush a character, even if the sub didn't appear to move.
|
||||
- Fixed nests sometimes getting placed outside caves. Happened when the game tried to position the nest above an opening at the bottom of the cave.
|
||||
- Fixed inability to use the health interface on the escorted characters.
|
||||
- Fixed 1st client (usually the host) always spawning at the same spawnpoint in multiplayer even if there's multiple suitable ones available.
|
||||
- Fixed stacked partially used items (e.g. stacked explosives that have been damaged) dropping from character inventories at the start of a round.
|
||||
- Fixed occasional freezing when replacing lost shuttles after purchasing submarine upgrades.
|
||||
- Adjusted Endworm's colliders so that you can't hit the tail between the armor segments before first breaking the armor (making it less easy to cut the worm in half).
|
||||
- Fixed hull properties not carrying over when copying hulls in the sub editor.
|
||||
- Fixed occasional "collection was modified" exception in CargoMission.DetermineCargo. Happened if the client received an updated campaign save while trying to load the sub between rounds.
|
||||
- Fixed a broken waypoint in Berilia's cargo bay.
|
||||
- Fixed seeds sometimes vanishing when trying to plant them in MP.
|
||||
- Fixed planter boxes displaying the "uproot" message when empty.
|
||||
- Fixed depth charges going through doors and hatches.
|
||||
- Fixed ability to dock docking hatches to ports and vice versa.
|
||||
- Fixed camera being able to focus on a turret when a periscope's "position_out" connection is wired to a turret's "power_in" connection.
|
||||
- Fixed water detectors not detecting very small (water depth < 1px) amounts of water in hulls.
|
||||
- Fixed duffel bags not spawning at the end of a round if the character hasn't despawned/respawned yet (i.e. if the character's corpse is still present in the sub).
|
||||
- Fixed wrecked Dugong's distress signal being impossible to receive due to "allow cross-team communication" being set to false on the wifi component sending the signal.
|
||||
- Fixed fabricator being unable to stack empty items in the output slots, preventing empty tanks from being fabricated when the output slots are occupied even if additional tanks could be stacked on them.
|
||||
- Fixed messed up "Shuttle Shell A Glass A" sprite.
|
||||
- Fixed delay components not working if the delay is set to 0.
|
||||
- Fixed loaders getting slightly misaligned when swapping an empty hardpoint with some weapon.
|
||||
- Fixed pumps placed in the sub editor being off by default.
|
||||
- Fixed weapon skill increases not being capped according to the max vitality of the target. Resulted in enormous weapon skill gains when doing massive amounts of damage (more than the target's max vitality), e.g. by shooting a hammerhead matriarch in the egg sack with a nuke.
|
||||
- Fixed ability to delete vanilla beacon stations and pirate subs in the sub editor.
|
||||
- Fixed pirate subs sometimes spawning in side paths that are too narrow for the sub to pass through.
|
||||
- Fixed an exploit that allowed creating game-crashing infinite signal loops using wifi components.
|
||||
- Fixed a typo in the abyss diving suit description (100,000 m -> 10,000 m).
|
||||
- Fixed several vanilla sub docking hatches requiring a welding tool instead of a wrench to repair them.
|
||||
- Fixed "Operate Weapons" order being available when interacting with a turret directly, leading to a crash when trying to do so.
|
||||
- Fixed missions disappearing from abandoned outposts after finishing the campaign.
|
||||
- Fixed canister shells refilling automatically between rounds.
|
||||
- Fixed turrets working with incorrect loader types (e.g. coilgun being able to fire laser bolts when linked to a pulse laser loader).
|
||||
- Fixed "exterior pressure exceeds diving suit capabilities" hint popping up when carrying a diving suit.
|
||||
- Fixed biome noise loops still playing after the round ends.
|
||||
- Fixed detonator rapidly draining condition from flash powder and incendium instead of exploding them immediately (potentially resulting in partially used items).
|
||||
- Fixed exploit in plastiseal crafting recipe (plastiseal deconstructing to 1 bandage even though 1 bandage can be used to craft 2 plastiseal).
|
||||
- Fixed RepairTools applying their effects multiple times to items whose bodies consist of multiple fixtures. Caused minerals to get deattached 3 times faster than they should.
|
||||
- Fixed AFK kicks when staying in any of the campaign UIs (store, etc) or other input-blocking menus for too long.
|
||||
- Fixed monsters sometimes targeting owners of targeted items although the owner is ignored.
|
||||
- Fixed some items held in the left hand rendering in front of the character's thigh.
|
||||
- Fixed characters being focusable through UI elements (e.g. item interfaces), causing accidental interactions when the NPC interaction key is bind to LMB.
|
||||
|
||||
Modding:
|
||||
- Added support for "additive" event sets which get added on top of another normal event set, allowing mods to spawn additional types of monsters without having to touch the vanilla event sets. See the "transitevents" event set in OutpostEvents.xml for an usage example.
|
||||
- Fixed subs appearing to disappear after being published in the Workshop, because the content package the sub gets moved to wasn't automatically selected.
|
||||
- Fixed MaxTargetsToHit not working on hitscan projectiles.
|
||||
- Fixed offsets not being taken into account when positioning contained items, causing status effects to happen at the position of the container instead of the position of the contained item. Didn't have a noticeable effect in the vanilla game because most contained items are positioned close to the container's origin.
|
||||
- Fixed spawnpoint's job restrictions being ignored when spawning pirates and escortees.
|
||||
- It's now possible to use multiple equipped WifiComponents at the same time: instead of finding the first equipped item and seeing if it can receive/transmit, the game now goes through the items until it finds one that can.
|
||||
- Fixed crashing when trying to pick an item that can't be put in any type of inventory slot from a container. Doesn't happen in the vanilla game because there are no such items in any container.
|
||||
- Fixed crashing during wreck generation if there's any thalamus organs outside hulls.
|
||||
- Fixed crashing if a wall whose max health is set to 0 in the sub editor takes damage.
|
||||
- Fixed crashing if a reactor's maximum output is set to 0 and it's set to be on by default.
|
||||
- Fixed a crashing due to a null reference exception if a status effect uses "target" instead of "targets" or "targettypes" in the definition.
|
||||
- Fixed shuttles docked to a wreck undocking during level generation.
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.14.6.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user