Merge branch 'master' of https://github.com/Regalis11/Barotrauma.git into Regalis11-master

This commit is contained in:
Evil Factory
2021-12-15 14:45:31 -03:00
388 changed files with 12646 additions and 8136 deletions
@@ -207,8 +207,14 @@ namespace Barotrauma.Items.Components
if (!item.linkedTo.Contains(target.item)) { item.linkedTo.Add(target.item); }
if (!target.item.linkedTo.Contains(item)) { target.item.linkedTo.Add(item); }
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
if (!target.item.Submarine.DockedTo.Contains(item.Submarine))
{
target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
}
if (!item.Submarine.DockedTo.Contains(target.item.Submarine))
{
item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
}
DockingTarget = target;
DockingTarget.DockingTarget = this;
@@ -484,7 +490,7 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"DockingPort.CreateDoorBody:InvalidPosition",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
errorMsg);
position = Vector2.Zero;
}
@@ -779,29 +785,25 @@ namespace Barotrauma.Items.Components
if (IsHorizontal)
{
if (hulls[0].WorldRect.X < hulls[1].WorldRect.X)
if (hulls[0].WorldRect.X > hulls[1].WorldRect.X)
{
gap.linkedTo.Add(hulls[0]);
gap.linkedTo.Add(hulls[1]);
}
else
{
gap.linkedTo.Add(hulls[1]);
gap.linkedTo.Add(hulls[0]);
var temp = hulls[0];
hulls[0] = hulls[1];
hulls[1] = temp;
}
gap.linkedTo.Add(hulls[0]);
gap.linkedTo.Add(hulls[1]);
}
else
{
if (hulls[0].WorldRect.Y > hulls[1].WorldRect.Y)
if (hulls[0].WorldRect.Y < hulls[1].WorldRect.Y)
{
gap.linkedTo.Add(hulls[0]);
gap.linkedTo.Add(hulls[1]);
}
else
{
gap.linkedTo.Add(hulls[1]);
gap.linkedTo.Add(hulls[0]);
var temp = hulls[0];
hulls[0] = hulls[1];
hulls[1] = temp;
}
gap.linkedTo.Add(hulls[0]);
gap.linkedTo.Add(hulls[1]);
}
for (int i = 0; i < 2; i++)
@@ -813,7 +815,7 @@ namespace Barotrauma.Items.Components
if (IsHorizontal)
{
if (item.WorldPosition.X < DockingTarget.item.WorldPosition.X)
if (doorGap.WorldPosition.X < gap.WorldPosition.X)
{
if (!doorGap.linkedTo.Contains(hulls[0])) { doorGap.linkedTo.Add(hulls[0]); }
}
@@ -831,7 +833,7 @@ namespace Barotrauma.Items.Components
}
else
{
if (item.WorldPosition.Y > DockingTarget.item.WorldPosition.Y)
if (doorGap.WorldPosition.Y > gap.WorldPosition.Y)
{
if (!doorGap.linkedTo.Contains(hulls[0])) { doorGap.linkedTo.Add(hulls[0]); }
}
@@ -873,11 +875,17 @@ namespace Barotrauma.Items.Components
if (myWayPoint != null && targetWayPoint != null)
{
myWayPoint.FindHull();
myWayPoint.linkedTo.Remove(targetWayPoint);
myWayPoint.OnLinksChanged?.Invoke(myWayPoint);
if (myWayPoint.linkedTo.Contains(targetWayPoint))
{
myWayPoint.linkedTo.Remove(targetWayPoint);
myWayPoint.OnLinksChanged?.Invoke(myWayPoint);
}
targetWayPoint.FindHull();
targetWayPoint.linkedTo.Remove(myWayPoint);
targetWayPoint.OnLinksChanged?.Invoke(targetWayPoint);
if (targetWayPoint.linkedTo.Contains(myWayPoint))
{
targetWayPoint.linkedTo.Remove(myWayPoint);
targetWayPoint.OnLinksChanged?.Invoke(targetWayPoint);
}
}
}
@@ -204,9 +204,12 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, true, description: ""), Editable]
[Editable, Serialize(true, true, description: "", alwaysUseInstanceValues: true)]
public bool UseBetweenOutpostModules { get; private set; }
[Editable, Serialize(false, false, description: "If true, bots won't try to close this door behind them.", alwaysUseInstanceValues: true)]
public bool BotsShouldKeepOpen { get; private set; }
public Door(Item item, XElement element)
: base(item, element)
{
@@ -291,7 +294,7 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (item.Condition < RepairThreshold) { return true; }
if (item.Condition < RepairThreshold && item.GetComponent<Repairable>().HasRequiredItems(picker, addMessage: false)) { return true; }
if (requiredItems.None()) { return false; }
if (HasAccess(picker) && HasRequiredItems(picker, false)) { return false; }
return base.Pick(picker);
@@ -299,7 +302,7 @@ namespace Barotrauma.Items.Components
public override bool OnPicked(Character picker)
{
if (item.Condition < RepairThreshold) { return true; }
if (item.Condition < RepairThreshold && item.GetComponent<Repairable>().HasRequiredItems(picker, addMessage: false)) { return true; }
if (!HasAccess(picker))
{
ToggleState(ActionType.OnPicked, picker);
@@ -339,6 +342,7 @@ namespace Barotrauma.Items.Components
ToggleState(ActionType.OnUse, character);
PickingTime = originalPickingTime;
StopPicking(picker);
return true;
}
#if CLIENT
else if (hasRequiredItems && character != null && character == Character.Controlled)
@@ -545,7 +549,7 @@ namespace Barotrauma.Items.Components
if (!itemPosErrorShown)
{
DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")");
GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsManager.ErrorSeverity.Error,
"Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ").");
itemPosErrorShown = true;
}
@@ -568,8 +572,8 @@ namespace Barotrauma.Items.Components
if (!characterPosErrorShown.Contains(c))
{
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); }
GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")." +
GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsManager.ErrorSeverity.Error,
"Failed to push a character out of a doorway - position of the character \"" + c.SpeciesName + "\" is not valid (" + c.SimPosition + ")." +
" Removed: " + c.Removed +
" Remoteplayer: " + c.IsRemotePlayer);
characterPosErrorShown.Add(c);
@@ -598,8 +602,8 @@ namespace Barotrauma.Items.Components
if (!MathUtils.IsValid(body.SimPosition))
{
DebugConsole.ThrowError("Failed to push a limb out of a doorway - position of the body (character \"" + c.Name + "\") is not valid (" + body.SimPosition + ")");
GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:LimbPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + body.SimPosition + ")." +
GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:LimbPosInvalid", GameAnalyticsManager.ErrorSeverity.Error,
"Failed to push a character out of a doorway - position of the character \"" + c.SpeciesName + "\" is not valid (" + body.SimPosition + ")." +
" Removed: " + c.Removed +
" Remoteplayer: " + c.IsRemotePlayer);
return false;
@@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components
if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; }
float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
conditionIncrease += user.GetStatValue(StatTypes.GeneticMaterialRefineBonus);
conditionIncrease += user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f;
if (item.Prefab == otherGeneticMaterial.item.Prefab)
{
item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease;
@@ -310,12 +310,6 @@ namespace Barotrauma.Items.Components
internal static class GrowthSideExtension
{
// Enum.HasFlag() sucks
public static bool IsBitSet(this TileSide side, TileSide bit)
{
return ((int) side & (int) bit) != 0;
}
// K&R algorithm for counting how many bits are set in a bit field
public static int Count(this TileSide side)
{
@@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components
CollidesWith = Physics.CollisionCharacter,
CollisionCategories = Physics.CollisionItemBlocking,
Enabled = false,
UserData = "Holdable.Pusher"
UserData = this
};
Pusher.FarseerBody.OnCollision += OnPusherCollision;
Pusher.FarseerBody.FixedRotation = false;
@@ -110,7 +110,9 @@ namespace Barotrauma.Items.Components
if (Item.RequireAimToUse && hitPos < MathHelper.PiOver4) { return false; }
ActivateNearbySleepingCharacters();
reloadTimer = reload / (1 + character.GetStatValue(StatTypes.MeleeAttackSpeed));
reloadTimer = reload;
reloadTimer /= (1f + character.GetStatValue(StatTypes.MeleeAttackSpeed));
reloadTimer /= (1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier));
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall;
@@ -385,7 +387,7 @@ namespace Barotrauma.Items.Components
{
Attack.SetUser(User);
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
if (targetLimb != null)
{
@@ -69,14 +69,28 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
//return if someone is already trying to pick the item
if (pickTimer > 0.0f) return false;
if (picker == null || picker.Inventory == null) return false;
if (pickTimer > 0.0f) { return false; }
if (picker == null || picker.Inventory == null) { return false; }
if (PickingTime > 0.0f)
{
var abilityPickingTime = new AbilityValueItem(PickingTime, item.Prefab);
picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime);
if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped))
{
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Equipped])
{
foreach (var heldItem in picker.HeldItems)
{
if (ri.MatchesItem(heldItem))
{
abilityPickingTime.Value /= 1 + heldItem.Prefab.AddedPickingSpeedMultiplier;
}
}
}
}
if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue)
{
#if SERVER
@@ -142,7 +156,7 @@ namespace Barotrauma.Items.Components
return false;
}
private IEnumerable<object> WaitForPick(Character picker, float requiredTime)
private IEnumerable<CoroutineStatus> WaitForPick(Character picker, float requiredTime)
{
activePicker = picker;
picker.PickingItem = item;
@@ -32,6 +32,13 @@ namespace Barotrauma.Items.Components
set { reload = Math.Max(value, 0.0f); }
}
[Serialize(false, false, description: "Tells the AI to hold the trigger down when it uses this weapon")]
public bool HoldTrigger
{
get;
set;
}
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
@@ -110,9 +117,7 @@ namespace Barotrauma.Items.Components
if (ReloadTimer < 0.0f)
{
ReloadTimer = 0.0f;
// was this an optimization or related to something else? it cannot occur for charge-type weapons
//IsActive = false;
if (MaxChargeTime == 0.0f)
if (MaxChargeTime <= 0f)
{
IsActive = false;
return;
@@ -147,9 +152,10 @@ namespace Barotrauma.Items.Components
private float GetSpread(Character user)
{
float degreeOfFailure = 1.0f - DegreeOfSuccess(user);
float degreeOfFailure = MathHelper.Clamp(1.0f - DegreeOfSuccess(user), 0.0f, 1.0f);
degreeOfFailure *= degreeOfFailure;
return MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure));
float spread = MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure) / (1f + user.GetStatValue(StatTypes.RangedSpreadReduction));
return MathHelper.ToRadians(spread);
}
private readonly List<Body> limbBodies = new List<Body>();
@@ -203,7 +209,8 @@ namespace Barotrauma.Items.Components
{
lastProjectile?.Item.GetComponent<Rope>()?.Snap();
}
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier);
projectile.Launcher = item;
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier);
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
if (i == 0)
@@ -627,7 +627,7 @@ namespace Barotrauma.Items.Components
{
string errorMsg = "ItemComponent.DegreeOfSuccess failed (character was null).\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return 0.0f;
}
@@ -646,6 +646,10 @@ namespace Barotrauma.Items.Components
public virtual void FlipY(bool relativeToSub) { }
public bool IsLoaded(Character user, bool checkContainedItems = true) =>
HasRequiredContainedItems(user, addMessage: false) &&
(!checkContainedItems || Item.OwnInventory == null || Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
public bool HasRequiredContainedItems(Character user, bool addMessage, string msg = null)
{
if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return true; }
@@ -899,7 +903,7 @@ namespace Barotrauma.Items.Components
{
DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException);
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.Load:TargetInvocationException" + item.Name + element.Name,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
"Error while loading entity of the type " + t + " (" + e.InnerException + ")\n" + Environment.StackTrace.CleanupStackTrace());
}
@@ -1004,7 +1008,7 @@ namespace Barotrauma.Items.Components
{
containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
{
targetItemCount = itemCount,
ItemCount = itemCount,
Equip = equip,
RemoveEmpty = removeEmpty,
GetItemPriority = i =>
@@ -470,7 +470,9 @@ namespace Barotrauma.Items.Components
if (!AllowDragAndDrop && user != null) { return false; }
if (!slotRestrictions.Any(s => s.MatchesItem(item))) { return false; }
if (user != null && !user.CanAccessInventory(Inventory)) { return false; }
//genetic materials use special logic for combining, don't allow doing it by placing them inside each other here
if (this.Item.GetComponent<GeneticMaterial>() != null) { return false; }
if (Inventory.TryPutItem(item, user))
{
IsActive = true;
@@ -575,7 +577,7 @@ namespace Barotrauma.Items.Components
{
DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace());
GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:" + contained.Name,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
"SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace());
}
contained.body.Submarine = item.Submarine;
@@ -13,6 +13,8 @@ namespace Barotrauma.Items.Components
partial void OnStateChanged();
private string prevColorSignal;
public override void ReceiveSignal(Signal signal, Connection connection)
{
switch (connection.Name)
@@ -22,6 +24,13 @@ namespace Barotrauma.Items.Components
Text = signal.value;
OnStateChanged();
break;
case "set_text_color":
if (signal.value != prevColorSignal)
{
TextColor = XMLExtensions.ParseColor(signal.value, false);
prevColorSignal = signal.value;
}
break;
}
}
}
@@ -61,14 +61,14 @@ namespace Barotrauma.Items.Components
public IEnumerable<LimbPos> LimbPositions { get { return limbPositions; } }
[Editable, Serialize(false, false, description: "When enabled, the item will continuously send out a 0/1 signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out 1 when interacted with.")]
[Editable, Serialize(false, false, description: "When enabled, the item will continuously send out a 0/1 signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out 1 when interacted with.", alwaysUseInstanceValues: true)]
public bool IsToggle
{
get;
set;
}
[Editable, Serialize(false, false, description: "Whether the item is toggled on/off. Only valid if IsToggle is set to true.")]
[Editable, Serialize(false, false, description: "Whether the item is toggled on/off. Only valid if IsToggle is set to true.", alwaysUseInstanceValues: true)]
public bool State
{
get;
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
private float userDeconstructorSpeedMultiplier = 1.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private const float TinkeringSpeedIncrease = 2.5f;
private ItemContainer inputContainer, outputContainer;
@@ -158,10 +158,21 @@ namespace Barotrauma.Items.Components
// In multiplayer, the server handles the deconstruction into new items
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
float amountMultiplier = 1f;
if (user != null && !user.Removed)
{
var abilityTargetItem = new AbilityItem(targetItem);
var abilityTargetItem = new AbilityDeconstructedItem(targetItem, user);
user.CheckTalents(AbilityEffectType.OnItemDeconstructed, abilityTargetItem);
foreach (Character character in Character.GetFriendlyCrew(user))
{
character.CheckTalents(AbilityEffectType.OnItemDeconstructedByAlly, abilityTargetItem);
}
var itemCreationMultiplier = new AbilityValueItem(amountMultiplier, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemCreationMultiplier);
amountMultiplier = (int)itemCreationMultiplier.Value;
}
if (targetItem.Prefab.RandomDeconstructionOutput)
@@ -187,18 +198,18 @@ namespace Barotrauma.Items.Components
foreach (DeconstructItem deconstructProduct in products)
{
CreateDeconstructProduct(deconstructProduct, inputItems);
CreateDeconstructProduct(deconstructProduct, inputItems, amountMultiplier);
}
}
else
{
foreach (DeconstructItem deconstructProduct in validDeconstructItems)
{
CreateDeconstructProduct(deconstructProduct, inputItems);
CreateDeconstructProduct(deconstructProduct, inputItems, amountMultiplier);
}
}
void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable<Item> inputItems)
void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable<Item> inputItems, float amountMultiplier)
{
float percentageHealth = targetItem.Condition / targetItem.MaxCondition;
@@ -221,7 +232,7 @@ namespace Barotrauma.Items.Components
if (targetItem == otherItem) { continue; }
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r.Equals(otherItem.Prefab.Identifier, StringComparison.OrdinalIgnoreCase)))
{
user.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
user?.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
foreach (Character character in Character.GetFriendlyCrew(user))
{
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
@@ -247,27 +258,35 @@ namespace Barotrauma.Items.Components
}
}
int amount = 1;
if (user != null && !user.Removed)
{
var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated);
amount = (int)itemsCreated.Value;
// used to spawn items directly into the deconstructor
var itemContainer = new AbilityItemPrefabItem(item, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemContainer);
}
int amount = (int)amountMultiplier;
for (int i = 0; i < amount; i++)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
{
spawnedItem.StolenDuringRound = targetItem.StolenDuringRound;
spawnedItem.AllowStealing = targetItem.AllowStealing;
for (int i = 0; i < outputContainer.Capacity; i++)
{
var containedItem = outputContainer.Inventory.GetItemAt(i);
if (containedItem?.Combine(spawnedItem, null) ?? false)
if (containedItem?.OwnInventory != null)
{
foreach (Item subItem in containedItem.ContainedItems.ToList())
{
if (subItem.Combine(spawnedItem, null))
{
break;
}
}
}
else if (containedItem?.Combine(spawnedItem, null) ?? false)
{
break;
}
@@ -283,7 +302,13 @@ namespace Barotrauma.Items.Components
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
{
if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null));
foreach (Item containedItem in ic.Inventory.AllItemsMod)
{
if (!outputContainer.Inventory.TryPutItem(containedItem, user: null))
{
containedItem.Drop(dropper: null);
}
}
}
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
@@ -401,4 +426,15 @@ namespace Barotrauma.Items.Components
inputContainer.Inventory.Locked = IsActive;
}
}
class AbilityDeconstructedItem : AbilityObject, IAbilityItem, IAbilityCharacter
{
public AbilityDeconstructedItem(Item item, Character character)
{
Item = item;
Character = character;
}
public Item Item { get; set; }
public Character Character { get; set; }
}
}
@@ -20,6 +20,11 @@ namespace Barotrauma.Items.Components
private string savedFabricatedItem;
private float savedTimeUntilReady, savedRequiredTime;
private readonly Dictionary<string, List<Item>> availableIngredients = new Dictionary<string, List<Item>>();
const float RefreshIngredientsInterval = 1.0f;
private float refreshIngredientsTimer;
private bool hasPower;
private Character user;
@@ -32,7 +37,7 @@ namespace Barotrauma.Items.Components
[Serialize(1.0f, true)]
public float SkillRequirementMultiplier { get; set; }
private const float TinkeringSpeedIncrease = 1.5f;
private const float TinkeringSpeedIncrease = 2.5f;
private enum FabricatorState
{
@@ -174,6 +179,8 @@ namespace Barotrauma.Items.Components
if (selectedItem == null) { return; }
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
RefreshAvailableIngredients();
#if CLIENT
itemList.Enabled = false;
activateButton.Text = TextManager.Get("FabricatorCancel");
@@ -242,7 +249,13 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
var availableIngredients = GetAvailableIngredients();
if (refreshIngredientsTimer <= 0.0f)
{
RefreshAvailableIngredients();
refreshIngredientsTimer = RefreshIngredientsInterval;
}
refreshIngredientsTimer -= deltaTime;
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
{
CancelFabricating();
@@ -271,56 +284,87 @@ namespace Barotrauma.Items.Components
State = FabricatorState.Active;
}
float tinkeringStrength = 0f;
var repairable = item.GetComponent<Repairable>();
if (repairable != null)
{
repairable.LastActiveTime = (float)Timing.TotalTime + 10.0f;
if (repairable.IsTinkering)
{
tinkeringStrength = repairable.TinkeringStrength;
}
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerConsumption <= 0) { Voltage = 1.0f; }
float tinkeringStrength = 0f;
if (repairable.IsTinkering)
{
tinkeringStrength = repairable.TinkeringStrength;
}
float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease;
timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(Voltage, 1.0f);
UpdateRequiredTimeProjSpecific();
if (timeUntilReady > 0.0f) { return; }
if (timeUntilReady <= 0.0f)
{
Fabricate();
}
}
private void Fabricate()
{
RefreshAvailableIngredients();
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
{
CancelFabricating();
return;
}
bool ingredientsStolen = false;
bool ingredientsAllowStealing = true;
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
fabricatedItem.RequiredItems.ForEach(requiredItem => {
fabricatedItem.RequiredItems.ForEach(requiredItem =>
{
for (int usedPrefabsAmount = 0; usedPrefabsAmount < requiredItem.Amount; usedPrefabsAmount++)
{
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
{
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab =>
var availableItems = availableIngredients[requiredPrefab.Identifier];
var availableItem = availableItems.FirstOrDefault(potentialPrefab =>
{
return potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f &&
potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f;
});
if (availablePrefab == null) { continue; }
if (availableItem == null) { continue; }
if (requiredItem.UseCondition && availablePrefab.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
ingredientsStolen |= availableItem.StolenDuringRound;
if (!availableItem.AllowStealing)
{
availablePrefab.Condition -= availablePrefab.Prefab.Health * requiredItem.MinCondition;
continue;
ingredientsAllowStealing = false;
}
availablePrefabs.Remove(availablePrefab);
Entity.Spawner.AddToRemoveQueue(availablePrefab);
inputContainer.Inventory.RemoveItem(availablePrefab);
//Leave it behind with reduced condition if it has enough to stay above 0
if (requiredItem.UseCondition && availableItem.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f)
{
availableItem.Condition -= availableItem.Prefab.Health * requiredItem.MinCondition;
continue;
}
if (availableItem.OwnInventory != null)
{
foreach (Item containedItem in availableItem.OwnInventory.AllItemsMod)
{
containedItem.Drop(dropper: null);
}
}
availableItems.Remove(availableItem);
Entity.Spawner.AddToRemoveQueue(availableItem);
inputContainer.Inventory.RemoveItem(availableItem);
}
}
});
@@ -332,7 +376,7 @@ namespace Barotrauma.Items.Components
int quality = 0;
if (user?.Info != null)
{
foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID))
foreach (Character character in Character.GetFriendlyCrew(user))
{
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem);
}
@@ -351,6 +395,9 @@ namespace Barotrauma.Items.Components
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
spawnedItem.StolenDuringRound = ingredientsStolen;
spawnedItem.AllowStealing = ingredientsAllowStealing;
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
});
@@ -361,6 +408,9 @@ namespace Barotrauma.Items.Components
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
spawnedItem.StolenDuringRound = ingredientsStolen;
spawnedItem.AllowStealing = ingredientsAllowStealing;
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
});
@@ -383,13 +433,12 @@ namespace Barotrauma.Items.Components
{
float userSkill = user.GetSkillLevel(skill.Identifier);
float addedSkill = skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f);
var addedSkillValue = new AbilityValueString(0f, skill.Identifier);
var addedSkillValue = new AbilityValueString(addedSkill, skill.Identifier);
user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue);
addedSkill += addedSkillValue.Value;
user.Info.IncreaseSkillLevel(
skill.Identifier,
addedSkill);
addedSkillValue.Value);
}
}
@@ -408,6 +457,7 @@ namespace Barotrauma.Items.Components
CancelFabricating();
}
}
private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user)
@@ -446,8 +496,8 @@ namespace Barotrauma.Items.Components
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
foreach (Item availablePrefab in availablePrefabs)
{
if (availablePrefab.Condition / availablePrefab.Prefab.Health >= requiredItem.MinCondition &&
availablePrefab.Condition / availablePrefab.Prefab.Health <= requiredItem.MaxCondition)
if (availablePrefab.ConditionPercentage / 100.0f >= requiredItem.MinCondition &&
availablePrefab.ConditionPercentage / 100.0f <= requiredItem.MaxCondition)
{
availablePrefabsAmount++;
}
@@ -490,14 +540,10 @@ namespace Barotrauma.Items.Components
return SkillRequirementMultiplier;
}
/// <summary>
/// Get a list of all items available in the input container and linked containers
/// </summary>
/// <returns></returns>
private Dictionary<string, List<Item>> GetAvailableIngredients()
private void RefreshAvailableIngredients()
{
List<Item> availableIngredients = new List<Item>();
availableIngredients.AddRange(inputContainer.Inventory.AllItems);
List<Item> itemList = new List<Item>();
itemList.AddRange(inputContainer.Inventory.AllItems);
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item linkedItem)
@@ -511,34 +557,38 @@ namespace Barotrauma.Items.Components
itemContainer = deconstructor.OutputContainer;
}
availableIngredients.AddRange(itemContainer.Inventory.AllItems);
itemList.AddRange(itemContainer.Inventory.AllItems);
}
}
for (int i = 0; i < itemList.Count; i++)
{
var container = itemList[i].GetComponent<ItemContainer>();
if (container != null)
{
itemList.AddRange(container.Inventory.AllItems);
}
}
#if CLIENT
if (Character.Controlled?.Inventory != null)
{
availableIngredients.AddRange(Character.Controlled.Inventory.AllItems);
itemList.AddRange(Character.Controlled.Inventory.AllItems);
}
#else
if (user?.Inventory != null)
{
availableIngredients.AddRange(user.Inventory.AllItems);
itemList.AddRange(user.Inventory.AllItems);
}
#endif
Dictionary<string, List<Item>> ingredientsDictionary = new Dictionary<string, List<Item>>();
for (int i = 0; i < availableIngredients.Count; i++)
availableIngredients.Clear();
foreach (Item item in itemList)
{
var itemIdentifier = availableIngredients[i].prefab.Identifier;
if (!ingredientsDictionary.ContainsKey(itemIdentifier))
var itemIdentifier = item.prefab.Identifier;
if (!availableIngredients.ContainsKey(itemIdentifier))
{
ingredientsDictionary[itemIdentifier] = new List<Item>(availableIngredients.Count);
availableIngredients[itemIdentifier] = new List<Item>(itemList.Count);
}
ingredientsDictionary[itemIdentifier].Add(availableIngredients[i]);
availableIngredients[itemIdentifier].Add(item);
}
return ingredientsDictionary;
}
/// <summary>
@@ -552,7 +602,6 @@ namespace Barotrauma.Items.Components
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
var availableIngredients = GetAvailableIngredients();
targetItem.RequiredItems.ForEach(requiredItem => {
for (int i = 0; i < requiredItem.Amount; i++)
{
@@ -588,6 +637,7 @@ namespace Barotrauma.Items.Components
}
}
});
RefreshAvailableIngredients();
}
public override XElement Save(XElement parentElement)
@@ -147,16 +147,12 @@ namespace Barotrauma.Items.Components
{
case "water_data_in":
//cheating a bit because water detectors don't actually send the water level
float waterAmount;
if (source.GetComponent<WaterDetector>() == null)
bool fromWaterDetector = source.GetComponent<WaterDetector>() != null;
hullData.ReceivedWaterAmount = null;
if (fromWaterDetector)
{
waterAmount = Rand.Range(0.0f, 1.0f);
hullData.ReceivedWaterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
}
else
{
waterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
}
hullData.ReceivedWaterAmount = waterAmount;
foreach (var linked in sourceHull.linkedTo)
{
if (!(linked is Hull linkedHull)) { continue; }
@@ -165,7 +161,11 @@ namespace Barotrauma.Items.Components
linkedHullData = new HullData();
hullDatas.Add(linkedHull, linkedHullData);
}
linkedHullData.ReceivedWaterAmount = waterAmount;
linkedHullData.ReceivedWaterAmount = null;
if (fromWaterDetector)
{
linkedHullData.ReceivedWaterAmount = Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f);
}
}
break;
case "oxygen_data_in":
@@ -70,7 +70,7 @@ namespace Barotrauma.Items.Components
public bool HasPower => IsActive && Voltage >= MinVoltage;
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private const float TinkeringSpeedIncrease = 4.0f;
public Pump(Item item, XElement element)
: base(item, element)
@@ -190,28 +190,38 @@ namespace Barotrauma.Items.Components
#if CLIENT
if (GameMain.Client != null) { return false; }
#endif
if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase))
switch (objective.Option.ToLowerInvariant())
{
case "pumpout":
#if SERVER
if (objective.Override || FlowPercentage > 0.0f)
{
item.CreateServerEvent(this);
}
if (objective.Override || !IsActive || FlowPercentage > -100.0f)
{
item.CreateServerEvent(this);
}
#endif
IsActive = false;
FlowPercentage = 0.0f;
}
else
{
IsActive = true;
FlowPercentage = -100.0f;
break;
case "pumpin":
#if SERVER
if (objective.Override || !IsActive || FlowPercentage > -100.0f)
{
item.CreateServerEvent(this);
}
if (objective.Override || !IsActive || FlowPercentage < 100.0f)
{
item.CreateServerEvent(this);
}
#endif
IsActive = true;
FlowPercentage = -100.0f;
IsActive = true;
FlowPercentage = 100.0f;
break;
case "stoppumping":
#if SERVER
if (objective.Override || FlowPercentage > 0.0f)
{
item.CreateServerEvent(this);
}
#endif
IsActive = false;
FlowPercentage = 0.0f;
break;
}
return true;
}
@@ -12,7 +12,6 @@ namespace Barotrauma.Items.Components
partial class Reactor : Powered, IServerSerializable, IClientSerializable
{
const float NetworkUpdateIntervalHigh = 0.5f;
const float NetworkUpdateIntervalLow = 10.0f;
//the rate at which the reactor is being run on (higher rate -> higher temperature)
private float fissionRate;
@@ -38,9 +37,8 @@ namespace Barotrauma.Items.Components
private float maxPowerOutput;
private Queue<float> loadQueue = new Queue<float>();
private float load;
private readonly Queue<float> loadQueue = new Queue<float>();
private bool unsentChanges;
private float sendUpdateTimer;
@@ -158,11 +156,6 @@ namespace Barotrauma.Items.Components
set { /*do nothing*/ }
}
private float correctTurbineOutput;
private float targetFissionRate;
private float targetTurbineOutput;
[Serialize(false, true, description: "Is the automatic temperature control currently on. Indended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
public bool AutoTemp
{
@@ -181,6 +174,25 @@ namespace Barotrauma.Items.Components
[Serialize(0.0f, true)]
public float AvailableFuel { get; set; }
[Serialize(0.0f, true)]
public new float Load { get; private set; }
[Serialize(0.0f, true)]
public float TargetFissionRate { get; set; }
[Serialize(0.0f, true)]
public float TargetTurbineOutput { get; set; }
[Serialize(0.0f, true)]
public float CorrectTurbineOutput { get; set; }
[Editable, Serialize(true, true)]
public bool ExplosionDamagesOtherSubs
{
get;
set;
}
public Reactor(Item item, XElement element)
: base(item, element)
{
@@ -199,8 +211,8 @@ namespace Barotrauma.Items.Components
{
GameServer.Log(GameServer.CharacterLogName(lastUser) + " adjusted reactor settings: " +
"Temperature: " + (int)(temperature * 100.0f) +
", Fission rate: " + (int)targetFissionRate +
", Turbine output: " + (int)targetTurbineOutput +
", Fission rate: " + (int)TargetFissionRate +
", Turbine output: " + (int)TargetTurbineOutput +
(autoTemp ? ", Autotemp ON" : ", Autotemp OFF"),
ServerLog.MessageType.ItemInteraction);
@@ -223,7 +235,7 @@ namespace Barotrauma.Items.Components
}
#if CLIENT
if(PowerOn && AvailableFuel < 1)
if (PowerOn && AvailableFuel < 1)
{
HintManager.OnReactorOutOfFuel(this);
}
@@ -236,15 +248,15 @@ namespace Barotrauma.Items.Components
//so the player doesn't have to keep adjusting the rate impossibly fast when the load fluctuates heavily
if (!MathUtils.NearlyEqual(MaxPowerOutput, 0.0f))
{
correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime;
CorrectTurbineOutput += MathHelper.Clamp((Load / MaxPowerOutput * 100.0f) - CorrectTurbineOutput, -10.0f, 10.0f) * deltaTime;
}
//calculate tolerances of the meters based on the skills of the user
//more skilled characters have larger "sweet spots", making it easier to keep the power output at a suitable level
float tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
optimalTurbineOutput = new Vector2(CorrectTurbineOutput - tolerance, CorrectTurbineOutput + tolerance);
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
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);
@@ -260,9 +272,9 @@ namespace Barotrauma.Items.Components
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);
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(TargetFissionRate, AvailableFuel), deltaTime);
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
TurbineOutput = MathHelper.Lerp(turbineOutput, TargetTurbineOutput, deltaTime);
float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
currPowerConsumption = -MaxPowerOutput * Math.Min(turbineOutput / 100.0f, temperatureFactor);
@@ -276,7 +288,7 @@ namespace Barotrauma.Items.Components
float maxAutoAdjust = maxPowerOutput * 0.1f;
autoAdjustAmount = MathHelper.Lerp(
autoAdjustAmount,
MathHelper.Clamp(-load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
MathHelper.Clamp(-Load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
deltaTime * 10.0f);
}
else
@@ -287,8 +299,8 @@ namespace Barotrauma.Items.Components
if (!PowerOn)
{
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
TargetFissionRate = 0.0f;
TargetTurbineOutput = 0.0f;
}
else if (autoTemp)
{
@@ -317,56 +329,30 @@ 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 = MathUtils.NearlyEqual(MaxPowerOutput, 0.0f) ? 0.0f : currentLoad / MaxPowerOutput * 100.0f;
tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
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)
{
load = loadQueue.Average();
Load = loadQueue.Average();
loadQueue.Dequeue();
}
float fuelLeft = 0.0f;
var containedItems = item.OwnInventory?.AllItems;
if (containedItems != null)
{
foreach (Item item in containedItems)
{
if (!item.HasTag("reactorfuel")) { continue; }
if (fissionRate > 0.0f)
{
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
}
fuelLeft += item.ConditionPercentage;
}
}
if (fissionRate > 0.0f)
{
var containedItems = item.OwnInventory?.AllItems;
if (containedItems != null)
{
foreach (Item item in containedItems)
{
if (!item.HasTag("reactorfuel")) { continue; }
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
}
}
if (item.AiTarget != null && MaxPowerOutput > 0)
{
var aiTarget = item.AiTarget;
@@ -385,8 +371,9 @@ namespace Barotrauma.Items.Components
item.SendSignal(((int)(temperature * 100.0f)).ToString(), "temperature_out");
item.SendSignal(((int)-CurrPowerConsumption).ToString(), "power_value_out");
item.SendSignal(((int)load).ToString(), "load_value_out");
item.SendSignal(((int)Load).ToString(), "load_value_out");
item.SendSignal(((int)AvailableFuel).ToString(), "fuel_out");
item.SendSignal(((int)fuelLeft).ToString(), "fuel_percentage_left");
UpdateFailures(deltaTime);
#if CLIENT
@@ -407,8 +394,7 @@ namespace Barotrauma.Items.Components
{
item.CreateServerEvent(this);
}
#endif
#if CLIENT
#elif CLIENT
if (GameMain.Client != null)
{
item.CreateClientEvent(this);
@@ -424,12 +410,6 @@ 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>
@@ -438,7 +418,7 @@ namespace Barotrauma.Items.Components
private bool NeedMoreFuel(float minimumOutputRatio, float minCondition = 0)
{
float remainingFuel = item.ContainedItems.Sum(i => i.Condition);
if (remainingFuel <= minCondition && load > 0.0f)
if (remainingFuel <= minCondition && Load > 0.0f)
{
return true;
}
@@ -455,7 +435,7 @@ namespace Barotrauma.Items.Components
float theoreticalMaxOutput = Math.Min(maxTurbineOutput / 100.0f, temperatureFactor) * MaxPowerOutput;
//maximum output not enough, we need more fuel
return theoreticalMaxOutput < load * minimumOutputRatio;
return theoreticalMaxOutput < Load * minimumOutputRatio;
}
private bool TooMuchFuel()
@@ -467,7 +447,7 @@ namespace Barotrauma.Items.Components
float minimumHeat = GetGeneratedHeat(optimalFissionRate.X);
//if we need a very high turbine output to keep the engine from overheating, there's too much fuel
return minimumHeat > Math.Min(correctTurbineOutput * 1.5f, 90);
return minimumHeat > Math.Min(CorrectTurbineOutput * 1.5f, 90);
}
private void UpdateFailures(float deltaTime)
@@ -514,26 +494,26 @@ namespace Barotrauma.Items.Components
public void UpdateAutoTemp(float speed, float deltaTime)
{
float desiredTurbineOutput = (optimalTurbineOutput.X + optimalTurbineOutput.Y) / 2.0f;
targetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - targetTurbineOutput, -speed, speed) * deltaTime;
targetTurbineOutput = MathHelper.Clamp(targetTurbineOutput, 0.0f, 100.0f);
TargetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - TargetTurbineOutput, -speed, speed) * deltaTime;
TargetTurbineOutput = MathHelper.Clamp(TargetTurbineOutput, 0.0f, 100.0f);
float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
targetFissionRate += MathHelper.Clamp(desiredFissionRate - targetFissionRate, -speed, speed) * deltaTime;
TargetFissionRate += MathHelper.Clamp(desiredFissionRate - TargetFissionRate, -speed, speed) * deltaTime;
if (temperature > (optimalTemperature.X + optimalTemperature.Y) / 2.0f)
{
targetFissionRate = Math.Min(targetFissionRate - speed * 2 * deltaTime, allowedFissionRate.Y);
TargetFissionRate = Math.Min(TargetFissionRate - speed * 2 * deltaTime, allowedFissionRate.Y);
}
else if (-currPowerConsumption < load)
else if (-currPowerConsumption < Load)
{
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, 100.0f);
TargetFissionRate = Math.Min(TargetFissionRate + speed * 2 * deltaTime, 100.0f);
}
targetFissionRate = MathHelper.Clamp(targetFissionRate, 0.0f, 100.0f);
TargetFissionRate = MathHelper.Clamp(TargetFissionRate, 0.0f, 100.0f);
//don't push the target too far from the current fission rate
//otherwise we may "overshoot", cranking the target fission rate all the way up because it takes a while
//for the actual fission rate and temperature to follow
targetFissionRate = MathHelper.Clamp(targetFissionRate, FissionRate - 5, FissionRate + 5);
TargetFissionRate = MathHelper.Clamp(TargetFissionRate, FissionRate - 5, FissionRate + 5);
}
public void PowerUpImmediately()
@@ -557,8 +537,8 @@ namespace Barotrauma.Items.Components
currPowerConsumption = 0.0f;
Temperature -= deltaTime * 1000.0f;
targetFissionRate = Math.Max(targetFissionRate - deltaTime * 10.0f, 0.0f);
targetTurbineOutput = Math.Max(targetTurbineOutput - deltaTime * 10.0f, 0.0f);
TargetFissionRate = Math.Max(TargetFissionRate - deltaTime * 10.0f, 0.0f);
TargetTurbineOutput = Math.Max(TargetTurbineOutput - deltaTime * 10.0f, 0.0f);
#if CLIENT
FissionRateScrollBar.BarScroll = 1.0f - FissionRate / 100.0f;
TurbineOutputScrollBar.BarScroll = 1.0f - TurbineOutput / 100.0f;
@@ -571,6 +551,20 @@ namespace Barotrauma.Items.Components
if (item.Condition <= 0.0f) { return; }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (!ExplosionDamagesOtherSubs && (statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false))
{
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
{
foreach (Explosion explosion in statusEffect.Explosions)
{
foreach (Submarine sub in Submarine.Loaded)
{
if (sub != item.Submarine) { explosion.IgnoredSubmarines.Add(sub); }
}
}
}
}
item.Condition = 0.0f;
fireTimer = 0.0f;
meltDownTimer = 0.0f;
@@ -583,7 +577,6 @@ namespace Barotrauma.Items.Components
containedItem.Condition = 0.0f;
}
}
#if SERVER
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
if (GameMain.Server != null)
@@ -628,7 +621,7 @@ namespace Barotrauma.Items.Components
var container = item.GetComponent<ItemContainer>();
if (objective.SubObjectives.None())
{
var containObjective = AIContainItems<Reactor>(container, character, objective, itemCount: 1, equip: true, removeEmpty: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC, dropItemOnDeselected: true);
var containObjective = AIContainItems<Reactor>(container, character, objective, itemCount: 1, equip: true, removeEmpty: true, spawnItemIfNotFound: !character.IsOnPlayerTeam, dropItemOnDeselected: true);
containObjective.Completed += ReportFuelRodCount;
containObjective.Abandoned += ReportFuelRodCount;
character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
@@ -696,15 +689,15 @@ namespace Barotrauma.Items.Components
bool prevAutoTemp = autoTemp;
bool prevPowerOn = _powerOn;
float prevFissionRate = targetFissionRate;
float prevTurbineOutput = targetTurbineOutput;
float prevFissionRate = TargetFissionRate;
float prevTurbineOutput = TargetTurbineOutput;
if (shutDown)
{
PowerOn = false;
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
TargetFissionRate = 0.0f;
TargetTurbineOutput = 0.0f;
unsentChanges = true;
return true;
}
@@ -730,8 +723,8 @@ namespace Barotrauma.Items.Components
#endif
if (autoTemp != prevAutoTemp ||
prevPowerOn != _powerOn ||
Math.Abs(prevFissionRate - targetFissionRate) > 1.0f ||
Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f)
Math.Abs(prevFissionRate - TargetFissionRate) > 1.0f ||
Math.Abs(prevTurbineOutput - TargetTurbineOutput) > 1.0f)
{
unsentChanges = true;
}
@@ -767,32 +760,32 @@ namespace Barotrauma.Items.Components
switch (connection.Name)
{
case "shutdown":
if (targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
if (TargetFissionRate > 0.0f || TargetTurbineOutput > 0.0f)
{
PowerOn = false;
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
TargetFissionRate = 0.0f;
TargetTurbineOutput = 0.0f;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
}
break;
case "set_fissionrate":
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
{
targetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
TargetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
#if CLIENT
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
#endif
}
break;
case "set_turbineoutput":
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
{
targetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
TargetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
#if CLIENT
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
#endif
}
break;
@@ -17,6 +17,8 @@ namespace Barotrauma.Items.Components
public const float DefaultSonarRange = 10000.0f;
public const float PassivePowerConsumption = 0.1f;
class ConnectedTransducer
{
public readonly SonarTransducer Transducer;
@@ -150,7 +152,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = (currentMode == Mode.Active) ? powerConsumption : powerConsumption * 0.1f;
currPowerConsumption = (currentMode == Mode.Active) ? powerConsumption : powerConsumption * PassivePowerConsumption;
UpdateOnActiveEffects(deltaTime);
@@ -332,7 +334,9 @@ namespace Barotrauma.Items.Components
if (connection.Name == "transducer_in")
{
var transducer = signal.source.GetComponent<SonarTransducer>();
if (transducer == null) return;
if (transducer == null) { return; }
transducer.ConnectedSonar = this;
var connectedTransducer = connectedTransducers.Find(t => t.Transducer == transducer);
if (connectedTransducer == null)
@@ -8,6 +8,8 @@ namespace Barotrauma.Items.Components
private float sendSignalTimer;
public Sonar ConnectedSonar;
public SonarTransducer(Item item, XElement element) : base(item, element)
{
IsActive = true;
@@ -17,7 +19,7 @@ namespace Barotrauma.Items.Components
{
UpdateOnActiveEffects(deltaTime);
CurrPowerConsumption = powerConsumption;
CurrPowerConsumption = powerConsumption * (ConnectedSonar?.CurrentMode == Sonar.Mode.Active ? 1.0f : Sonar.PassivePowerConsumption);
if (Voltage >= MinVoltage)
{
@@ -61,6 +61,13 @@ namespace Barotrauma.Items.Components
public List<Body> IgnoredBodies;
/// <summary>
/// The item that launched this projectile (if any)
/// </summary>
public Item Launcher;
private Character stickTargetCharacter;
private Character _user;
public Character User
{
@@ -322,6 +329,7 @@ namespace Barotrauma.Items.Components
item.body.SetTransform(item.body.SimPosition, launchAngle);
float modifiedLaunchImpulse = LaunchImpulse * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread));
DoLaunch(launchDir * modifiedLaunchImpulse * item.body.Mass);
System.Diagnostics.Debug.WriteLine("launch: " + modifiedLaunchImpulse + " - " + item.body.LinearVelocity);
}
}
User = character;
@@ -345,7 +353,7 @@ namespace Barotrauma.Items.Components
launchPos = item.SimPosition;
item.body.Enabled = true;
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.9f);
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.95f);
item.body.FarseerBody.OnCollision += OnProjectileCollision;
item.body.FarseerBody.IsBullet = true;
@@ -521,11 +529,13 @@ namespace Barotrauma.Items.Components
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
//ignore everything else than characters, sub walls and level walls
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; }
if (!(fixture.Body.UserData is Holdable holdable && holdable.CanPush))
{
//ignore everything else than characters, sub walls and level walls
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; }
}
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
if (submarine != null)
@@ -564,7 +574,7 @@ namespace Barotrauma.Items.Components
hits.Add(new HitscanResult(fixture, point, normal, fraction));
return 1;
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel);
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking);
return hits;
}
@@ -614,8 +624,8 @@ namespace Barotrauma.Items.Components
return;
}
//target very far from the item -> update the item's transform to make sure it's inside the same sub as the target (or outside)
if (Math.Abs(stickJoint.JointTranslation) > 100.0f)
// Update the item's transform to make sure it's inside the same sub as the target (or outside)
if (StickTarget?.UserData is Limb target && target.Submarine != item.Submarine || Math.Abs(stickJoint.JointTranslation) > 100.0f)
{
item.UpdateTransform();
}
@@ -752,7 +762,7 @@ namespace Barotrauma.Items.Components
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)
else if ((target.Body.UserData as Item ?? (target.Body.UserData as ItemComponent)?.Item) is Item targetItem)
{
if (targetItem.Removed) { return false; }
if (Attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0)
@@ -866,7 +876,7 @@ namespace Barotrauma.Items.Components
(DoesStick ||
(StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
(StickToStructures && target.Body.UserData is Structure) ||
(StickToItems && target.Body.UserData is Item)))
(StickToItems && target.Body.UserData is Item)))
{
Vector2 dir = new Vector2(
(float)Math.Cos(item.body.Rotation),
@@ -965,9 +975,14 @@ namespace Barotrauma.Items.Components
GameMain.World.Add(stickJoint);
IsActive = true;
if (targetBody.UserData is Limb limb)
{
stickTargetCharacter = limb.character;
stickTargetCharacter.AttachedProjectiles.Add(this);
}
}
private void Unstick()
public void Unstick()
{
StickTarget = null;
if (stickJoint != null)
@@ -979,25 +994,21 @@ namespace Barotrauma.Items.Components
stickJoint = null;
}
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
item.GetComponent<Rope>()?.Snap();
if (stickTargetCharacter != null)
{
stickTargetCharacter.AttachedProjectiles.Remove(this);
stickTargetCharacter = null;
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
if (stickJoint != null)
if (IsStuckToTarget || stickJoint != null || stickTargetCharacter != null)
{
try
{
GameMain.World.Remove(stickJoint);
}
catch
{
//the body that the projectile was stuck to has been removed
}
stickJoint = null;
Unstick();
}
}
partial void LaunchProjSpecific(Vector2 startLocation, Vector2 endLocation);
}
@@ -26,14 +26,18 @@ namespace Barotrauma.Items.Components
RepairToolStructureRepairMultiplier,
RepairToolStructureDamageMultiplier,
RepairToolDeattachTimeMultiplier,
FirepowerMultiplier,
StrikingPowerMultiplier,
StrikingSpeedMultiplier,
FiringRateMultiplier,
// unused as of now
AttackMultiplier,
// unused as of now
AttackSpeedMultiplier,
ForceDoorsOpenSpeedMultiplier,
RangedSpreadReduction,
ChargeSpeedMultiplier,
MovementSpeedMultiplier,
// generic stats to be used for various needs, declared just in case (localization)
EffectivenessMultiplier,
PowerOutputMultiplier,
ConsumptionReductionMultiplier,
@@ -43,7 +47,7 @@ namespace Barotrauma.Items.Components
private int qualityLevel;
[Serialize(0, true)]
[Editable, Serialize(0, true)]
public int QualityLevel
{
get { return qualityLevel; }
@@ -105,11 +105,6 @@ namespace Barotrauma.Items.Components
public bool IsTinkering { get; private set; } = false;
public float RepairIconThreshold
{
get { return RepairThreshold / 2; }
}
public Character CurrentFixer { get; private set; }
private Item currentRepairItem;
@@ -118,6 +113,9 @@ namespace Barotrauma.Items.Components
public float TinkeringStrength => tinkeringStrength;
public bool IsBelowRepairThreshold => item.ConditionPercentage <= RepairThreshold;
public bool IsBelowRepairIconThreshold => item.ConditionPercentage <= RepairThreshold / 2;
public enum FixActions : int
{
None = 0,
@@ -179,8 +177,17 @@ namespace Barotrauma.Items.Components
if (bestRepairItem != null && bestRepairItem.Prefab.CannotRepairFail) { return true; }
// unpowered (electrical) items can be repaired without a risk of electrical shock
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) &&
item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f) { return true; }
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)))
{
if (item.GetComponent<Reactor>() is Reactor reactor)
{
if (MathUtils.NearlyEqual(reactor.CurrPowerConsumption, 0.0f, 0.1f)) { return true; }
}
else if (item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f)
{
return true;
}
}
if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; }
@@ -199,7 +206,7 @@ namespace Barotrauma.Items.Components
public float RepairDegreeOfSuccess(Character character, List<Skill> skills)
{
if (skills.Count == 0) return 1.0f;
if (skills.Count == 0) { return 1.0f; }
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
float average = skillSum / skills.Count;
@@ -207,6 +214,21 @@ namespace Barotrauma.Items.Components
return ((average + 100.0f) / 2.0f) / 100.0f;
}
public void RepairBoost(bool qteSuccess)
{
if (qteSuccess)
{
item.Condition += RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
}
else if (Rand.Range(0.0f, 2.0f) > RepairDegreeOfSuccess(CurrentFixer, requiredSkills))
{
ApplyStatusEffects(ActionType.OnFailure, 1.0f, CurrentFixer);
#if SERVER
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, CurrentFixer.ID });
#endif
}
}
public bool StartRepairing(Character character, FixActions action)
{
if (character == null || character.IsDead || action == FixActions.None)
@@ -291,6 +313,8 @@ namespace Barotrauma.Items.Components
currentRepairItem = null;
currentFixerAction = FixActions.None;
#if CLIENT
qteTimer = QteDuration;
qteCooldown = 0.0f;
repairSoundChannel?.FadeOutAndDispose();
repairSoundChannel = null;
#endif
@@ -393,7 +417,7 @@ namespace Barotrauma.Items.Components
float successFactor = requiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, requiredSkills);
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it
if (item.ConditionPercentage < RepairThreshold)
if (IsBelowRepairThreshold)
{
wasBroken = true;
}
@@ -437,6 +461,7 @@ namespace Barotrauma.Items.Components
SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete);
}
if (CurrentFixer?.SelectedConstruction == item) { CurrentFixer.SelectedConstruction = null; }
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
wasBroken = false;
StopRepairing(CurrentFixer);
@@ -524,7 +549,7 @@ namespace Barotrauma.Items.Components
public void AdjustPowerConsumption(ref float powerConsumption)
{
if (item.ConditionPercentage < RepairThreshold)
if (IsBelowRepairThreshold)
{
powerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
}
@@ -306,9 +306,13 @@ namespace Barotrauma.Items.Components
forceDir.X = Math.Clamp(forceDir.X, -0.1f, 0.1f);
}
}
float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance)) : TargetPullForce;
float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance - 50)) : TargetPullForce;
targetBody?.ApplyForce(-forceDir * force);
targetCharacter?.AnimController.Collider.ApplyForce(-forceDir * force * 3);
var targetRagdoll = targetCharacter?.AnimController;
if (targetRagdoll != null && (targetRagdoll.InWater || targetRagdoll.OnGround))
{
targetRagdoll.Collider.ApplyForce(-forceDir * force * 3);
}
}
}
}
@@ -19,6 +19,10 @@ namespace Barotrauma.Items.Components
get { return timeFrame; }
set
{
if (value > timeFrame)
{
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
}
timeFrame = Math.Max(0.0f, value);
}
}
@@ -39,6 +39,10 @@ namespace Barotrauma.Items.Components
get { return timeFrame; }
set
{
if (value > timeFrame)
{
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
}
timeFrame = Math.Max(0.0f, value);
}
}
@@ -36,9 +36,9 @@ namespace Barotrauma.Items.Components
if (UseHSV)
{
Color hsvColor = ToolBox.HSVToRGB(signalR, signalG, signalB);
signalR = hsvColor.R / (float) byte.MaxValue;
signalG = hsvColor.G / (float) byte.MaxValue;
signalB = hsvColor.B / (float) byte.MaxValue;
signalR = hsvColor.R;
signalG = hsvColor.G;
signalB = hsvColor.B;
}
output = signalR.ToString("G", CultureInfo.InvariantCulture);
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
class DelayComponent : ItemComponent
@@ -114,6 +114,18 @@ namespace Barotrauma.Items.Components
};
signalQueue.Enqueue(prevQueuedSignal);
break;
case "set_delay":
if (float.TryParse(signal.value, out float newDelay))
{
newDelay = MathHelper.Clamp(newDelay, 0, 60);
if (signalQueue.Count > 0 && newDelay != Delay)
{
prevQueuedSignal = null;
signalQueue.Clear();
}
Delay = newDelay;
}
break;
}
}
}
@@ -62,6 +62,10 @@ namespace Barotrauma.Items.Components
get { return timeFrame; }
set
{
if (value > timeFrame)
{
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
}
timeFrame = Math.Max(0.0f, value);
}
}
@@ -36,9 +36,17 @@ namespace Barotrauma.Items.Components
{
case FunctionType.Round:
value = MathF.Round(value);
if (value == -0)
{
value = 0;
}
break;
case FunctionType.Ceil:
value = MathF.Ceiling(value);
if (value == -0)
{
value = 0;
}
break;
case FunctionType.Floor:
value = MathF.Floor(value);
@@ -43,7 +43,16 @@ namespace Barotrauma.Items.Components
}
}
public float Rotation;
private float rotation;
public float Rotation
{
get { return rotation; }
set
{
rotation = value;
SetLightSourceTransform();
}
}
[Editable, Serialize(true, true, description: "Should structures cast shadows when light from this light source hits them. " +
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range.", alwaysUseInstanceValues: true)]
@@ -246,39 +255,14 @@ namespace Barotrauma.Items.Components
SetLightSourceState(false, 0.0f);
return;
}
#if CLIENT
if (ParentBody != null)
{
Light.Position = ParentBody.Position;
}
else if (turret != null)
{
Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
}
else
{
Light.Position = item.Position;
}
#endif
SetLightSourceTransform();
PhysicsBody body = ParentBody ?? item.body;
if (body != null)
if (body != null && !body.Enabled)
{
#if CLIENT
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)
{
SetLightSourceState(false, 0.0f);
return;
}
}
else
{
#if CLIENT
Light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
Light.LightSpriteEffect = item.SpriteEffects;
#endif
SetLightSourceState(false, 0.0f);
return;
}
currPowerConsumption = powerConsumption;
@@ -333,6 +317,9 @@ namespace Barotrauma.Items.Components
if (signal.value != prevColorSignal)
{
LightColor = XMLExtensions.ParseColor(signal.value, false);
#if CLIENT
SetLightSourceState(Light.Enabled, currentBrightness);
#endif
prevColorSignal = signal.value;
}
break;
@@ -350,5 +337,8 @@ namespace Barotrauma.Items.Components
}
partial void SetLightSourceState(bool enabled, float brightness);
partial void SetLightSourceTransform();
}
}
@@ -34,7 +34,12 @@ namespace Barotrauma.Items.Components
}
}
protected bool writeable = true;
[Editable, Serialize(true, true, description: "Can the value stored in the memory component be changed via signals.", alwaysUseInstanceValues: true)]
public bool Writeable
{
get;
set;
}
public MemoryComponent(Item item, XElement element)
: base(item, element)
@@ -54,7 +59,7 @@ namespace Barotrauma.Items.Components
switch (connection.Name)
{
case "signal_in":
if (writeable)
if (Writeable)
{
string prevValue = Value;
Value = signal.value;
@@ -66,7 +71,7 @@ namespace Barotrauma.Items.Components
break;
case "signal_store":
case "lock_state":
writeable = signal.value == "1";
Writeable = signal.value == "1";
break;
}
}
@@ -131,6 +131,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, true, description: "Should the sensor trigger when the item itself moves.")]
public bool DetectOwnMotion
{
get;
set;
}
public MotionSensor(Item item, XElement element)
: base(item, element)
{
@@ -168,7 +175,7 @@ namespace Barotrauma.Items.Components
MotionDetected = false;
updateTimer = UpdateInterval;
if (item.body != null && item.body.Enabled)
if (item.body != null && item.body.Enabled && DetectOwnMotion)
{
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
{
@@ -6,6 +6,8 @@ namespace Barotrauma.Items.Components
{
class RegExFindComponent : ItemComponent
{
private static readonly TimeSpan timeout = TimeSpan.FromSeconds(Timing.Step);
private string expression;
private string receivedSignal;
@@ -67,7 +69,10 @@ namespace Barotrauma.Items.Components
try
{
regex = new Regex(@expression);
regex = new Regex(
@expression,
options: RegexOptions.None,
matchTimeout: timeout);
}
catch
@@ -97,11 +102,14 @@ namespace Barotrauma.Items.Components
previousResult = match.Success;
previousGroups = UseCaptureGroup && previousResult ? match.Groups : null;
previousReceivedSignal = receivedSignal;
}
catch
catch (Exception e)
{
item.SendSignal("ERROR", "signal_out");
item.SendSignal(
e is RegexMatchTimeoutException
? "TIMEOUT"
: "ERROR",
"signal_out");
previousResult = false;
return;
}
@@ -24,6 +24,10 @@ namespace Barotrauma.Items.Components
get { return timeFrame; }
set
{
if (value > timeFrame)
{
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
}
timeFrame = Math.Max(0.0f, value);
}
}
@@ -2,16 +2,35 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
readonly struct TerminalMessage
{
public readonly string Text;
public readonly Color Color;
public TerminalMessage(string text, Color color)
{
Text = text;
Color = color;
}
public void Deconstruct(out string text, out Color color)
{
text = Text;
color = Color;
}
}
partial class Terminal : ItemComponent
{
private const int MaxMessageLength = ChatMessage.MaxLength;
private const int MaxMessages = 60;
private List<string> messageHistory = new List<string>(MaxMessages);
private List<TerminalMessage> messageHistory = new List<TerminalMessage>(MaxMessages);
public string DisplayedWelcomeMessage
{
@@ -37,19 +56,39 @@ namespace Barotrauma.Items.Components
/// </summary>
public string ShowMessage
{
get { return messageHistory.Count == 0 ? string.Empty : messageHistory.Last(); }
get { return messageHistory.Count == 0 ? string.Empty : messageHistory.Last().Text; }
set
{
if (string.IsNullOrEmpty(value)) { return; }
ShowOnDisplay(value, addToHistory: true);
ShowOnDisplay(value, addToHistory: true, TextColor);
}
}
[Editable, Serialize(false, true, description: "The terminal will use a monospace font if this box is ticked.", alwaysUseInstanceValues: true)]
public bool UseMonospaceFont { get; set; }
private Color textColor = Color.LimeGreen;
[Editable, Serialize("50,205,50,255", true, description: "Color of the terminal text.", alwaysUseInstanceValues: true)]
public Color TextColor
{
get => textColor;
set
{
textColor = value;
#if CLIENT
if (inputBox is { } input)
{
input.TextColor = value;
}
#endif
}
}
private string OutputValue { get; set; }
private string prevColorSignal;
public Terminal(Item item, XElement element)
: base(item, element)
{
@@ -59,18 +98,41 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element);
partial void ShowOnDisplay(string input, bool addToHistory);
partial void ShowOnDisplay(string input, bool addToHistory, Color color);
public override void ReceiveSignal(Signal signal, Connection connection)
{
if (connection.Name != "signal_in") { return; }
if (signal.value.Length > MaxMessageLength)
switch (connection.Name)
{
signal.value = signal.value.Substring(0, MaxMessageLength);
}
case "set_text":
case "signal_in":
if (signal.value.Length > MaxMessageLength)
{
signal.value = signal.value.Substring(0, MaxMessageLength);
}
string inputSignal = signal.value.Replace("\\n", "\n");
ShowOnDisplay(inputSignal, addToHistory: true);
string inputSignal = signal.value.Replace("\\n", "\n");
ShowOnDisplay(inputSignal, addToHistory: true, TextColor);
break;
case "set_text_color":
if (signal.value != prevColorSignal)
{
TextColor = XMLExtensions.ParseColor(signal.value, false);
prevColorSignal = signal.value;
}
break;
case "clear_text" when signal.value != "0":
messageHistory.Clear();
#if CLIENT
if (historyBox?.Content is { } history)
{
history.ClearChildren();
}
CreateFillerBlock();
#endif
break;
}
}
public override void OnItemLoaded()
@@ -83,7 +145,7 @@ namespace Barotrauma.Items.Components
base.OnItemLoaded();
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
{
ShowOnDisplay(DisplayedWelcomeMessage, addToHistory: !isSubEditor);
ShowOnDisplay(DisplayedWelcomeMessage, addToHistory: !isSubEditor, TextColor);
DisplayedWelcomeMessage = "";
//remove welcome message if a game session is running so it doesn't reappear on successive rounds
if (GameMain.GameSession != null && !isSubEditor)
@@ -98,7 +160,8 @@ namespace Barotrauma.Items.Components
var componentElement = base.Save(parentElement);
for (int i = 0; i < messageHistory.Count; i++)
{
componentElement.Add(new XAttribute("msg" + i, messageHistory[i]));
componentElement.Add(new XAttribute("msg" + i, messageHistory[i].Text));
componentElement.Add(new XAttribute("color" + i, messageHistory[i].Color.ToStringHex()));
}
return componentElement;
}
@@ -109,8 +172,9 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < MaxMessages; i++)
{
string msg = componentElement.GetAttributeString("msg" + i, null);
if (msg == null) { break; }
ShowOnDisplay(msg, addToHistory: true);
if (msg is null) { break; }
Color color = componentElement.GetAttributeColor("color" + i, TextColor);
ShowOnDisplay(msg, addToHistory: true, color);
}
}
}
@@ -17,8 +17,8 @@ namespace Barotrauma.Items.Components
Atan,
}
private float[] receivedSignal = new float[2];
private float[] timeSinceReceived = new float[2];
private readonly float[] receivedSignal = new float[2];
private readonly float[] timeSinceReceived = new float[2];
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.", alwaysUseInstanceValues: true)]
public FunctionType Function
@@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components
//item in water -> we definitely want to send the True output
isInWater = true;
}
else if (item.CurrentHull != null && item.CurrentHull.WaterPercentage > 0.0f)
else if (item.CurrentHull != null && item.CurrentHull.WaterPercentage > 0.0f && item.CurrentHull.WaterVolume > 1.0f)
{
//(center of the) item in not water -> check if the water surface is below the bottom of the item's rect
if (item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
@@ -100,7 +100,12 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull != null)
{
int waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100);
int waterPercentage = 0;
//ignore minuscule amounts of water
if (item.CurrentHull.WaterVolume > 1.0f)
{
waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100);
}
item.SendSignal(waterPercentage.ToString(), "water_%");
}
string highPressureOut = (item.CurrentHull == null || item.CurrentHull.LethalPressure > 5.0f) ? "1" : "0";
@@ -128,6 +128,9 @@ namespace Barotrauma.Items.Components
return HasRequiredContainedItems(user: null, addMessage: false);
}
/// <summary>
/// Returns the wifi components that can receive signals from this one
/// </summary>
public IEnumerable<WifiComponent> GetReceiversInRange()
{
return list.Where(w => w != this && w.CanReceive(this));
@@ -136,10 +139,16 @@ namespace Barotrauma.Items.Components
public bool CanReceive(WifiComponent sender)
{
if (sender == null || sender.channel != channel) { return false; }
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication) { return false; }
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication)
//if the component is not linked to chat and has nothing connected to the output, sending a signal to it does nothing
// = no point in receiving
if (!LinkToChat)
{
return false;
if (signalOutConnection == null || !signalOutConnection.Wires.Any(w => w != null))
{
return false;
}
}
if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
@@ -147,6 +156,21 @@ namespace Barotrauma.Items.Components
return HasRequiredContainedItems(user: null, addMessage: false);
}
/// <summary>
/// Returns the wifi components that can transmit signals to this one
/// </summary>
public IEnumerable<WifiComponent> GetTransmittersInRange()
{
return list.Where(w => w != this && w.CanTransmit(this));
}
public bool CanTransmit(WifiComponent sender)
{
if (sender == null || sender.channel != channel) { return false; }
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication) { return false; }
if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
return HasRequiredContainedItems(user: null, addMessage: false);
}
public override void Update(float deltaTime, Camera cam)
{
chatMsgCooldown -= deltaTime;
@@ -756,14 +756,17 @@ namespace Barotrauma.Items.Components
{
if (item.ParentInventory != null) { return; }
#if CLIENT
if (!relativeToSub && Screen.Selected != GameMain.SubEditorScreen) { return; }
if (!relativeToSub)
{
if (Screen.Selected != GameMain.SubEditorScreen || (item.Submarine?.Loading ?? false)) { return; }
}
#else
if (!relativeToSub) { return; }
#endif
Vector2 refPos = item.Submarine == null ?
Vector2.Zero :
item.Position - item.Submarine.HiddenSubPosition;
item.Position - item.Submarine.HiddenSubPosition;
for (int i = 0; i < nodes.Count; i++)
{
@@ -9,7 +9,7 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class TriggerComponent : ItemComponent
partial class TriggerComponent : ItemComponent
{
[Editable, Serialize(0.0f, true, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
public float Force { get; set; }
@@ -18,12 +18,32 @@ namespace Barotrauma.Items.Components
private float Radius { get; set; }
private float RadiusInDisplayUnits { get; set; }
private bool TriggeredOnce { get; set; }
private float CurrentForceFluctuation { get; set; } = 1.0f;
public bool TriggerActive { get; private set; }
private float ForceFluctuationTimer { get; set; }
private static float TimeInLevel
{
get
{
if (GameMain.GameSession != null)
{
return (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime);
}
else
{
return 0.0f;
}
}
}
private readonly LevelTrigger.TriggererType triggeredBy;
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
private readonly bool triggerOnce;
private readonly bool distanceBasedForce;
private readonly bool forceFluctuation;
private readonly float forceFluctuationStrength;
private readonly float forceFluctuationFrequency;
private readonly float forceFluctuationInterval;
private readonly List<ISerializableEntity> statusEffectTargets = new List<ISerializableEntity>();
/// <summary>
/// Effects applied to entities inside the trigger
@@ -42,6 +62,15 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type.");
}
triggerOnce = element.GetAttributeBool("triggeronce", false);
distanceBasedForce = element.GetAttributeBool("distancebasedforce", false);
forceFluctuation = element.GetAttributeBool("forcefluctuation", false);
forceFluctuationStrength = element.GetAttributeFloat("forcefluctuationstrength", 1.0f);
forceFluctuationStrength = Math.Clamp(forceFluctuationStrength, 0.0f, 1.0f);
forceFluctuationFrequency = element.GetAttributeFloat("fluctuationfrequency", 1.0f);
forceFluctuationFrequency = Math.Max(forceFluctuationFrequency, 0.01f);
forceFluctuationInterval = element.GetAttributeFloat("fluctuationinterval", 0.01f);
forceFluctuationInterval = Math.Max(forceFluctuationInterval, 0.01f);
string parentDebugName = $"TriggerComponent in {item.Name}";
foreach (XElement subElement in element.Elements())
{
@@ -128,6 +157,19 @@ namespace Barotrauma.Items.Components
TriggerActive = triggerers.Any();
if (forceFluctuation && TriggerActive && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
ForceFluctuationTimer += deltaTime;
if (ForceFluctuationTimer >= forceFluctuationInterval)
{
float v = MathF.Sin(2 * MathF.PI * forceFluctuationFrequency * TimeInLevel);
float amount = MathUtils.InverseLerp(-1.0f, 1.0f, v);
CurrentForceFluctuation = MathHelper.Lerp(1.0f - forceFluctuationStrength, 1.0f, amount);
ForceFluctuationTimer = 0.0f;
GameMain.NetworkMember?.CreateEntityEvent(this);
}
}
foreach (Entity triggerer in triggerers)
{
LevelTrigger.ApplyStatusEffects(statusEffects, item.WorldPosition, triggerer, deltaTime, statusEffectTargets);
@@ -167,9 +209,9 @@ namespace Barotrauma.Items.Components
{
Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition);
if (diff.LengthSquared() < 0.0001f) { return; }
float distanceFactor = LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits);
float distanceFactor = distanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
if (distanceFactor <= 0.0f) { return; }
Vector2 force = distanceFactor * Force * Vector2.Normalize(diff);
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff);
if (force.LengthSquared() < 0.01f) { return; }
body.ApplyForce(force);
}
@@ -64,10 +64,15 @@ namespace Barotrauma.Items.Components
private Character currentTarget;
const float aiFindTargetInterval = 5.0f;
private int currentLoaderIndex;
private const float TinkeringPowerCostReduction = 0.2f;
private const float TinkeringDamageIncrease = 0.2f;
private const float TinkeringReloadDecrease = 0.2f;
public Character ActiveUser;
private float resetActiveUserTimer;
public float Rotation
{
get { return rotation; }
@@ -362,7 +367,7 @@ namespace Barotrauma.Items.Components
UpdateTransformedBarrelPos();
}
if (user != null && user.Removed)
if (user is { Removed: true })
{
user = null;
}
@@ -371,6 +376,19 @@ namespace Barotrauma.Items.Components
resetUserTimer -= deltaTime;
if (resetUserTimer <= 0.0f) { user = null; }
}
if (ActiveUser is { Removed: true })
{
ActiveUser = null;
}
else
{
resetActiveUserTimer -= deltaTime;
if (resetActiveUserTimer <= 0.0f)
{
ActiveUser = null;
}
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
@@ -576,16 +594,17 @@ namespace Barotrauma.Items.Components
}
else
{
foreach (MapEntity e in item.linkedTo)
for (int j = 0; j < item.linkedTo.Count; j++)
{
var e = item.linkedTo[(j + currentLoaderIndex) % item.linkedTo.Count];
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) { continue; }
if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; }
if (linkedItem.Condition <= 0.0f)
if (linkedItem.Condition <= 0.0f)
{
loaderBroken = true;
continue;
continue;
}
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null)
@@ -594,7 +613,6 @@ namespace Barotrauma.Items.Components
projectiles = GetLoadedProjectiles();
if (projectiles.Any()) { break; }
}
}
}
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
@@ -689,6 +707,10 @@ namespace Barotrauma.Items.Components
{
ShiftItemsInProjectileContainer(container.GetComponent<ItemContainer>());
}
if (item.linkedTo.Count > 0)
{
currentLoaderIndex = (currentLoaderIndex + 1) % item.linkedTo.Count;
}
}
}
@@ -743,9 +765,12 @@ namespace Barotrauma.Items.Components
Projectile projectileComponent = projectile.GetComponent<Projectile>();
if (projectileComponent != null)
{
projectileComponent.Launcher = item;
projectileComponent.Attacker = projectileComponent.User = user;
projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength);
if (projectileComponent.Attack != null)
{
projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength);
}
projectileComponent.Use();
projectile.GetComponent<Rope>()?.Attach(item, projectile);
projectileComponent.User = user;
@@ -956,10 +981,11 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
previousTarget.IsDead)
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead)
{
character.Speak(TextManager.Get("DialogTurretTargetDead"), identifier: "killedtarget" + previousTarget.ID, minDurationBetweenSimilar: 10.0f);
character.Speak(TextManager.Get("DialogTurretTargetDead"),
identifier: "killedtarget" + previousTarget.ID,
minDurationBetweenSimilar: 10.0f);
character.AIController.SelectTarget(null);
}
@@ -986,7 +1012,9 @@ namespace Barotrauma.Items.Components
}
else
{
character.Speak(TextManager.Get("DialogSupercapacitorIsBroken"), identifier: "supercapacitorisbroken", minDurationBetweenSimilar: 30.0f);
character.Speak(TextManager.Get("DialogSupercapacitorIsBroken"),
identifier: "supercapacitorisbroken",
minDurationBetweenSimilar: 30.0f);
canShoot = false;
}
}
@@ -999,7 +1027,9 @@ namespace Barotrauma.Items.Components
}
if (lowestCharge <= 0 && batteryToLoad.Item.ConditionPercentage > 0)
{
character.Speak(TextManager.Get("DialogTurretHasNoPower"), identifier: "turrethasnopower", minDurationBetweenSimilar: 30.0f);
character.Speak(TextManager.Get("DialogTurretHasNoPower"),
identifier: "turrethasnopower",
minDurationBetweenSimilar: 30.0f);
canShoot = false;
}
}
@@ -1039,7 +1069,9 @@ namespace Barotrauma.Items.Components
{
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "cannotloadturret", minDurationBetweenSimilar: 30.0f);
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, formatCapitals: true),
identifier: "cannotloadturret",
minDurationBetweenSimilar: 30.0f);
}
return true;
}
@@ -1049,7 +1081,9 @@ namespace Barotrauma.Items.Components
loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "loadturret", minDurationBetweenSimilar: 30.0f);
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: true),
identifier: "loadturret",
minDurationBetweenSimilar: 30.0f);
}
loadItemsObjective.Abandoned += CheckRemainingAmmo;
loadItemsObjective.Completed += CheckRemainingAmmo;
@@ -1063,11 +1097,15 @@ namespace Barotrauma.Items.Components
int remainingAmmo = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(ammoType) && i.Condition > 1);
if (remainingAmmo == 0)
{
character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"), identifier: "outofammo", minDurationBetweenSimilar: 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}"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f);
character.Speak(TextManager.Get($"DialogLowOn{ammoType}"),
identifier: "outofammo",
minDurationBetweenSimilar: 30.0f);
}
}
}
@@ -1090,7 +1128,8 @@ namespace Barotrauma.Items.Components
float closestDistance = maxDistance * maxDistance;
if (currentTarget != null)
bool hadCurrentTarget = currentTarget != null;
if (hadCurrentTarget)
{
if (currentTarget.Removed || currentTarget.IsDead)
{
@@ -1131,7 +1170,7 @@ namespace Barotrauma.Items.Components
{
targetPos = closestEnemy.WorldPosition;
//if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is
if (closestEnemy.Submarine != null && closestEnemy.CurrentHull != null && closestEnemy.Submarine != item.Submarine)
if (closestEnemy.Submarine != null && closestEnemy.CurrentHull != null && closestEnemy.Submarine != item.Submarine && !closestEnemy.CanSeeTarget(Item))
{
targetPos = closestEnemy.CurrentHull.WorldPosition;
}
@@ -1222,24 +1261,32 @@ namespace Barotrauma.Items.Components
{
if (character.IsOnPlayerTeam)
{
if (character.AIController.SelectedAiTarget == null)
if (character.AIController.SelectedAiTarget == null && !hadCurrentTarget)
{
if (GameMain.Config.RecentlyEncounteredCreatures.Contains(closestEnemy.SpeciesName))
{
character.Speak(TextManager.Get("DialogNewTargetSpotted"), null, 0.0f, "newtargetspotted", 30.0f);
character.Speak(TextManager.Get("DialogNewTargetSpotted"),
identifier: "newtargetspotted",
minDurationBetweenSimilar: 30.0f);
}
else if (GameMain.Config.EncounteredCreatures.Any(name => name.Equals(closestEnemy.SpeciesName, StringComparison.OrdinalIgnoreCase)))
{
character.Speak(TextManager.GetWithVariable("DialogIdentifiedTargetSpotted", "[speciesname]", closestEnemy.DisplayName), null, 0.0f, "identifiedtargetspotted", 30.0f);
character.Speak(TextManager.GetWithVariable("DialogIdentifiedTargetSpotted", "[speciesname]", closestEnemy.DisplayName),
identifier: "identifiedtargetspotted",
minDurationBetweenSimilar: 30.0f);
}
else
{
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"), null, 0.0f, "unidentifiedtargetspotted", 5.0f);
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"),
identifier: "unidentifiedtargetspotted",
minDurationBetweenSimilar: 5.0f);
}
}
else if (GameMain.Config.EncounteredCreatures.None(name => name.Equals(closestEnemy.SpeciesName, StringComparison.OrdinalIgnoreCase)))
{
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"), null, 0.0f, "unidentifiedtargetspotted", 5.0f);
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"),
identifier: "unidentifiedtargetspotted",
minDurationBetweenSimilar: 5.0f);
}
character.AddEncounter(closestEnemy);
}
@@ -1247,7 +1294,9 @@ namespace Barotrauma.Items.Components
}
else if (closestEnemy == null && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogIceSpireSpotted"), null, 0.0f, "icespirespotted", 60.0f);
character.Speak(TextManager.Get("DialogIceSpireSpotted"),
identifier: "icespirespotted",
minDurationBetweenSimilar: 60.0f);
}
character.CursorPosition = targetPos.Value;
@@ -1289,7 +1338,9 @@ namespace Barotrauma.Items.Components
if (!shoot) { return false; }
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
character.Speak(TextManager.Get("DialogFireTurret"),
identifier: "fireturret",
minDurationBetweenSimilar: 30.0f);
}
character.SetInput(InputType.Shoot, true, true);
}
@@ -1389,6 +1440,7 @@ namespace Barotrauma.Items.Components
crosshairSprite?.Remove(); crosshairSprite = null;
crosshairPointerSprite?.Remove(); crosshairPointerSprite = null;
moveSoundChannel?.Dispose(); moveSoundChannel = null;
WeaponIndicatorSprite?.Remove(); WeaponIndicatorSprite = null;
#endif
}
@@ -1397,8 +1449,9 @@ namespace Barotrauma.Items.Components
List<Projectile> projectiles = new List<Projectile>();
// check the item itself first
CheckProjectileContainer(item, projectiles, out bool _);
foreach (MapEntity e in item.linkedTo)
for (int j = 0; j < item.linkedTo.Count; j++)
{
var e = item.linkedTo[(j + currentLoaderIndex) % item.linkedTo.Count];
if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; }
if (e is Item projectileContainer)
{
@@ -1406,7 +1459,6 @@ namespace Barotrauma.Items.Components
if (projectiles.Any() || stopSearching) { return projectiles; }
}
}
return projectiles;
}
@@ -1503,12 +1555,16 @@ namespace Barotrauma.Items.Components
IsActive = true;
}
user = sender;
ActiveUser = sender;
resetActiveUserTimer = 1f;
resetUserTimer = 10.0f;
break;
case "trigger_in":
if (signal.value == "0") { return; }
item.Use((float)Timing.Step, sender);
user = sender;
ActiveUser = sender;
resetActiveUserTimer = 1f;
resetUserTimer = 10.0f;
//triggering the Use method through item.Use will fail if the item is not characterusable and the signal was sent by a character
//so lets do it manually
@@ -1521,12 +1577,18 @@ namespace Barotrauma.Items.Components
if (lightComponent != null && signal.value != "0")
{
lightComponent.IsOn = !lightComponent.IsOn;
UpdateLightComponent();
}
break;
case "set_light":
if (lightComponent != null)
{
lightComponent.IsOn = signal.value != "0";
bool shouldBeOn = signal.value != "0";
if (shouldBeOn != lightComponent.IsOn)
{
lightComponent.IsOn = shouldBeOn;
UpdateLightComponent();
}
}
break;
}