v0.14.6.0
This commit is contained in:
@@ -538,16 +538,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (hull.Submarine != subs[i]) continue;
|
||||
if (hull.WorldRect.Y < hullRects[i].Y - hullRects[i].Height) continue;
|
||||
if (hull.WorldRect.Y - hull.WorldRect.Height > hullRects[i].Y) continue;
|
||||
if (hull.Submarine != subs[i]) { continue; }
|
||||
if (hull.WorldRect.Y - 5 < hullRects[i].Y - hullRects[i].Height) { continue; }
|
||||
if (hull.WorldRect.Y - hull.WorldRect.Height + 5 > hullRects[i].Y) { continue; }
|
||||
|
||||
if (i == 0) //left hull
|
||||
{
|
||||
if (hull.WorldPosition.X > hullRects[0].Center.X) { continue; }
|
||||
leftSubRightSide = Math.Max(hull.WorldRect.Right, leftSubRightSide);
|
||||
}
|
||||
else //upper hull
|
||||
{
|
||||
if (hull.WorldPosition.X < hullRects[1].Center.X) { continue; }
|
||||
rightSubLeftSide = Math.Min(hull.WorldRect.X, rightSubLeftSide);
|
||||
}
|
||||
}
|
||||
@@ -591,8 +593,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
int expand = 5;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hullRects[i].X -= expand;
|
||||
hullRects[i].Width += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
@@ -636,16 +641,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (hull.Submarine != subs[i]) continue;
|
||||
if (hull.WorldRect.Right < hullRects[i].X) continue;
|
||||
if (hull.WorldRect.X > hullRects[i].Right) continue;
|
||||
if (hull.Submarine != subs[i]) { continue; }
|
||||
if (hull.WorldRect.Right - 5 < hullRects[i].X) { continue; }
|
||||
if (hull.WorldRect.X + 5 > hullRects[i].Right) { continue; }
|
||||
|
||||
if (i == 0) //lower hull
|
||||
{
|
||||
if (hull.WorldPosition.Y > hullRects[i].Y - hullRects[i].Height / 2) { continue; }
|
||||
lowerSubTop = Math.Max(hull.WorldRect.Y, lowerSubTop);
|
||||
}
|
||||
else //upper hull
|
||||
{
|
||||
if (hull.WorldPosition.Y < hullRects[i].Y - hullRects[i].Height / 2) { continue; }
|
||||
upperSubBottom = Math.Min(hull.WorldRect.Y - hull.WorldRect.Height, upperSubBottom);
|
||||
}
|
||||
}
|
||||
@@ -705,8 +712,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
|
||||
int expand = 5;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hullRects[i].Y += expand;
|
||||
hullRects[i].Height += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
@@ -797,22 +807,22 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Gap doorGap = i == 0 ? Door?.LinkedGap : DockingTarget?.Door?.LinkedGap;
|
||||
if (doorGap == null) continue;
|
||||
if (doorGap == null) { continue; }
|
||||
doorGap.DisableHullRechecks = true;
|
||||
if (doorGap.linkedTo.Count >= 2) continue;
|
||||
if (doorGap.linkedTo.Count >= 2) { continue; }
|
||||
|
||||
if (IsHorizontal)
|
||||
{
|
||||
if (item.WorldPosition.X < DockingTarget.item.WorldPosition.X)
|
||||
{
|
||||
if (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]);
|
||||
if (!doorGap.linkedTo.Contains(hulls[0])) { doorGap.linkedTo.Add(hulls[0]); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
|
||||
if (!doorGap.linkedTo.Contains(hulls[1])) { doorGap.linkedTo.Add(hulls[1]); }
|
||||
}
|
||||
//make sure the left hull is linked to the gap first (gap logic assumes that the first hull is the one to the left)
|
||||
if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].Rect.X > doorGap.linkedTo[1].Rect.X)
|
||||
if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].WorldRect.X > doorGap.linkedTo[1].WorldRect.X)
|
||||
{
|
||||
var temp = doorGap.linkedTo[0];
|
||||
doorGap.linkedTo[0] = doorGap.linkedTo[1];
|
||||
@@ -821,16 +831,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.WorldPosition.Y < DockingTarget.item.WorldPosition.Y)
|
||||
if (item.WorldPosition.Y > DockingTarget.item.WorldPosition.Y)
|
||||
{
|
||||
if (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]);
|
||||
if (!doorGap.linkedTo.Contains(hulls[0])) { doorGap.linkedTo.Add(hulls[0]); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
|
||||
if (!doorGap.linkedTo.Contains(hulls[1])) { doorGap.linkedTo.Add(hulls[1]); }
|
||||
}
|
||||
//make sure the upper hull is linked to the gap first (gap logic assumes that the first hull is above the second one)
|
||||
if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].Rect.Y < doorGap.linkedTo[1].Rect.Y)
|
||||
if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].WorldRect.Y < doorGap.linkedTo[1].WorldRect.Y)
|
||||
{
|
||||
var temp = doorGap.linkedTo[0];
|
||||
doorGap.linkedTo[0] = doorGap.linkedTo[1];
|
||||
@@ -887,10 +897,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
wire.Drop(null);
|
||||
}
|
||||
wire?.Drop(null);
|
||||
|
||||
if (joint != null)
|
||||
{
|
||||
@@ -991,7 +998,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (DockingTarget.Door != null && doorBody != null)
|
||||
{
|
||||
doorBody.Enabled = DockingTarget.Door.Body.Enabled;
|
||||
doorBody.Enabled = DockingTarget.Door.Body.Enabled && !(DockingTarget.Door.Body.FarseerBody.FixtureList.FirstOrDefault()?.IsSensor ?? false);
|
||||
}
|
||||
dockingState = MathHelper.Lerp(dockingState, 1.0f, deltaTime * 10.0f);
|
||||
}
|
||||
|
||||
@@ -259,6 +259,10 @@ namespace Barotrauma.Items.Components
|
||||
Body.SetTransformIgnoreContacts(
|
||||
ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
|
||||
0.0f);
|
||||
if (isBroken)
|
||||
{
|
||||
DisableBody();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
@@ -659,7 +663,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
return Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character));
|
||||
return base.HasAccess(character) && Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +319,25 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
//if the item has multiple Pickable components (e.g. Holdable and Wearable, check that we don't equip it in hands when the item is worn or vice versa)
|
||||
if (item.GetComponents<Pickable>().Count() > 0)
|
||||
{
|
||||
bool inSuitableSlot = false;
|
||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||
{
|
||||
if (character.Inventory.GetItemsAt(i).Contains(item))
|
||||
{
|
||||
if (character.Inventory.SlotTypes[i] != InvSlotType.Any &&
|
||||
allowedSlots.Any(a => a.HasFlag(character.Inventory.SlotTypes[i])))
|
||||
{
|
||||
inSuitableSlot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!inSuitableSlot) { return; }
|
||||
}
|
||||
|
||||
picker = character;
|
||||
|
||||
if (item.Removed)
|
||||
@@ -327,6 +346,13 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
var wearable = item.GetComponent<Wearable>();
|
||||
if (wearable != null)
|
||||
{
|
||||
//cannot hold and wear an item at the same time
|
||||
wearable.Unequip(character);
|
||||
}
|
||||
|
||||
if (character != null) { item.Submarine = character.Submarine; }
|
||||
if (item.body == null)
|
||||
{
|
||||
@@ -414,7 +440,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.CurrentHull == null)
|
||||
{
|
||||
return attachTargetCell != null && Structure.GetAttachTarget(item.WorldPosition) != null;
|
||||
return attachTargetCell != null || Structure.GetAttachTarget(item.WorldPosition) != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -75,6 +75,14 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
if (trigger != null && amount.LengthSquared() > 0.00001f)
|
||||
{
|
||||
trigger.SetTransform(item.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (holdable != null && !holdable.Attached)
|
||||
|
||||
@@ -132,7 +132,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
return characterUsable || character == null;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
base.Drop(dropper);
|
||||
|
||||
@@ -90,6 +90,24 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public virtual bool OnPicked(Character picker)
|
||||
{
|
||||
//if the item has multiple Pickable components (e.g. Holdable and Wearable, check that we don't equip it in hands when the item is worn or vice versa)
|
||||
if (item.GetComponents<Pickable>().Count() > 0)
|
||||
{
|
||||
bool alreadyEquipped = false;
|
||||
for (int i = 0; i < picker.Inventory.Capacity; i++)
|
||||
{
|
||||
if (picker.Inventory.GetItemsAt(i).Contains(item))
|
||||
{
|
||||
if (picker.Inventory.SlotTypes[i] != InvSlotType.Any &&
|
||||
!allowedSlots.Any(a => a.HasFlag(picker.Inventory.SlotTypes[i])))
|
||||
{
|
||||
alreadyEquipped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alreadyEquipped) { return false; }
|
||||
}
|
||||
if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots))
|
||||
{
|
||||
if (!picker.HeldItems.Contains(item) && item.body != null) { item.body.Enabled = false; }
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace Barotrauma.Items.Components
|
||||
item.IsShootable = true;
|
||||
// TODO: should define this in xml if we have ranged weapons that don't require aim to use
|
||||
item.RequireAimToUse = true;
|
||||
characterUsable = true;
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -151,6 +152,11 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
return characterUsable || character == null;
|
||||
}
|
||||
|
||||
public Projectile FindProjectile(bool triggerOnUseOnContainers = false)
|
||||
{
|
||||
var containedItems = item.OwnInventory?.AllItemsMod;
|
||||
|
||||
@@ -548,6 +548,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is LevelObject levelObject && levelObject.Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
levelObject.AddDamage(-LevelWallFixAmount, deltaTime, item);
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter.Removed) { return false; }
|
||||
|
||||
@@ -660,6 +660,7 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
public virtual bool HasAccess(Character character)
|
||||
{
|
||||
if (character.IsBot && item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (requiredItems.None()) { return true; }
|
||||
if (character.Inventory != null)
|
||||
@@ -678,7 +679,6 @@ namespace Barotrauma.Items.Components
|
||||
public virtual bool HasRequiredItems(Character character, bool addMessage, string msg = null)
|
||||
{
|
||||
if (requiredItems.None()) { return true; }
|
||||
if (!character.IsPlayer && character.Params.AI != null && character.Params.AI.Infiltrate) { return true; }
|
||||
if (character.Inventory == null) { return false; }
|
||||
bool hasRequiredItems = false;
|
||||
bool canContinue = true;
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool alwaysContainedItemsSpawned;
|
||||
|
||||
public ItemInventory Inventory;
|
||||
|
||||
private readonly List<ActiveContainedItem> activeContainedItems = new List<ActiveContainedItem>();
|
||||
@@ -204,8 +206,15 @@ namespace Barotrauma.Items.Components
|
||||
return ContainableItems.Find(c => c.MatchesItem(itemPrefab)) != null;
|
||||
}
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
|
||||
{
|
||||
SpawnAlwaysContainedItems();
|
||||
}
|
||||
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
@@ -235,8 +244,8 @@ namespace Barotrauma.Items.Components
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(item.WorldPosition, targets);
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(item.WorldPosition, targets));
|
||||
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
|
||||
}
|
||||
}
|
||||
@@ -403,10 +412,28 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (SpawnWithId.Length > 0)
|
||||
{
|
||||
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
|
||||
if (prefab != null && Inventory != null && Inventory.CanBePut(prefab))
|
||||
string[] splitIds = SpawnWithId.Split(',');
|
||||
foreach (string id in splitIds)
|
||||
{
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory, spawnIfInventoryFull: false);
|
||||
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == id);
|
||||
if (prefab != null && Inventory != null && Inventory.CanBePut(prefab))
|
||||
{
|
||||
bool isEditor = false;
|
||||
#if CLIENT
|
||||
isEditor = Screen.Selected == GameMain.SubEditorScreen;
|
||||
#endif
|
||||
if (!isEditor && (Entity.Spawner == null || Entity.Spawner.Removed) && GameMain.NetworkMember == null)
|
||||
{
|
||||
var spawnedItem = new Item(prefab, Vector2.Zero, null);
|
||||
Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots, createNetworkEvent: false);
|
||||
alwaysContainedItemsSpawned = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsActive = true;
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory, spawnIfInventoryFull: false, onSpawned: (Item item) => { alwaysContainedItemsSpawned = true; });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,14 +172,23 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float scaledDamageRange = propellerDamage.DamageRange * item.Scale;
|
||||
|
||||
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos * item.Scale;
|
||||
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos * item.Scale;
|
||||
float broadRange = Math.Max(scaledDamageRange * 2, 500);
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (!character.Enabled || character.Removed) { continue; }
|
||||
float distSqr = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
|
||||
if (distSqr > scaledDamageRange * scaledDamageRange) { continue; }
|
||||
character.LastDamageSource = item;
|
||||
propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
|
||||
if (Math.Abs(character.WorldPosition.X - propellerWorldPos.X) > broadRange) { continue; }
|
||||
if (Math.Abs(character.WorldPosition.Y - propellerWorldPos.Y) > broadRange) { continue; }
|
||||
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered || !limb.body.Enabled) { continue; }
|
||||
float distSqr = Vector2.DistanceSquared(limb.WorldPosition, propellerWorldPos);
|
||||
if (distSqr > scaledDamageRange * scaledDamageRange) { continue; }
|
||||
character.LastDamageSource = item;
|
||||
propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,10 +290,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < ingredient.Amount; i++)
|
||||
{
|
||||
var availableItem = availableIngredients.FirstOrDefault(it => it != null && ingredient.ItemPrefabs.Contains(it.Prefab) && it.ConditionPercentage >= ingredient.MinCondition * 100.0f);
|
||||
var availableItem = availableIngredients.FirstOrDefault(it =>
|
||||
it != null && ingredient.ItemPrefabs.Contains(it.Prefab) &&
|
||||
it.ConditionPercentage >= ingredient.MinCondition * 100.0f &&
|
||||
it.ConditionPercentage <= ingredient.MaxCondition * 100.0f);
|
||||
if (availableItem == null) { continue; }
|
||||
|
||||
//Item4 = use condition bool
|
||||
|
||||
if (ingredient.UseCondition && availableItem.ConditionPercentage - ingredient.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
||||
{
|
||||
availableItem.Condition -= availableItem.Prefab.Health * ingredient.MinCondition;
|
||||
@@ -493,7 +495,8 @@ namespace Barotrauma.Items.Components
|
||||
return
|
||||
item != null &&
|
||||
requiredItem.ItemPrefabs.Contains(item.prefab) &&
|
||||
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
|
||||
item.Condition / item.Prefab.Health >= requiredItem.MinCondition &&
|
||||
item.Condition / item.Prefab.Health <= requiredItem.MaxCondition;
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Character LastAIUser { get; private set; }
|
||||
|
||||
[Serialize(defaultValue: false, isSaveable: true)]
|
||||
public bool LastUserWasPlayer { get; private set; }
|
||||
|
||||
private Character lastUser;
|
||||
public Character LastUser
|
||||
{
|
||||
@@ -178,8 +181,6 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0.0f, true)]
|
||||
public float AvailableFuel { get; set; }
|
||||
|
||||
public bool LastUserWasPlayer { get; private set; }
|
||||
|
||||
public Reactor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -241,7 +242,7 @@ namespace Barotrauma.Items.Components
|
||||
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
|
||||
|
||||
optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess);
|
||||
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
|
||||
|
||||
@@ -251,11 +252,13 @@ namespace Barotrauma.Items.Components
|
||||
allowedFissionRate.X = Math.Min(allowedFissionRate.X, allowedFissionRate.Y - 10);
|
||||
|
||||
float heatAmount = GetGeneratedHeat(fissionRate);
|
||||
|
||||
float temperatureDiff = (heatAmount - turbineOutput) - Temperature;
|
||||
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
|
||||
//if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime;
|
||||
|
||||
|
||||
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime);
|
||||
|
||||
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
|
||||
|
||||
float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
|
||||
@@ -311,6 +314,38 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadQueue.Any() && PowerOn)
|
||||
{
|
||||
//loadQueue is empty, round must've just started
|
||||
//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;
|
||||
tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
|
||||
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
|
||||
DebugConsole.Log($"Degree of success: {degreeOfSuccess}");
|
||||
DebugConsole.Log($"Current load: {currentLoad}");
|
||||
DebugConsole.Log($"Max power output: {MaxPowerOutput}");
|
||||
DebugConsole.Log($"Available fuel: {AvailableFuel}");
|
||||
|
||||
float desiredTurbineOutput = MathHelper.Clamp(correctTurbineOutput, 0.0f, 100.0f);
|
||||
DebugConsole.Log($"Turbine output reset: {targetTurbineOutput}, {turbineOutput} -> {desiredTurbineOutput}");
|
||||
targetTurbineOutput = desiredTurbineOutput;
|
||||
turbineOutput = desiredTurbineOutput;
|
||||
|
||||
float desiredTemperature = (optimalTemperature.X + optimalTemperature.Y) / 2.0f;
|
||||
DebugConsole.Log($"Temperature reset: {temperature} -> {desiredTemperature}");
|
||||
temperature = desiredTemperature;
|
||||
|
||||
float desiredFissionRate = GetFissionRateForTargetTemperatureAndTurbineOutput(desiredTemperature, desiredTurbineOutput);
|
||||
DebugConsole.Log($"Fission rate reset: {targetFissionRate}, {fissionRate} -> {desiredFissionRate}");
|
||||
targetFissionRate = desiredFissionRate;
|
||||
fissionRate = desiredFissionRate;
|
||||
}
|
||||
|
||||
loadQueue.Enqueue(currentLoad);
|
||||
while (loadQueue.Count() > 60.0f)
|
||||
{
|
||||
@@ -390,6 +425,12 @@ namespace Barotrauma.Items.Components
|
||||
return fissionRate * (prevAvailableFuel / 100.0f) * 2.0f;
|
||||
}
|
||||
|
||||
private float GetFissionRateForTargetTemperatureAndTurbineOutput(float temperature, float turbineOutput)
|
||||
{
|
||||
if (MathUtils.NearlyEqual(AvailableFuel, 0f)) { return 0f; }
|
||||
return (temperature + turbineOutput) / (AvailableFuel / 100f) / 2f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do we need more fuel to generate enough power to match the current load.
|
||||
/// </summary>
|
||||
@@ -564,6 +605,7 @@ namespace Barotrauma.Items.Components
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
character.AIController.SteeringManager.Reset();
|
||||
bool shutDown = objective.Option.Equals("shutdown", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
IsActive = true;
|
||||
@@ -598,6 +640,7 @@ namespace Barotrauma.Items.Components
|
||||
void ReportFuelRodCount()
|
||||
{
|
||||
if (!character.IsOnPlayerTeam) { return; }
|
||||
if (character.Submarine != Submarine.MainSub) { return; }
|
||||
int remainingFuelRods = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("reactorfuel") && i.Condition > 1);
|
||||
if (remainingFuelRods == 0)
|
||||
{
|
||||
@@ -614,6 +657,18 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character))
|
||||
{
|
||||
if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogReactorIsBroken"), identifier: "reactorisbroken", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
if (TooMuchFuel())
|
||||
{
|
||||
DropFuel(minCondition: 0.1f, maxCondition: 100);
|
||||
@@ -728,7 +783,7 @@ namespace Barotrauma.Items.Components
|
||||
case "set_fissionrate":
|
||||
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
|
||||
{
|
||||
targetFissionRate = newFissionRate;
|
||||
targetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
#if CLIENT
|
||||
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
|
||||
@@ -738,7 +793,7 @@ namespace Barotrauma.Items.Components
|
||||
case "set_turbineoutput":
|
||||
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
|
||||
{
|
||||
targetTurbineOutput = newTurbineOutput;
|
||||
targetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
#if CLIENT
|
||||
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
|
||||
|
||||
@@ -57,6 +57,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Submarine controlledSub;
|
||||
|
||||
// AI interfacing
|
||||
public Vector2 AITacticalTarget { get; set; }
|
||||
public float AIRamTimer { get; set; }
|
||||
bool navigateTactically; // this will be removed after rewriting steering to use an enum
|
||||
|
||||
private bool showIceSpireWarning;
|
||||
|
||||
private List<Submarine> connectedSubs = new List<Submarine>();
|
||||
@@ -305,7 +310,13 @@ namespace Barotrauma.Items.Components
|
||||
userSkill = user.GetSkillLevel("helm") / 100.0f;
|
||||
}
|
||||
|
||||
if (AutoPilot)
|
||||
// override autopilot pathing while the AI rams, and go full speed ahead
|
||||
if (AIRamTimer > 0f)
|
||||
{
|
||||
AIRamTimer -= deltaTime;
|
||||
TargetVelocity = GetSteeringVelocity(AITacticalTarget, 0f);
|
||||
}
|
||||
else if (AutoPilot)
|
||||
{
|
||||
UpdateAutoPilot(deltaTime);
|
||||
float throttle = 1.0f;
|
||||
@@ -352,6 +363,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float velY = MathHelper.Lerp((neutralBallastLevel * 100 - 50) * 2, -100 * Math.Sign(targetVelocity.Y), Math.Abs(targetVelocity.Y) / 100.0f);
|
||||
item.SendSignal(new Signal(velY.ToString(CultureInfo.InvariantCulture), sender: user), "velocity_y_out");
|
||||
|
||||
// if our tactical AI pilot has left, revert back to maintaining position
|
||||
if (navigateTactically && (user == null || user.SelectedConstruction != item))
|
||||
{
|
||||
navigateTactically = false;
|
||||
AIRamTimer = 0f;
|
||||
SetMaintainPosition();
|
||||
}
|
||||
}
|
||||
|
||||
private void IncreaseSkillLevel(Character user, float deltaTime)
|
||||
@@ -580,13 +599,18 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Vector2 target;
|
||||
if (LevelEndSelected)
|
||||
|
||||
if (navigateTactically)
|
||||
{
|
||||
target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
|
||||
target = ConvertUnits.ToSimUnits(AITacticalTarget);
|
||||
}
|
||||
else if (LevelEndSelected)
|
||||
{
|
||||
target = ConvertUnits.ToSimUnits(Level.Loaded.EndExitPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
target = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition);
|
||||
target = ConvertUnits.ToSimUnits(Level.Loaded.StartExitPosition);
|
||||
}
|
||||
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition), target, errorMsgStr: "(Autopilot, target: " + target + ")");
|
||||
}
|
||||
@@ -597,6 +621,7 @@ namespace Barotrauma.Items.Components
|
||||
MaintainPos = false;
|
||||
posToMaintain = null;
|
||||
LevelEndSelected = false;
|
||||
navigateTactically = false;
|
||||
if (!LevelStartSelected)
|
||||
{
|
||||
LevelStartSelected = true;
|
||||
@@ -610,6 +635,7 @@ namespace Barotrauma.Items.Components
|
||||
MaintainPos = false;
|
||||
posToMaintain = null;
|
||||
LevelStartSelected = false;
|
||||
navigateTactically = false;
|
||||
if (!LevelEndSelected)
|
||||
{
|
||||
LevelEndSelected = true;
|
||||
@@ -617,6 +643,36 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDestinationTactical()
|
||||
{
|
||||
AutoPilot = true;
|
||||
MaintainPos = false;
|
||||
posToMaintain = null;
|
||||
LevelStartSelected = false;
|
||||
LevelEndSelected = false;
|
||||
if (!navigateTactically)
|
||||
{
|
||||
navigateTactically = true;
|
||||
UpdatePath();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMaintainPosition()
|
||||
{
|
||||
if (!MaintainPos)
|
||||
{
|
||||
unsentChanges = true;
|
||||
MaintainPos = true;
|
||||
}
|
||||
if (!posToMaintain.HasValue)
|
||||
{
|
||||
unsentChanges = true;
|
||||
posToMaintain = controlledSub != null ?
|
||||
controlledSub.WorldPosition :
|
||||
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get optimal velocity for moving towards a position
|
||||
/// </summary>
|
||||
@@ -640,6 +696,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
if (objective.Override)
|
||||
{
|
||||
if (user != character && user != null && user.SelectedConstruction == item && character.IsOnPlayerTeam)
|
||||
@@ -648,6 +705,20 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
user = character;
|
||||
|
||||
if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character))
|
||||
{
|
||||
if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNavTerminalIsBroken"), identifier: "navterminalisbroken", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
unsentChanges = true;
|
||||
@@ -659,18 +730,7 @@ namespace Barotrauma.Items.Components
|
||||
case "maintainposition":
|
||||
if (objective.Override)
|
||||
{
|
||||
if (!MaintainPos)
|
||||
{
|
||||
unsentChanges = true;
|
||||
MaintainPos = true;
|
||||
}
|
||||
if (!posToMaintain.HasValue)
|
||||
{
|
||||
unsentChanges = true;
|
||||
posToMaintain = controlledSub != null ?
|
||||
controlledSub.WorldPosition :
|
||||
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
|
||||
}
|
||||
SetMaintainPosition();
|
||||
}
|
||||
break;
|
||||
case "navigateback":
|
||||
@@ -681,7 +741,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (objective.Override)
|
||||
{
|
||||
if (MaintainPos || LevelEndSelected || !LevelStartSelected)
|
||||
if (MaintainPos || LevelEndSelected || !LevelStartSelected || navigateTactically)
|
||||
{
|
||||
unsentChanges = true;
|
||||
}
|
||||
@@ -696,13 +756,28 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (objective.Override)
|
||||
{
|
||||
if (MaintainPos || !LevelEndSelected || LevelStartSelected)
|
||||
if (MaintainPos || !LevelEndSelected || LevelStartSelected || navigateTactically)
|
||||
{
|
||||
unsentChanges = true;
|
||||
}
|
||||
SetDestinationLevelEnd();
|
||||
}
|
||||
break;
|
||||
case "navigatetactical":
|
||||
if (Level.IsLoadedOutpost) { break; }
|
||||
if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
item.SendSignal("1", "toggle_docking");
|
||||
}
|
||||
if (objective.Override)
|
||||
{
|
||||
if (MaintainPos || LevelEndSelected || LevelStartSelected || !navigateTactically)
|
||||
{
|
||||
unsentChanges = true;
|
||||
}
|
||||
SetDestinationTactical();
|
||||
}
|
||||
break;
|
||||
}
|
||||
sonar?.AIOperate(deltaTime, character, objective);
|
||||
if (!MaintainPos && showIceSpireWarning && character.IsOnPlayerTeam)
|
||||
|
||||
@@ -137,16 +137,9 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HasAnyFinishedGrowing())
|
||||
{
|
||||
Msg = MsgHarvest;
|
||||
ParseMsg();
|
||||
return true;
|
||||
}
|
||||
|
||||
Msg = string.Empty;
|
||||
Msg = MsgHarvest;
|
||||
ParseMsg();
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Pick(Character character)
|
||||
@@ -199,12 +192,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Debug.Assert(container != null, "Tried to harvest a planter without an item container.");
|
||||
|
||||
bool anyDecayed = GrowableSeeds.Any(s => s is { } seed && (seed.Decayed || seed.FullyGrown));
|
||||
for (var i = 0; i < GrowableSeeds.Length; i++)
|
||||
{
|
||||
Growable? seed = GrowableSeeds[i];
|
||||
if (seed == null) { continue; }
|
||||
|
||||
if (seed.Decayed || seed.FullyGrown)
|
||||
if (!anyDecayed || seed.Decayed || seed.FullyGrown)
|
||||
{
|
||||
container?.Inventory.RemoveItem(seed.Item);
|
||||
Entity.Spawner?.AddToRemoveQueue(seed.Item);
|
||||
|
||||
@@ -359,7 +359,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//other junction boxes don't need to receive the signal in the pass-through signal connections
|
||||
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
||||
if (ic is PowerTransfer && !(ic is RelayComponent) && connection.Name.Contains("signal")) { continue; }
|
||||
if (ic is PowerTransfer && !(ic is RelayComponent)) { continue; }
|
||||
ic.ReceiveSignal(signal, recipient);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Barotrauma.Items.Components
|
||||
public Vector2 Point;
|
||||
public Vector2 Normal;
|
||||
public float Fraction;
|
||||
|
||||
public HitscanResult(Fixture fixture, Vector2 point, Vector2 normal, float fraction)
|
||||
{
|
||||
Fixture = fixture;
|
||||
@@ -45,6 +44,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||
|
||||
private bool removePending;
|
||||
|
||||
//continuous collision detection is used while the projectile is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
|
||||
@@ -71,6 +72,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public Character Attacker { get; set; }
|
||||
|
||||
public IEnumerable<Body> Hits
|
||||
{
|
||||
get { return hits; }
|
||||
@@ -81,7 +84,11 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(10.0f, false, description: "The impulse applied to the physics body of the item when it's launched. Higher values make the projectile faster.")]
|
||||
public float LaunchImpulse { get; set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "The random percentage modifier used to add variance to the launch impulse.")]
|
||||
public float ImpulseSpread { get; set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "The rotation of the item relative to the rotation of the weapon when launched (in degrees).")]
|
||||
|
||||
public float LaunchRotation
|
||||
{
|
||||
get { return MathHelper.ToDegrees(LaunchRotationRadians); }
|
||||
@@ -168,6 +175,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Override random spread with static spread; hitscan are launched with an equal amount of angle between them. Only applies when firing multiple hitscan.")]
|
||||
public bool StaticSpread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
@@ -189,7 +203,9 @@ namespace Barotrauma.Items.Components
|
||||
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
Attack = new Attack(subElement, item.Name + ", Projectile");
|
||||
}
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
@@ -264,11 +280,26 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = 0; i < HitScanCount; i++)
|
||||
{
|
||||
float launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * Rand.Range(-0.5f, 0.5f));
|
||||
float launchAngle;
|
||||
|
||||
if (StaticSpread)
|
||||
{
|
||||
float staticSpread = Spread / (HitScanCount - 1);
|
||||
// because the position of the item changes as hitscan are fired, we will set an
|
||||
// initial offset on the first hitscan and then increase the item's angle by a set amount as hitscan are fired
|
||||
float offset = i == 0 ? -staticSpread * (HitScanCount -1) : 0f;
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(staticSpread + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * Rand.Range(-0.5f, 0.5f));
|
||||
}
|
||||
|
||||
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
|
||||
if (Hitscan)
|
||||
{
|
||||
Vector2 prevSimpos = item.SimPosition;
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, launchAngle);
|
||||
DoHitscan(launchDir);
|
||||
if (i < HitScanCount - 1)
|
||||
{
|
||||
@@ -277,7 +308,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
DoLaunch(launchDir * LaunchImpulse * item.body.Mass);
|
||||
item.body.SetTransform(item.body.SimPosition, launchAngle);
|
||||
float modifiedLaunchImpulse = LaunchImpulse * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread));
|
||||
DoLaunch(launchDir * modifiedLaunchImpulse * item.body.Mass);
|
||||
}
|
||||
}
|
||||
User = character;
|
||||
@@ -331,7 +364,14 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
|
||||
Vector2 rayStart = simPositon;
|
||||
Vector2 rayEnd = simPositon + dir * 500.0f;
|
||||
Vector2 rayEnd = rayStart + dir * 500.0f;
|
||||
|
||||
Vector2 rayStartWorld = item.WorldPosition;
|
||||
float worldDist = 1000.0f;
|
||||
#if CLIENT
|
||||
worldDist = Screen.Selected?.Cam?.WorldView.Width ?? GameMain.GraphicsWidth;
|
||||
#endif
|
||||
Vector2 rayEndWorld = rayStartWorld + dir * worldDist;
|
||||
|
||||
List<HitscanResult> hits = new List<HitscanResult>();
|
||||
|
||||
@@ -375,6 +415,7 @@ namespace Barotrauma.Items.Components
|
||||
item.SetTransform(h.Point, rotation);
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
|
||||
{
|
||||
LaunchProjSpecific(rayStartWorld, item.WorldPosition);
|
||||
hitSomething = true;
|
||||
break;
|
||||
}
|
||||
@@ -383,6 +424,8 @@ namespace Barotrauma.Items.Components
|
||||
//the raycast didn't hit anything -> the projectile flew somewhere outside the level and is permanently lost
|
||||
if (!hitSomething)
|
||||
{
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation);
|
||||
LaunchProjSpecific(rayStartWorld, rayEndWorld);
|
||||
if (Entity.Spawner == null)
|
||||
{
|
||||
item.Remove();
|
||||
@@ -413,8 +456,15 @@ namespace Barotrauma.Items.Components
|
||||
var aabb = new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f);
|
||||
GameMain.World.QueryAABB((fixture) =>
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return true; }
|
||||
if (fixture?.Body.UserData is LevelObject levelObj)
|
||||
{
|
||||
if (!levelObj.Prefab.TakeLevelWallDamage) { return true; }
|
||||
}
|
||||
else if (fixture?.Body == null || fixture.IsSensor)
|
||||
{
|
||||
//ignore sensors and items
|
||||
return true;
|
||||
}
|
||||
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; }
|
||||
@@ -422,6 +472,7 @@ namespace Barotrauma.Items.Components
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
if (submarine != null)
|
||||
{
|
||||
if (fixture.Body.UserData is VoronoiCell) { return true; }
|
||||
if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return true; }
|
||||
}
|
||||
|
||||
@@ -442,7 +493,15 @@ namespace Barotrauma.Items.Components
|
||||
GameMain.World.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return -1; }
|
||||
if (fixture?.Body.UserData is LevelObject levelObj)
|
||||
{
|
||||
if (!levelObj.Prefab.TakeLevelWallDamage) { return -1; }
|
||||
}
|
||||
else if (fixture?.Body == null || fixture.IsSensor)
|
||||
{
|
||||
//ignore sensors and items
|
||||
return -1;
|
||||
}
|
||||
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; }
|
||||
@@ -456,21 +515,40 @@ namespace Barotrauma.Items.Components
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
if (submarine != null)
|
||||
{
|
||||
if (fixture.Body.UserData is VoronoiCell) { return -1; }
|
||||
if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return -1; }
|
||||
}
|
||||
|
||||
//ignore level cells if the item and the point of impact are inside a sub
|
||||
if (fixture.Body.UserData is VoronoiCell && this.item.Submarine != null)
|
||||
if (fixture.Body.UserData is VoronoiCell)
|
||||
{
|
||||
if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null)
|
||||
if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null && this.item.Submarine != null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (hits.Count > 50)
|
||||
{
|
||||
float furthestHit = 0.0f;
|
||||
int furthestHitIndex = -1;
|
||||
for (int i = 0; i < hits.Count; i++)
|
||||
{
|
||||
if (hits[i].Fraction > furthestHit)
|
||||
{
|
||||
furthestHitIndex = i;
|
||||
furthestHit = hits[i].Fraction;
|
||||
}
|
||||
}
|
||||
if (furthestHitIndex > -1)
|
||||
{
|
||||
hits.RemoveAt(furthestHitIndex);
|
||||
}
|
||||
}
|
||||
|
||||
hits.Add(new HitscanResult(fixture, point, normal, fraction));
|
||||
|
||||
return hits.Count < 25 ? 1 : 0;
|
||||
return 1;
|
||||
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel);
|
||||
|
||||
return hits;
|
||||
@@ -488,14 +566,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
|
||||
}
|
||||
|
||||
if (!removePending)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
}
|
||||
|
||||
if (item.body != null && item.body.FarseerBody.IsBullet)
|
||||
{
|
||||
if (item.body.LinearVelocity.LengthSquared() < ContinuousCollisionThreshold * ContinuousCollisionThreshold)
|
||||
@@ -573,11 +654,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (target.Body.UserData is Limb limb)
|
||||
{
|
||||
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
|
||||
if (limb.IsSevered)
|
||||
{
|
||||
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
|
||||
return true;
|
||||
//push the severed limb around a bit, but let the projectile pass through it
|
||||
limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (target.Body.UserData is Item item)
|
||||
@@ -594,6 +675,10 @@ namespace Barotrauma.Items.Components
|
||||
hits.Add(target.Body);
|
||||
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal, item.body.LinearVelocity));
|
||||
IsActive = true;
|
||||
if (RemoveOnHit)
|
||||
{
|
||||
item.body.FarseerBody.ResetDynamics();
|
||||
}
|
||||
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
|
||||
{
|
||||
Deactivate();
|
||||
@@ -605,6 +690,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
|
||||
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity)
|
||||
{
|
||||
if (User != null && User.Removed) { User = null; }
|
||||
@@ -615,6 +702,9 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
float projectileNewSpeed = 0.5f;
|
||||
float projectileDeflectedNewSpeed = 0.1f;
|
||||
|
||||
AttackResult attackResult = new AttackResult();
|
||||
Character character = null;
|
||||
if (target.Body.UserData is Submarine submarine)
|
||||
@@ -626,12 +716,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (target.Body.UserData is Limb limb)
|
||||
{
|
||||
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
|
||||
if (limb.IsSevered) { return true; }
|
||||
if (limb.character == null || limb.character.Removed) { return false; }
|
||||
// when hitting limbs with piercing ammo, don't lose as much speed
|
||||
if (MaxTargetsToHit > 1)
|
||||
{
|
||||
projectileNewSpeed = 1f;
|
||||
projectileDeflectedNewSpeed = 0.8f;
|
||||
}
|
||||
if (limb.IsSevered || limb.character == null || limb.character.Removed) { return false; }
|
||||
|
||||
limb.character.LastDamageSource = item;
|
||||
if (Attack != null) { attackResult = Attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); }
|
||||
if (Attack != null) { attackResult = Attack.DoDamageToLimb(User ?? Attacker, limb, item.WorldPosition, 1.0f); }
|
||||
if (limb.character != null) { character = limb.character; }
|
||||
}
|
||||
else if (target.Body.UserData is Item targetItem)
|
||||
@@ -639,18 +733,18 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem.Removed) { return false; }
|
||||
if (Attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0)
|
||||
{
|
||||
attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
|
||||
}
|
||||
}
|
||||
else if (target.Body.UserData is IDamageable damageable)
|
||||
{
|
||||
if (Attack != null) { attackResult = Attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); }
|
||||
if (Attack != null) { attackResult = Attack.DoDamage(User ?? Attacker, damageable, item.WorldPosition, 1.0f); }
|
||||
}
|
||||
else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.StructureDamage) > 0.0f)
|
||||
else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.LevelWallDamage) > 0.0f)
|
||||
{
|
||||
if (Level.Loaded?.ExtraWalls.Find(w => w.Body == target.Body) is DestructibleLevelWall destructibleWall)
|
||||
{
|
||||
attackResult = Attack.DoDamage(User, destructibleWall, item.WorldPosition, 1.0f);
|
||||
attackResult = Attack.DoDamage(User ?? Attacker, destructibleWall, item.WorldPosition, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,8 +784,8 @@ namespace Barotrauma.Items.Components
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(targetLimb.WorldPosition, targets);
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(targetLimb.WorldPosition, targets));
|
||||
effect.Apply(ActionType.OnActive, 1.0f, targetLimb.character, targets);
|
||||
}
|
||||
|
||||
@@ -731,7 +825,7 @@ namespace Barotrauma.Items.Components
|
||||
if (attackResult.AppliedDamageModifiers != null &&
|
||||
attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles))
|
||||
{
|
||||
item.body.LinearVelocity *= 0.1f;
|
||||
item.body.LinearVelocity *= projectileDeflectedNewSpeed;
|
||||
}
|
||||
else if (Vector2.Dot(velocity, collisionNormal) < 0.0f && hits.Count() >= MaxTargetsToHit &&
|
||||
target.Body.Mass > item.body.Mass * 0.5f &&
|
||||
@@ -761,13 +855,13 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
item.body.LinearVelocity *= 0.5f;
|
||||
item.body.LinearVelocity *= projectileNewSpeed;
|
||||
|
||||
return Hitscan;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.LinearVelocity *= 0.5f;
|
||||
item.body.LinearVelocity *= projectileNewSpeed;
|
||||
}
|
||||
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
@@ -784,15 +878,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (RemoveOnHit)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
//clients aren't allowed to remove items by themselves, so lets hide the projectile until the server tells us to remove it
|
||||
item.HiddenInGame = Hitscan;
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner?.AddToRemoveQueue(item);
|
||||
}
|
||||
removePending = true;
|
||||
item.HiddenInGame = true;
|
||||
item.body.FarseerBody.Enabled = false;
|
||||
Entity.Spawner?.AddToRemoveQueue(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -876,5 +965,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
}
|
||||
partial void LaunchProjSpecific(Vector2 startLocation, Vector2 endLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Rope : ItemComponent, IServerSerializable
|
||||
{
|
||||
private Item source, target;
|
||||
private ISpatialEntity source;
|
||||
private Item target;
|
||||
|
||||
private float snapTimer;
|
||||
private const float SnapAnimDuration = 1.0f;
|
||||
@@ -85,7 +86,7 @@ namespace Barotrauma.Items.Components
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
|
||||
public void Attach(Item source, Item target)
|
||||
public void Attach(ISpatialEntity source, Item target)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(source != null);
|
||||
System.Diagnostics.Debug.Assert(target != null);
|
||||
@@ -97,7 +98,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (source == null || source.Removed || target == null || target.Removed)
|
||||
if (source == null || target == null || target.Removed ||
|
||||
(source is Entity sourceEntity && sourceEntity.Removed))
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
@@ -195,19 +197,13 @@ namespace Barotrauma.Items.Components
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
{
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
{
|
||||
sourceBody.ApplyForce(forceDir * SourcePullForce);
|
||||
}
|
||||
sourceBody?.ApplyForce(forceDir * SourcePullForce);
|
||||
}
|
||||
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null)
|
||||
{
|
||||
targetBody.ApplyForce(-forceDir * TargetPullForce);
|
||||
}
|
||||
targetBody?.ApplyForce(-forceDir * TargetPullForce);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,32 +220,40 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private PhysicsBody GetBodyToPull(Item target)
|
||||
private PhysicsBody GetBodyToPull(ISpatialEntity target)
|
||||
{
|
||||
if (target.ParentInventory is CharacterInventory characterInventory &&
|
||||
characterInventory.Owner is Character ownerCharacter)
|
||||
if (target is Item targetItem)
|
||||
{
|
||||
if (ownerCharacter.Removed) { return null; }
|
||||
return ownerCharacter.AnimController.Collider;
|
||||
if (targetItem.ParentInventory is CharacterInventory characterInventory &&
|
||||
characterInventory.Owner is Character ownerCharacter)
|
||||
{
|
||||
if (ownerCharacter.Removed) { return null; }
|
||||
return ownerCharacter.AnimController.Collider;
|
||||
}
|
||||
var projectile = targetItem.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
{
|
||||
if (projectile.StickTarget?.UserData is Structure structure)
|
||||
{
|
||||
return structure.Submarine?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Submarine sub)
|
||||
{
|
||||
return sub?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Character character)
|
||||
{
|
||||
return character.AnimController.Collider;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (targetItem.body != null) { return targetItem.body; }
|
||||
}
|
||||
var projectile = target.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
else if (target is Limb targetLimb)
|
||||
{
|
||||
if (projectile.StickTarget?.UserData is Structure structure)
|
||||
{
|
||||
return structure.Submarine?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Submarine sub)
|
||||
{
|
||||
return sub?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Character character)
|
||||
{
|
||||
return character.AnimController.Collider;
|
||||
}
|
||||
return null;
|
||||
return targetLimb.body;
|
||||
}
|
||||
if (target.body != null) { return target.body; }
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
public bool TemporarilyLocked
|
||||
{
|
||||
get { return Level.IsLoadedOutpost && item.GetComponent<DockingPort>() != null; }
|
||||
}
|
||||
|
||||
//connection panels can't be deactivated externally (by signals or status effects)
|
||||
public override bool IsActive
|
||||
{
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private string[] labels;
|
||||
[Serialize("", true, description: "The texts displayed on the buttons/tickboxes, separated by commas.")]
|
||||
[Serialize("", true, description: "The texts displayed on the buttons/tickboxes, separated by commas.", alwaysUseInstanceValues: true)]
|
||||
public string Labels
|
||||
{
|
||||
get { return string.Join(",", labels); }
|
||||
@@ -111,7 +111,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private string[] signals;
|
||||
[Serialize("", true, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.")]
|
||||
[Serialize("", true, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.", alwaysUseInstanceValues: true)]
|
||||
public string Signals
|
||||
{
|
||||
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
|
||||
|
||||
+31
-26
@@ -20,9 +20,10 @@ namespace Barotrauma.Items.Components
|
||||
private bool castShadows;
|
||||
private bool drawBehindSubs;
|
||||
|
||||
|
||||
private double lastToggleSignalTime;
|
||||
|
||||
private string prevColorSignal;
|
||||
|
||||
public PhysicsBody ParentBody;
|
||||
|
||||
private Turret turret;
|
||||
@@ -37,7 +38,7 @@ namespace Barotrauma.Items.Components
|
||||
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
if (light != null) { light.Range = range; }
|
||||
if (Light != null) { Light.Range = range; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -53,7 +54,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
castShadows = value;
|
||||
#if CLIENT
|
||||
if (light != null) light.CastShadows = value;
|
||||
if (Light != null) Light.CastShadows = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -67,7 +68,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
drawBehindSubs = value;
|
||||
#if CLIENT
|
||||
if (light != null) light.IsBackground = drawBehindSubs;
|
||||
if (Light != null) Light.IsBackground = drawBehindSubs;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -93,7 +94,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.Flicker = flicker; }
|
||||
if (Light != null) { Light.LightSourceParams.Flicker = flicker; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
flickerSpeed = value;
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.FlickerSpeed = flickerSpeed; }
|
||||
if (Light != null) { Light.LightSourceParams.FlickerSpeed = flickerSpeed; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -119,7 +120,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.PulseFrequency = pulseFrequency; }
|
||||
if (Light != null) { Light.LightSourceParams.PulseFrequency = pulseFrequency; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -132,7 +133,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.PulseAmount = pulseAmount; }
|
||||
if (Light != null) { Light.LightSourceParams.PulseAmount = pulseAmount; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -145,7 +146,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.BlinkFrequency = blinkFrequency; }
|
||||
if (Light != null) { Light.LightSourceParams.BlinkFrequency = blinkFrequency; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -158,7 +159,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
lightColor = value;
|
||||
#if CLIENT
|
||||
if (light != null) light.Color = IsActive ? lightColor : Color.Transparent;
|
||||
if (Light != null) Light.Color = IsActive ? lightColor : Color.Transparent;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -173,7 +174,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
#if CLIENT
|
||||
light.Position += amount;
|
||||
Light.Position += amount;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -197,7 +198,7 @@ namespace Barotrauma.Items.Components
|
||||
: base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
light = new LightSource(element)
|
||||
Light = new LightSource(element)
|
||||
{
|
||||
ParentSub = item.CurrentHull?.Submarine,
|
||||
Position = item.Position,
|
||||
@@ -206,11 +207,11 @@ namespace Barotrauma.Items.Components
|
||||
SpriteScale = Vector2.One * item.Scale,
|
||||
Range = range
|
||||
};
|
||||
light.LightSourceParams.Flicker = flicker;
|
||||
light.LightSourceParams.FlickerSpeed = FlickerSpeed;
|
||||
light.LightSourceParams.PulseAmount = pulseAmount;
|
||||
light.LightSourceParams.PulseFrequency = pulseFrequency;
|
||||
light.LightSourceParams.BlinkFrequency = blinkFrequency;
|
||||
Light.LightSourceParams.Flicker = flicker;
|
||||
Light.LightSourceParams.FlickerSpeed = FlickerSpeed;
|
||||
Light.LightSourceParams.PulseAmount = pulseAmount;
|
||||
Light.LightSourceParams.PulseFrequency = pulseFrequency;
|
||||
Light.LightSourceParams.BlinkFrequency = blinkFrequency;
|
||||
#endif
|
||||
|
||||
IsActive = IsOn;
|
||||
@@ -233,7 +234,7 @@ namespace Barotrauma.Items.Components
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
#if CLIENT
|
||||
light.ParentSub = item.Submarine;
|
||||
Light.ParentSub = item.Submarine;
|
||||
#endif
|
||||
if (item.Container != null)
|
||||
{
|
||||
@@ -243,23 +244,23 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (ParentBody != null)
|
||||
{
|
||||
light.Position = ParentBody.Position;
|
||||
Light.Position = ParentBody.Position;
|
||||
}
|
||||
else if (turret != null)
|
||||
{
|
||||
light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
|
||||
Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
light.Position = item.Position;
|
||||
Light.Position = item.Position;
|
||||
}
|
||||
#endif
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null)
|
||||
{
|
||||
#if CLIENT
|
||||
light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
|
||||
light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
|
||||
Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
|
||||
Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
|
||||
#endif
|
||||
if (!body.Enabled)
|
||||
{
|
||||
@@ -270,8 +271,8 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
light.LightSpriteEffect = item.SpriteEffects;
|
||||
Light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
Light.LightSpriteEffect = item.SpriteEffects;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -326,7 +327,11 @@ namespace Barotrauma.Items.Components
|
||||
IsOn = signal.value != "0";
|
||||
break;
|
||||
case "set_color":
|
||||
LightColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
if (signal.value != prevColorSignal)
|
||||
{
|
||||
LightColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
prevColorSignal = signal.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class MotionSensor : ItemComponent
|
||||
{
|
||||
private const float UpdateInterval = 0.1f;
|
||||
private float rangeX, rangeY;
|
||||
|
||||
private Vector2 detectOffset;
|
||||
@@ -19,7 +18,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Any,
|
||||
Human,
|
||||
Monster
|
||||
Monster,
|
||||
Wall
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
@@ -74,6 +74,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.1f, MaxValueFloat = 100.0f, DecimalCount = 2), Serialize(0.1f, true, description: "How often the sensor checks if there's something moving near it. Higher values are better for performance.", alwaysUseInstanceValues: true)]
|
||||
public float UpdateInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
@@ -134,6 +141,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f);
|
||||
}
|
||||
|
||||
//randomize update timer so all sensors aren't updated during the same frame
|
||||
updateTimer = Rand.Range(0.0f, UpdateInterval);
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
@@ -153,7 +163,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(new Signal(signalOut, 1), "state_out"); }
|
||||
|
||||
updateTimer -= deltaTime;
|
||||
if (updateTimer > 0.0f) return;
|
||||
if (updateTimer > 0.0f) { return; }
|
||||
|
||||
MotionDetected = false;
|
||||
updateTimer = UpdateInterval;
|
||||
@@ -163,6 +173,7 @@ namespace Barotrauma.Items.Components
|
||||
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,42 +182,94 @@ namespace Barotrauma.Items.Components
|
||||
float broadRangeX = Math.Max(rangeX * 2, 500);
|
||||
float broadRangeY = Math.Max(rangeY * 2, 500);
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
if (item.CurrentHull == null && item.Submarine != null && Level.Loaded != null &&
|
||||
(Target == TargetType.Wall || Target == TargetType.Any))
|
||||
{
|
||||
if (IgnoreDead && c.IsDead) { continue; }
|
||||
|
||||
//ignore characters that have spawned a second or less ago
|
||||
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
||||
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
|
||||
switch (Target)
|
||||
if (Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity)
|
||||
{
|
||||
case TargetType.Human:
|
||||
if (!c.IsHuman) { continue; }
|
||||
break;
|
||||
case TargetType.Monster:
|
||||
if (c.IsHuman || c.IsPet) { continue; }
|
||||
break;
|
||||
var cells = Level.Loaded.GetCells(item.WorldPosition, 1);
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.IsPointInside(item.WorldPosition))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
Vector2 e1 = edge.Point1 + cell.Translation;
|
||||
Vector2 e2 = edge.Point2 + cell.Translation;
|
||||
if (MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.Right, detectRect.Y)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Bottom), new Vector2(detectRect.Right, detectRect.Bottom)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.X, detectRect.Bottom)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.Right, detectRect.Y), new Vector2(detectRect.Right, detectRect.Bottom)))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
//before the more accurate limb-based check
|
||||
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (sub == item.Submarine) { continue; }
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) { continue; }
|
||||
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
||||
Vector2 relativeVelocity = item.Submarine.Velocity - sub.Velocity;
|
||||
if (Math.Abs(relativeVelocity.X) < MinimumVelocity && Math.Abs(relativeVelocity.Y) < MinimumVelocity) { continue; }
|
||||
|
||||
Rectangle worldBorders = new Rectangle(
|
||||
sub.Borders.X + (int)sub.WorldPosition.X,
|
||||
sub.Borders.Y + (int)sub.WorldPosition.Y - sub.Borders.Height,
|
||||
sub.Borders.Width,
|
||||
sub.Borders.Height);
|
||||
|
||||
if (worldBorders.Intersects(detectRect))
|
||||
{
|
||||
MotionDetected = true;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Target != TargetType.Wall)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (IgnoreDead && c.IsDead) { continue; }
|
||||
|
||||
//ignore characters that have spawned a second or less ago
|
||||
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
||||
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
|
||||
switch (Target)
|
||||
{
|
||||
case TargetType.Human:
|
||||
if (!c.IsHuman) { continue; }
|
||||
break;
|
||||
case TargetType.Monster:
|
||||
if (c.IsHuman || c.IsPet) { continue; }
|
||||
break;
|
||||
}
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
//before the more accurate limb-based check
|
||||
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) { continue; }
|
||||
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
|
||||
@@ -19,5 +19,22 @@ namespace Barotrauma.Items.Components
|
||||
this.power = power;
|
||||
this.strength = strength;
|
||||
}
|
||||
|
||||
internal Signal WithStepsTaken(int stepsTaken)
|
||||
{
|
||||
Signal retVal = this;
|
||||
retVal.stepsTaken = stepsTaken;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static bool operator==(Signal a, Signal b) =>
|
||||
a.value == b.value &&
|
||||
a.stepsTaken == b.stepsTaken &&
|
||||
a.sender == b.sender &&
|
||||
a.source == b.source &&
|
||||
MathUtils.NearlyEqual(a.power, b.power) &&
|
||||
MathUtils.NearlyEqual(a.strength, b.strength);
|
||||
|
||||
public static bool operator!=(Signal a, Signal b) => !(a == b);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -64,6 +64,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float value);
|
||||
bool sendOutputImmediately = true;
|
||||
switch (Function)
|
||||
{
|
||||
case FunctionType.Sin:
|
||||
@@ -105,11 +106,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
|
||||
sendOutputImmediately = false;
|
||||
}
|
||||
else if (connection.Name == "signal_in_y")
|
||||
{
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
|
||||
sendOutputImmediately = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -121,9 +124,11 @@ namespace Barotrauma.Items.Components
|
||||
default:
|
||||
throw new NotImplementedException($"Function {Function} has not been implemented.");
|
||||
}
|
||||
|
||||
signal.value = value.ToString("G", CultureInfo.InvariantCulture);
|
||||
item.SendSignal(signal, "signal_out");
|
||||
if (sendOutputImmediately)
|
||||
{
|
||||
signal.value = value.ToString("G", CultureInfo.InvariantCulture);
|
||||
item.SendSignal(signal, "signal_out");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, false, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowLinkingWifiToChat)]
|
||||
[Serialize(false, false, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
|
||||
"as chat messages in the chatbox of the player holding the item.", alwaysUseInstanceValues: true)]
|
||||
public bool LinkToChat
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Barotrauma.Items.Components
|
||||
get
|
||||
{
|
||||
if (GameMain.NetworkMember?.ServerSettings != null && !GameMain.NetworkMember.ServerSettings.AllowRewiring) { return false; }
|
||||
return locked || connections.Any(c => c != null && c.ConnectionPanel.Locked);
|
||||
return locked || connections.Any(c => c != null && (c.ConnectionPanel.Locked || c.ConnectionPanel.TemporarilyLocked));
|
||||
}
|
||||
set { locked = value; }
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components
|
||||
partial class Turret : Powered, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private Sprite barrelSprite, railSprite;
|
||||
private readonly List<(Sprite sprite, Vector2 position)> chargeSprites = new List<(Sprite sprite, Vector2 position)>();
|
||||
private readonly List<Sprite> spinningBarrelSprites = new List<Sprite>();
|
||||
|
||||
private Vector2 barrelPos;
|
||||
private Vector2 transformedBarrelPos;
|
||||
@@ -35,6 +37,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private int failedLaunchAttempts;
|
||||
|
||||
private float currentChargeTime;
|
||||
private bool tryingToCharge;
|
||||
|
||||
private enum ChargingState
|
||||
{
|
||||
Inactive,
|
||||
WindingUp,
|
||||
WindingDown,
|
||||
}
|
||||
|
||||
private ChargingState currentChargingState;
|
||||
|
||||
private float currentBarrelSpin = 0f;
|
||||
|
||||
private readonly List<Item> activeProjectiles = new List<Item>();
|
||||
public IEnumerable<Item> ActiveProjectiles => activeProjectiles;
|
||||
|
||||
@@ -42,6 +58,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float resetUserTimer;
|
||||
|
||||
private float aiTargetingGraceTimer;
|
||||
|
||||
private float aiFindTargetTimer;
|
||||
private Character currentTarget;
|
||||
const float aiFindTargetInterval = 5.0f;
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
@@ -61,6 +83,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("0,0", false, description: "The projectile launching location relative to transformed barrel position (in pixels).")]
|
||||
public Vector2 FiringOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Vector2 TransformedBarrelPos
|
||||
{
|
||||
get
|
||||
@@ -198,6 +226,20 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false, description: "How fast the turret can rotate while firing (for charged weapons).")]
|
||||
public float FiringRotationSpeedModifier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true, description: "Whether the turret should always charge-up fully to shoot.")]
|
||||
public bool SingleChargedShot
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private float prevScale;
|
||||
float prevBaseRotation;
|
||||
[Serialize(0.0f, true, description: "The angle of the turret's base in degrees.", alwaysUseInstanceValues: true)]
|
||||
@@ -225,6 +267,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0f, true, description: "The time required for a charge-type turret to charge up before able to fire.")]
|
||||
public float MaxChargeTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Turret(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -240,6 +289,16 @@ namespace Barotrauma.Items.Components
|
||||
case "railsprite":
|
||||
railSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "chargesprite":
|
||||
chargeSprites.Add((new Sprite(subElement), subElement.GetAttributeVector2("chargetarget", Vector2.Zero)));
|
||||
break;
|
||||
case "spinningbarrelsprite":
|
||||
int spriteCount = subElement.GetAttributeInt("spriteamount", 1);
|
||||
for (int i = 0; i < spriteCount; i++)
|
||||
{
|
||||
spinningBarrelSprites.Add(new Sprite(subElement));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
item.IsShootable = true;
|
||||
@@ -311,6 +370,35 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
float previousChargeTime = currentChargeTime;
|
||||
|
||||
if (SingleChargedShot && reload > 0f)
|
||||
{
|
||||
// single charged shot guns will decharge after firing
|
||||
// for cosmetic reasons, this is done by lerping in half the reload time
|
||||
currentChargeTime = Math.Max(0f, MaxChargeTime * (reload / reloadTime - 0.5f));
|
||||
}
|
||||
else
|
||||
{
|
||||
float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime;
|
||||
currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime);
|
||||
}
|
||||
tryingToCharge = false;
|
||||
|
||||
if (currentChargeTime == 0f)
|
||||
{
|
||||
currentChargingState = ChargingState.Inactive;
|
||||
}
|
||||
else if (currentChargeTime < previousChargeTime)
|
||||
{
|
||||
currentChargingState = ChargingState.WindingDown;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we are charging up or at maxed charge, remain winding up
|
||||
currentChargingState = ChargingState.WindingUp;
|
||||
}
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
if (MathUtils.NearlyEqual(minRotation, maxRotation))
|
||||
@@ -333,6 +421,10 @@ namespace Barotrauma.Items.Components
|
||||
float springStiffness = MathHelper.Lerp(SpringStiffnessLowSkill, SpringStiffnessHighSkill, degreeOfSuccess);
|
||||
float springDamping = MathHelper.Lerp(SpringDampingLowSkill, SpringDampingHighSkill, degreeOfSuccess);
|
||||
float rotationSpeed = MathHelper.Lerp(RotationSpeedLowSkill, RotationSpeedHighSkill, degreeOfSuccess);
|
||||
if (MaxChargeTime > 0)
|
||||
{
|
||||
rotationSpeed *= MathHelper.Lerp(1f, FiringRotationSpeedModifier, MathUtils.EaseIn(currentChargeTime / MaxChargeTime));
|
||||
}
|
||||
|
||||
// Do not increase the weapons skill when operating a turret in an outpost level
|
||||
if (user?.Info != null && (GameMain.GameSession?.Campaign == null || !Level.IsLoadedOutpost))
|
||||
@@ -384,6 +476,15 @@ namespace Barotrauma.Items.Components
|
||||
angularVelocity *= -0.5f;
|
||||
}
|
||||
|
||||
if (aiTargetingGraceTimer > 0f)
|
||||
{
|
||||
aiTargetingGraceTimer -= deltaTime;
|
||||
}
|
||||
if (aiFindTargetTimer > 0.0f)
|
||||
{
|
||||
aiFindTargetTimer -= deltaTime;
|
||||
}
|
||||
|
||||
UpdateLightComponent();
|
||||
}
|
||||
|
||||
@@ -403,10 +504,18 @@ namespace Barotrauma.Items.Components
|
||||
return TryLaunch(deltaTime, character);
|
||||
}
|
||||
|
||||
public bool HasPowerToShoot()
|
||||
{
|
||||
return GetAvailableBatteryPower() >= powerConsumption;
|
||||
}
|
||||
|
||||
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
|
||||
{
|
||||
tryingToCharge = true;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
|
||||
if (currentChargeTime < MaxChargeTime) { return false; }
|
||||
|
||||
if (reload > 0.0f) { return false; }
|
||||
|
||||
if (MaxActiveProjectiles >= 0)
|
||||
@@ -420,7 +529,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!ignorePower)
|
||||
{
|
||||
if (GetAvailableBatteryPower() < powerConsumption)
|
||||
if (!HasPowerToShoot())
|
||||
{
|
||||
#if CLIENT
|
||||
if (!flashLowPower && character != null && character == Character.Controlled)
|
||||
@@ -434,13 +543,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Projectile launchedProjectile = null;
|
||||
bool loaderBroken = false;
|
||||
for (int i = 0; i < ProjectileCount; i++)
|
||||
{
|
||||
var projectiles = GetLoadedProjectiles(true);
|
||||
var projectiles = GetLoadedProjectiles();
|
||||
if (projectiles.Any())
|
||||
{
|
||||
ItemContainer projectileContainer = projectiles.First().Item.Container?.GetComponent<ItemContainer>();
|
||||
if (projectileContainer?.Item != item) { projectileContainer?.Item.Use(deltaTime, null); }
|
||||
if (projectileContainer != null && projectileContainer.Item != item)
|
||||
{
|
||||
projectileContainer?.Item.Use(deltaTime, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -449,11 +562,16 @@ 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 (linkedItem.Condition <= 0.0f)
|
||||
{
|
||||
loaderBroken = true;
|
||||
continue;
|
||||
}
|
||||
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null)
|
||||
{
|
||||
linkedItem.Use(deltaTime, null);
|
||||
projectiles = GetLoadedProjectiles(true);
|
||||
projectiles = GetLoadedProjectiles();
|
||||
if (projectiles.Any()) { break; }
|
||||
}
|
||||
}
|
||||
@@ -465,9 +583,16 @@ namespace Barotrauma.Items.Components
|
||||
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
|
||||
failedLaunchAttempts++;
|
||||
#if CLIENT
|
||||
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
|
||||
if (!flashNoAmmo && !flashLoaderBroken && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
|
||||
{
|
||||
flashNoAmmo = true;
|
||||
if (loaderBroken)
|
||||
{
|
||||
flashLoaderBroken = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
flashNoAmmo = true;
|
||||
}
|
||||
failedLaunchAttempts = 0;
|
||||
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
@@ -475,7 +600,6 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
failedLaunchAttempts = 0;
|
||||
launchedProjectile = projectiles.FirstOrDefault();
|
||||
if (!ignorePower)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
@@ -496,6 +620,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
launchedProjectile = projectiles.FirstOrDefault();
|
||||
if (launchedProjectile?.Item.Container != null)
|
||||
{
|
||||
var repairable = launchedProjectile?.Item.Container.GetComponent<Repairable>();
|
||||
@@ -507,7 +632,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (launchedProjectile != null || LaunchWithoutProjectile)
|
||||
{
|
||||
Launch(launchedProjectile?.Item, character);
|
||||
if (projectiles.Any())
|
||||
{
|
||||
foreach (Projectile projectile in projectiles)
|
||||
{
|
||||
Launch(projectile.Item, character);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Launch(null, character);
|
||||
}
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange;
|
||||
@@ -550,10 +685,10 @@ namespace Barotrauma.Items.Components
|
||||
projectile.body.ResetDynamics();
|
||||
projectile.body.Enabled = true;
|
||||
}
|
||||
|
||||
|
||||
float spread = MathHelper.ToRadians(Spread) * Rand.Range(-0.5f, 0.5f);
|
||||
projectile.SetTransform(
|
||||
ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)),
|
||||
ConvertUnits.ToSimUnits(GetRelativeFiringPosition()),
|
||||
-(launchRotation ?? rotation) + spread);
|
||||
projectile.UpdateTransform();
|
||||
projectile.Submarine = projectile.body?.Submarine;
|
||||
@@ -561,7 +696,8 @@ namespace Barotrauma.Items.Components
|
||||
Projectile projectileComponent = projectile.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
{
|
||||
projectileComponent.Use((float)Timing.Step);
|
||||
projectileComponent.Attacker = user;
|
||||
projectileComponent.Use();
|
||||
projectile.GetComponent<Rope>()?.Attach(item, projectile);
|
||||
projectileComponent.User = user;
|
||||
|
||||
@@ -780,20 +916,19 @@ namespace Barotrauma.Items.Components
|
||||
TryLaunch(deltaTime, ignorePower: true);
|
||||
}
|
||||
|
||||
private bool outOfAmmo;
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
|
||||
previousTarget.IsDead)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 10.0f);
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead"), identifier: "killedtarget" + previousTarget.ID, minDurationBetweenSimilar: 10.0f);
|
||||
character.AIController.SelectTarget(null);
|
||||
}
|
||||
|
||||
if (GetAvailableBatteryPower() < powerConsumption)
|
||||
bool canShoot = true;
|
||||
if (!HasPowerToShoot())
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
|
||||
float lowestCharge = 0.0f;
|
||||
PowerContainer batteryToLoad = null;
|
||||
foreach (PowerContainer battery in batteries)
|
||||
@@ -804,15 +939,31 @@ namespace Barotrauma.Items.Components
|
||||
batteryToLoad = battery;
|
||||
lowestCharge = battery.Charge;
|
||||
}
|
||||
if (battery.Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(battery.Item, character))
|
||||
{
|
||||
if (battery.Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveRepairItem(character, battery.Item, objective.objectiveManager, isPriority: true));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogSupercapacitorIsBroken"), identifier: "supercapacitorisbroken", minDurationBetweenSimilar: 30.0f);
|
||||
canShoot = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (batteryToLoad == null) return true;
|
||||
|
||||
if (batteryToLoad == null) { return true; }
|
||||
if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false));
|
||||
return false;
|
||||
}
|
||||
if (lowestCharge <= 0 && batteryToLoad.Item.ConditionPercentage > 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretHasNoPower"), identifier: "turrethasnopower", minDurationBetweenSimilar: 30.0f);
|
||||
canShoot = false;
|
||||
}
|
||||
}
|
||||
|
||||
int usableProjectileCount = 0;
|
||||
@@ -849,7 +1000,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "cannotloadturret", 30.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "cannotloadturret", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -859,7 +1010,7 @@ namespace Barotrauma.Items.Components
|
||||
loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "loadturret", 30.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "loadturret", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
loadItemsObjective.Abandoned += CheckRemainingAmmo;
|
||||
loadItemsObjective.Completed += CheckRemainingAmmo;
|
||||
@@ -868,15 +1019,16 @@ namespace Barotrauma.Items.Components
|
||||
void CheckRemainingAmmo()
|
||||
{
|
||||
if (!character.IsOnPlayerTeam) { return; }
|
||||
string ammoType = container.Item.HasTag("railgunammosource") ? "railgunammo" : container.Item.HasTag("coilgunammosource") ? "coilgunammo" : "turretammo";
|
||||
if (character.Submarine != Submarine.MainSub) { return; }
|
||||
string ammoType = container.ContainableItems.First().Identifiers.FirstOrDefault() ?? "ammobox";
|
||||
int remainingAmmo = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(ammoType) && i.Condition > 1);
|
||||
if (remainingAmmo == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get($"DialogOutOf{ammoType}"), null, 0.0f, "outofammo", 30.0f);
|
||||
character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else if (remainingAmmo < 3)
|
||||
{
|
||||
character.Speak(TextManager.Get($"DialogLowOn{ammoType}"), null, 0.0f, "outofammo", 30.0f);
|
||||
character.Speak(TextManager.Get($"DialogLowOn{ammoType}"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -891,24 +1043,48 @@ namespace Barotrauma.Items.Components
|
||||
Vector2? targetPos = null;
|
||||
float maxDistance = 10000;
|
||||
float shootDistance = AIRange * item.OffsetOnSelectedMultiplier;
|
||||
float closestDistance = maxDistance * maxDistance;
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
// use full range only if we're actively firing
|
||||
if (aiTargetingGraceTimer <= 0f)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; }
|
||||
// Don't aim monsters that are inside a submarine.
|
||||
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
|
||||
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDistance) { continue; }
|
||||
if (dist < shootDistance * shootDistance)
|
||||
shootDistance *= 0.75f;
|
||||
}
|
||||
|
||||
float closestDistance = maxDistance * maxDistance;
|
||||
|
||||
if (currentTarget != null)
|
||||
{
|
||||
if (currentTarget.Removed || currentTarget.IsDead)
|
||||
{
|
||||
// Only check the angle to targets that are close enough to be shot at
|
||||
// We shouldn't check the angle when a long creature is traveling outside of the shooting range, because doing so would not allow us to shoot the limbs that might be close enough to shoot at.
|
||||
if (!CheckTurretAngle(enemy.WorldPosition)) { continue; }
|
||||
currentTarget = null;
|
||||
}
|
||||
closestEnemy = enemy;
|
||||
closestDistance = dist;
|
||||
}
|
||||
|
||||
if (aiFindTargetTimer <= 0.0f || currentTarget == null)
|
||||
{
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; }
|
||||
// Don't aim monsters that are inside a submarine.
|
||||
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
|
||||
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDistance) { continue; }
|
||||
if (dist < shootDistance * shootDistance)
|
||||
{
|
||||
// Only check the angle to targets that are close enough to be shot at
|
||||
// We shouldn't check the angle when a long creature is traveling outside of the shooting range, because doing so would not allow us to shoot the limbs that might be close enough to shoot at.
|
||||
if (!CheckTurretAngle(enemy.WorldPosition)) { continue; }
|
||||
}
|
||||
closestEnemy = enemy;
|
||||
closestDistance = dist;
|
||||
}
|
||||
currentTarget = closestEnemy;
|
||||
aiFindTargetTimer = aiFindTargetInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
closestEnemy = currentTarget;
|
||||
}
|
||||
|
||||
if (closestEnemy != null)
|
||||
@@ -938,6 +1114,7 @@ namespace Barotrauma.Items.Components
|
||||
else if (item.Submarine != null && Level.Loaded != null)
|
||||
{
|
||||
// Check ice spires
|
||||
shootDistance = AIRange * item.OffsetOnSelectedMultiplier;
|
||||
closestDistance = shootDistance;
|
||||
foreach (var wall in Level.Loaded.ExtraWalls)
|
||||
{
|
||||
@@ -990,6 +1167,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
if (targetPos == null) { return false; }
|
||||
// Force the highest priority so that we don't change the objective while targeting enemies.
|
||||
objective.ForceHighestPriority = true;
|
||||
|
||||
if (closestEnemy != null && character.AIController.SelectedAiTarget != closestEnemy.AiTarget)
|
||||
{
|
||||
@@ -1032,7 +1211,14 @@ namespace Barotrauma.Items.Components
|
||||
float enemyAngle = MathUtils.VectorToAngle(targetPos.Value - item.WorldPosition);
|
||||
float turretAngle = -rotation;
|
||||
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return false; }
|
||||
float maxAngleError = 0.15f;
|
||||
if (MaxChargeTime > 0.0f && currentChargingState == ChargingState.WindingUp && FiringRotationSpeedModifier > 0.0f)
|
||||
{
|
||||
//larger margin of error if the weapon needs to be charged (-> the bot can start charging when the turret is still rotating towards the target)
|
||||
maxAngleError *= 2.0f;
|
||||
}
|
||||
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > maxAngleError) { return false; }
|
||||
|
||||
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
|
||||
Vector2 end = ConvertUnits.ToSimUnits(targetPos.Value);
|
||||
@@ -1084,14 +1270,28 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (character.IsOnPlayerTeam)
|
||||
if (canShoot)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
|
||||
}
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
}
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
aiTargetingGraceTimer = 5f;
|
||||
return false;
|
||||
}
|
||||
|
||||
private Vector2 GetRelativeFiringPosition(bool useOffset = true)
|
||||
{
|
||||
Vector2 transformedFiringOffset = Vector2.Zero;
|
||||
if (useOffset)
|
||||
{
|
||||
transformedFiringOffset = MathUtils.RotatePoint(new Vector2(-FiringOffset.Y, -FiringOffset.X) * item.Scale, -rotation);
|
||||
}
|
||||
return new Vector2(item.WorldRect.X + transformedBarrelPos.X + transformedFiringOffset.X, item.WorldRect.Y - transformedBarrelPos.Y + transformedFiringOffset.Y);
|
||||
}
|
||||
|
||||
private bool CheckTurretAngle(float angle)
|
||||
{
|
||||
float midRotation = (minRotation + maxRotation) / 2.0f;
|
||||
@@ -1116,22 +1316,28 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
}
|
||||
|
||||
private List<Projectile> GetLoadedProjectiles(bool returnFirst = false)
|
||||
private List<Projectile> GetLoadedProjectiles()
|
||||
{
|
||||
List<Projectile> projectiles = new List<Projectile>();
|
||||
//check the item itself first
|
||||
CheckProjectileContainer(item, projectiles, returnFirst);
|
||||
// check the item itself first
|
||||
CheckProjectileContainer(item, projectiles, out bool _);
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
if (e is Item projectileContainer) { CheckProjectileContainer(projectileContainer, projectiles, returnFirst); }
|
||||
if (returnFirst && projectiles.Any()) { return projectiles; }
|
||||
if (e is Item projectileContainer)
|
||||
{
|
||||
CheckProjectileContainer(projectileContainer, projectiles, out bool stopSearching);
|
||||
if (projectiles.Any() || stopSearching) { return projectiles; }
|
||||
}
|
||||
}
|
||||
|
||||
return projectiles;
|
||||
}
|
||||
|
||||
private void CheckProjectileContainer(Item projectileContainer, List<Projectile> projectiles, bool returnFirst)
|
||||
private void CheckProjectileContainer(Item projectileContainer, List<Projectile> projectiles, out bool stopSearching)
|
||||
{
|
||||
stopSearching = false;
|
||||
if (projectileContainer.Condition <= 0.0f) { return; }
|
||||
|
||||
var containedItems = projectileContainer.ContainedItems;
|
||||
if (containedItems == null) { return; }
|
||||
|
||||
@@ -1141,7 +1347,7 @@ namespace Barotrauma.Items.Components
|
||||
if (projectileComponent != null && projectileComponent.Item.body != null)
|
||||
{
|
||||
projectiles.Add(projectileComponent);
|
||||
if (returnFirst) { return; }
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1152,9 +1358,15 @@ namespace Barotrauma.Items.Components
|
||||
if (projectileComponent != null && projectileComponent.Item.body != null)
|
||||
{
|
||||
projectiles.Add(projectileComponent);
|
||||
if (returnFirst) { return; }
|
||||
}
|
||||
}
|
||||
// in the case that we found a container that still has condition/ammo left,
|
||||
// return and inform GetLoadedProjectiles to stop searching past this point (even if no projectiles were not found)
|
||||
if (containedItem.Condition > 0.0f || projectiles.Any())
|
||||
{
|
||||
stopSearching = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1267,7 +1479,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (extraData.Length > 2)
|
||||
{
|
||||
msg.Write(!(extraData[2] is Item item) || item.Removed ? ushort.MaxValue : item.ID);
|
||||
msg.Write(!(extraData[2] is Item item) ? ushort.MaxValue : item.ID);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -51,6 +51,9 @@ namespace Barotrauma
|
||||
public bool InheritTextureScale { get; private set; }
|
||||
public bool InheritOrigin { get; private set; }
|
||||
public bool InheritSourceRect { get; private set; }
|
||||
|
||||
public float Scale { get; private set; }
|
||||
|
||||
public LimbType DepthLimb { get; private set; }
|
||||
private Wearable _wearableComponent;
|
||||
public Wearable WearableComponent
|
||||
@@ -161,10 +164,7 @@ namespace Barotrauma
|
||||
if (IsInitialized) { return; }
|
||||
_gender = UnassignedSpritePath.Contains("[GENDER]") ? gender : Gender.None;
|
||||
ParsePath(false);
|
||||
if (Sprite != null)
|
||||
{
|
||||
Sprite.Remove();
|
||||
}
|
||||
Sprite?.Remove();
|
||||
Sprite = new Sprite(SourceElement, file: SpritePath);
|
||||
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
|
||||
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
|
||||
@@ -175,6 +175,7 @@ namespace Barotrauma
|
||||
InheritSourceRect = SourceElement.GetAttributeBool("inheritsourcerect", false);
|
||||
DepthLimb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("depthlimb", "None"), true);
|
||||
Sound = SourceElement.GetAttributeString("sound", "");
|
||||
Scale = SourceElement.GetAttributeFloat("scale", 1.0f);
|
||||
var index = SourceElement.GetAttributePoint("sheetindex", new Point(-1, -1));
|
||||
if (index.X > -1 && index.Y > -1)
|
||||
{
|
||||
@@ -201,7 +202,7 @@ namespace Barotrauma
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Wearable : Pickable, IServerSerializable
|
||||
partial class Wearable : Pickable, IServerSerializable
|
||||
{
|
||||
private readonly XElement[] wearableElements;
|
||||
private readonly WearableSprite[] wearableSprites;
|
||||
@@ -209,6 +210,7 @@ namespace Barotrauma.Items.Components
|
||||
private readonly Limb[] limb;
|
||||
|
||||
private readonly List<DamageModifier> damageModifiers;
|
||||
public readonly Dictionary<string, float> SkillModifiers;
|
||||
|
||||
public IEnumerable<DamageModifier> DamageModifiers
|
||||
{
|
||||
@@ -264,7 +266,8 @@ namespace Barotrauma.Items.Components
|
||||
this.item = item;
|
||||
|
||||
damageModifiers = new List<DamageModifier>();
|
||||
|
||||
SkillModifiers = new Dictionary<string, float>();
|
||||
|
||||
int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite");
|
||||
Variants = element.GetAttributeInt("variants", 0);
|
||||
variant = Rand.Range(1, Variants + 1, Rand.RandSync.Server);
|
||||
@@ -307,18 +310,35 @@ namespace Barotrauma.Items.Components
|
||||
case "damagemodifier":
|
||||
damageModifiers.Add(new DamageModifier(subElement, item.Name + ", Wearable"));
|
||||
break;
|
||||
case "skillmodifier":
|
||||
string skillIdentifier = subElement.GetAttributeString("skillidentifier", string.Empty);
|
||||
float skillValue = subElement.GetAttributeFloat("skillvalue", 0f);
|
||||
if (SkillModifiers.ContainsKey(skillIdentifier))
|
||||
{
|
||||
SkillModifiers[skillIdentifier] += skillValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
SkillModifiers.TryAdd(skillIdentifier, skillValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
foreach (var allowedSlot in allowedSlots)
|
||||
{
|
||||
if (allowedSlot != InvSlotType.Any && !character.Inventory.IsInLimbSlot(item, allowedSlot)) { return; }
|
||||
}
|
||||
|
||||
picker = character;
|
||||
for (int i = 0; i < wearableSprites.Length; i++ )
|
||||
{
|
||||
var wearableSprite = wearableSprites[i];
|
||||
if (!wearableSprite.IsInitialized) { wearableSprite.Init(picker.Info?.Gender ?? Gender.None); }
|
||||
if (picker.Info?.Gender != Gender.None && (wearableSprite.Gender != Gender.None))
|
||||
if (picker.Info != null && picker.Info?.Gender != Gender.None && (wearableSprite.Gender != Gender.None))
|
||||
{
|
||||
// If the item is gender specific (it has a different textures for male and female), we have to change the gender here so that the texture is updated.
|
||||
wearableSprite.Gender = picker.Info.Gender;
|
||||
@@ -380,6 +400,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character == null || character.Removed) { return; }
|
||||
if (picker == null) { return; }
|
||||
|
||||
for (int i = 0; i < wearableSprites.Length; i++)
|
||||
{
|
||||
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
|
||||
|
||||
Reference in New Issue
Block a user