722 lines
29 KiB
C#
722 lines
29 KiB
C#
using Barotrauma.Abilities;
|
|
using Barotrauma.Extensions;
|
|
using Barotrauma.Networking;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable
|
|
{
|
|
private readonly LocalizedString header;
|
|
|
|
private float deteriorationTimer;
|
|
private float deteriorateAlwaysResetTimer;
|
|
|
|
private int updateDeteriorationCounter;
|
|
private const int UpdateDeteriorationInterval = 10;
|
|
|
|
private int prevSentConditionValue;
|
|
private string conditionSignal;
|
|
|
|
bool wasBroken;
|
|
bool wasGoodCondition;
|
|
|
|
public float LastActiveTime;
|
|
|
|
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the condition of the item deteriorates per second."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2)]
|
|
public float DeteriorationSpeed
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum initial delay before the item starts to deteriorate."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
|
|
public float MinDeteriorationDelay
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Maximum initial delay before the item starts to deteriorate."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
|
|
public float MaxDeteriorationDelay
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(50.0f, IsPropertySaveable.Yes, description: "The item won't deteriorate spontaneously if the condition is below this value. For example, if set to 10, the condition will spontaneously drop to 10 and then stop dropping (unless the item is damaged further by external factors). Percentages of max condition."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
|
public float MinDeteriorationCondition
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(0f, IsPropertySaveable.Yes, description: "How low a traitor must get the item's condition for it to start breaking down.")]
|
|
public float MinSabotageCondition
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(80.0f, IsPropertySaveable.Yes, description: "The condition of the item has to be below this for it to become repairable. Percentages of max condition."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
|
public float RepairThreshold
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(100.0f, IsPropertySaveable.Yes, description: "The amount of time it takes to fix the item with insufficient skill levels."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
|
public float FixDurationLowSkill
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(10.0f, IsPropertySaveable.Yes, description: "The amount of time it takes to fix the item with sufficient skill levels."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
|
public float FixDurationHighSkill
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(false, IsPropertySaveable.No, description: "If set to true, the deterioration timer will always run regardless if the item is being used or not.")]
|
|
public bool DeteriorateAlways
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private float skillRequirementMultiplier;
|
|
|
|
[Serialize(1.0f, IsPropertySaveable.Yes)]
|
|
public float SkillRequirementMultiplier
|
|
{
|
|
get { return skillRequirementMultiplier; }
|
|
set
|
|
{
|
|
var oldValue = skillRequirementMultiplier;
|
|
skillRequirementMultiplier = value;
|
|
#if CLIENT
|
|
if (!MathUtils.NearlyEqual(oldValue, skillRequirementMultiplier))
|
|
{
|
|
RecreateGUI();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private bool isTinkering;
|
|
public bool IsTinkering
|
|
{
|
|
get { return isTinkering; }
|
|
private set
|
|
{
|
|
if (isTinkering == value) { return; }
|
|
isTinkering = value;
|
|
|
|
if (tinkeringPowersDevices)
|
|
{
|
|
foreach (Powered powered in item.GetComponents<Powered>())
|
|
{
|
|
if (powered is PowerContainer) { continue; }
|
|
powered.PoweredByTinkering = isTinkering;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public Character CurrentFixer { get; private set; }
|
|
private Item currentRepairItem;
|
|
|
|
private float tinkeringDuration;
|
|
private float tinkeringStrength;
|
|
|
|
public float TinkeringStrength => tinkeringStrength;
|
|
|
|
private bool tinkeringPowersDevices;
|
|
public bool TinkeringPowersDevices => tinkeringPowersDevices;
|
|
|
|
public bool IsBelowRepairThreshold => item.ConditionPercentage <= RepairThreshold;
|
|
public bool IsBelowRepairIconThreshold => item.ConditionPercentage <= RepairThreshold / 2;
|
|
|
|
public enum FixActions : int
|
|
{
|
|
None = 0,
|
|
Repair = 1,
|
|
Sabotage = 2,
|
|
Tinker = 3,
|
|
}
|
|
|
|
private FixActions currentFixerAction = FixActions.None;
|
|
public FixActions CurrentFixerAction
|
|
{
|
|
get => currentFixerAction;
|
|
private set { currentFixerAction = value; }
|
|
}
|
|
|
|
public Repairable(Item item, ContentXElement element)
|
|
: base(item, element)
|
|
{
|
|
IsActive = true;
|
|
canBeSelected = true;
|
|
|
|
this.item = item;
|
|
header =
|
|
TextManager.Get(element.GetAttributeString("header", "")).Fallback(
|
|
TextManager.Get(item.Prefab.ConfigElement.GetAttributeString("header", ""))).Fallback(
|
|
element.GetAttributeString("name", ""));
|
|
|
|
//backwards compatibility
|
|
var repairThresholdAttribute =
|
|
element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase)) ??
|
|
element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairthreshold", StringComparison.OrdinalIgnoreCase));
|
|
if (repairThresholdAttribute != null)
|
|
{
|
|
if (float.TryParse(repairThresholdAttribute.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out float repairThreshold))
|
|
{
|
|
RepairThreshold = repairThreshold;
|
|
}
|
|
}
|
|
|
|
InitProjSpecific(element);
|
|
}
|
|
|
|
public override void OnItemLoaded()
|
|
{
|
|
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
|
|
}
|
|
|
|
partial void InitProjSpecific(ContentXElement element);
|
|
|
|
/// <summary>
|
|
/// Check if the character manages to succesfully repair the item
|
|
/// </summary>
|
|
public bool CheckCharacterSuccess(Character character, Item bestRepairItem)
|
|
{
|
|
if (character == null) { return false; }
|
|
|
|
if (statusEffectLists == null || statusEffectLists.None(s => s.Key == ActionType.OnFailure)) { return true; }
|
|
|
|
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 == "electrical"))
|
|
{
|
|
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; }
|
|
|
|
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
|
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
|
|
{
|
|
h.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override float GetSkillMultiplier()
|
|
{
|
|
return SkillRequirementMultiplier;
|
|
}
|
|
|
|
public float RepairDegreeOfSuccess(Character character, List<Skill> skills)
|
|
{
|
|
if (skills.Count == 0) { return 1.0f; }
|
|
if (character == null) { return 0.0f; }
|
|
|
|
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
|
|
float average = skillSum / skills.Count;
|
|
|
|
return ((average + 100.0f) / 2.0f) / 100.0f;
|
|
}
|
|
|
|
public void RepairBoost(bool qteSuccess)
|
|
{
|
|
if (CurrentFixer == null) { return; }
|
|
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 Item.ApplyStatusEffectEventData(ActionType.OnFailure, this, CurrentFixer));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public bool StartRepairing(Character character, FixActions action)
|
|
{
|
|
if (character == null || character.IsDead || action == FixActions.None)
|
|
{
|
|
DebugConsole.ThrowError("Invalid repair command!");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (CurrentFixerAction == FixActions.Tinker && action != FixActions.Tinker)
|
|
{
|
|
CurrentFixer?.CheckTalents(AbilityEffectType.OnStopTinkering);
|
|
}
|
|
|
|
Item bestRepairItem = GetBestRepairItem(character);
|
|
#if SERVER
|
|
if (CurrentFixer != character || currentFixerAction != action)
|
|
{
|
|
if (!CheckCharacterSuccess(character, bestRepairItem))
|
|
{
|
|
GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction);
|
|
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, this, character));
|
|
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
|
|
{
|
|
GameMain.Server?.CreateEntityEvent(bestRepairItem, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, h, character));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((character != prevLoggedFixer || action != prevLoggedFixAction) && (character.TeamID == CharacterTeamType.Team1 || character.TeamID == CharacterTeamType.Team2))
|
|
{
|
|
GameServer.Log($"{GameServer.CharacterLogName(character)} started {(action == FixActions.Sabotage ? "sabotaging" : "repairing")} {item.Name}", ServerLog.MessageType.ItemInteraction);
|
|
item.CreateServerEvent(this);
|
|
prevLoggedFixer = character;
|
|
prevLoggedFixAction = action;
|
|
}
|
|
}
|
|
#else
|
|
if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character, bestRepairItem)) { return false; }
|
|
#endif
|
|
CurrentFixer = character;
|
|
currentRepairItem = bestRepairItem;
|
|
CurrentFixerAction = action;
|
|
if (action == FixActions.Tinker)
|
|
{
|
|
tinkeringStrength = 1f + CurrentFixer.GetStatValue(StatTypes.TinkeringStrength);
|
|
tinkeringPowersDevices = CurrentFixer.HasAbilityFlag(AbilityFlags.TinkeringPowersDevices);
|
|
|
|
if (character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors) && item.GetComponent<Deconstructor>() != null || item.GetComponent<Fabricator>() != null)
|
|
{
|
|
// fabricators and deconstructors can be tinkered indefinitely (more or less)
|
|
tinkeringDuration = float.MaxValue;
|
|
}
|
|
else
|
|
{
|
|
tinkeringDuration = CurrentFixer.GetStatValue(StatTypes.TinkeringDuration);
|
|
}
|
|
}
|
|
return true;
|
|
|
|
static Item GetBestRepairItem(Character character)
|
|
{
|
|
return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public bool StopRepairing(Character character)
|
|
{
|
|
if (CurrentFixer == character)
|
|
{
|
|
#if SERVER
|
|
if (CurrentFixer != character || currentFixerAction != FixActions.None)
|
|
{
|
|
item.CreateServerEvent(this);
|
|
}
|
|
#endif
|
|
if (currentRepairItem != null)
|
|
{
|
|
foreach (var ic in currentRepairItem.GetComponents<ItemComponent>())
|
|
{
|
|
ic.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, character);
|
|
}
|
|
}
|
|
if (CurrentFixerAction == FixActions.Tinker)
|
|
{
|
|
CurrentFixer.CheckTalents(AbilityEffectType.OnStopTinkering);
|
|
}
|
|
CurrentFixer.AnimController.StopUsingItem();
|
|
CurrentFixer = null;
|
|
currentRepairItem = null;
|
|
currentFixerAction = FixActions.None;
|
|
#if CLIENT
|
|
qteTimer = QteDuration;
|
|
qteCooldown = 0.0f;
|
|
repairSoundChannel?.FadeOutAndDispose();
|
|
repairSoundChannel = null;
|
|
#endif
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public override void UpdateBroken(float deltaTime, Camera cam)
|
|
{
|
|
Update(deltaTime, cam);
|
|
}
|
|
|
|
public void ResetDeterioration()
|
|
{
|
|
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
|
|
item.Condition = item.MaxCondition;
|
|
#if SERVER
|
|
//let the clients know the deterioration delay
|
|
item.CreateServerEvent(this);
|
|
#endif
|
|
}
|
|
|
|
public override void Update(float deltaTime, Camera cam)
|
|
{
|
|
UpdateProjSpecific(deltaTime);
|
|
IsTinkering = false;
|
|
|
|
if (prevSentConditionValue != (int)item.ConditionPercentage || conditionSignal == null)
|
|
{
|
|
prevSentConditionValue = (int)item.ConditionPercentage;
|
|
conditionSignal = prevSentConditionValue.ToString();
|
|
}
|
|
|
|
item.SendSignal(conditionSignal, "condition_out");
|
|
|
|
if (CurrentFixer == null)
|
|
{
|
|
if (deteriorateAlwaysResetTimer > 0.0f)
|
|
{
|
|
deteriorateAlwaysResetTimer -= deltaTime;
|
|
if (deteriorateAlwaysResetTimer <= 0.0f)
|
|
{
|
|
DeteriorateAlways = false;
|
|
#if SERVER
|
|
//let the clients know the deterioration delay
|
|
item.CreateServerEvent(this);
|
|
#endif
|
|
}
|
|
}
|
|
updateDeteriorationCounter++;
|
|
if (updateDeteriorationCounter >= UpdateDeteriorationInterval)
|
|
{
|
|
UpdateDeterioration(deltaTime * UpdateDeteriorationInterval);
|
|
updateDeteriorationCounter = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
UpdateFixAnimation(CurrentFixer);
|
|
|
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
|
|
|
if (CurrentFixer != null && (CurrentFixer.SelectedItem != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
|
|
{
|
|
StopRepairing(CurrentFixer);
|
|
return;
|
|
}
|
|
|
|
if (currentFixerAction == FixActions.Tinker)
|
|
{
|
|
tinkeringDuration -= deltaTime;
|
|
// not great to interject it here, should be less reliant on returning
|
|
|
|
float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.Prefab.Health) * 100f;
|
|
item.Condition -= conditionDecrease;
|
|
|
|
if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f)
|
|
{
|
|
StopRepairing(CurrentFixer);
|
|
}
|
|
else
|
|
{
|
|
IsTinkering = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
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 (IsBelowRepairThreshold)
|
|
{
|
|
wasBroken = true;
|
|
}
|
|
if (item.ConditionPercentage > MinSabotageCondition)
|
|
{
|
|
wasGoodCondition = true;
|
|
}
|
|
|
|
float talentMultiplier = CurrentFixer.GetStatValue(StatTypes.RepairSpeed);
|
|
if (requiredSkills.Any(static skill => skill.Identifier == "mechanical"))
|
|
{
|
|
talentMultiplier += CurrentFixer.GetStatValue(StatTypes.MechanicalRepairSpeed);
|
|
}
|
|
|
|
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
|
|
fixDuration /= 1 + talentMultiplier + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
|
|
fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed);
|
|
|
|
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
|
|
|
|
if (currentFixerAction == FixActions.Repair)
|
|
{
|
|
if (fixDuration <= 0.0f)
|
|
{
|
|
item.Condition = item.MaxCondition;
|
|
}
|
|
else
|
|
{
|
|
// scale with prefab's health instead of real health to ensure repair speed remains static with upgrades
|
|
float conditionIncrease = deltaTime / (fixDuration / item.Prefab.Health);
|
|
item.Condition += conditionIncrease;
|
|
#if SERVER
|
|
GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease);
|
|
#endif
|
|
}
|
|
|
|
if (item.IsFullCondition)
|
|
{
|
|
if (wasBroken)
|
|
{
|
|
foreach (Skill skill in requiredSkills)
|
|
{
|
|
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
|
|
CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier,
|
|
SkillSettings.Current.SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f));
|
|
}
|
|
SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
|
|
CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete, new AbilityRepairable(item));
|
|
}
|
|
if (CurrentFixer?.SelectedItem == item) { CurrentFixer.SelectedItem = null; }
|
|
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
|
|
wasBroken = false;
|
|
StopRepairing(CurrentFixer);
|
|
#if SERVER
|
|
prevLoggedFixer = null;
|
|
prevLoggedFixAction = FixActions.None;
|
|
#endif
|
|
}
|
|
}
|
|
else if (currentFixerAction == FixActions.Sabotage)
|
|
{
|
|
if (fixDuration <= 0.0f)
|
|
{
|
|
item.Condition = item.MaxCondition * (MinSabotageCondition / 100);
|
|
}
|
|
else
|
|
{
|
|
// scale with prefab's health instead of real health to ensure sabotage speed remains static with (any) upgrades
|
|
float conditionDecrease = deltaTime / (fixDuration / item.Prefab.Health);
|
|
item.Condition -= conditionDecrease;
|
|
}
|
|
|
|
if (item.ConditionPercentage <= MinSabotageCondition)
|
|
{
|
|
if (wasGoodCondition)
|
|
{
|
|
foreach (Skill skill in requiredSkills)
|
|
{
|
|
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
|
|
CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier,
|
|
SkillSettings.Current.SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f));
|
|
}
|
|
|
|
deteriorationTimer = 0.0f;
|
|
deteriorateAlwaysResetTimer = item.Condition / DeteriorationSpeed;
|
|
DeteriorateAlways = true;
|
|
item.Condition = item.MaxCondition * (MinSabotageCondition / 100);
|
|
wasGoodCondition = false;
|
|
}
|
|
StopRepairing(CurrentFixer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new NotImplementedException(currentFixerAction.ToString());
|
|
}
|
|
}
|
|
|
|
private void UpdateDeterioration(float deltaTime)
|
|
{
|
|
if (item.Condition <= 0.0f) { return; }
|
|
if (!ShouldDeteriorate()) { return; }
|
|
|
|
if (deteriorationTimer > 0.0f)
|
|
{
|
|
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
|
{
|
|
deteriorationTimer -= deltaTime * GetDeteriorationDelayMultiplier();
|
|
#if SERVER
|
|
if (deteriorationTimer <= 0.0f) { item.CreateServerEvent(this); }
|
|
#endif
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (item.ConditionPercentage > MinDeteriorationCondition)
|
|
{
|
|
float deteriorationSpeed = item.StatManager.GetAdjustedValue(ItemTalentStats.DetoriationSpeed, DeteriorationSpeed);
|
|
item.Condition -= deteriorationSpeed * deltaTime;
|
|
}
|
|
}
|
|
|
|
private float GetMaxRepairConditionMultiplier(Character character)
|
|
{
|
|
if (character == null) { return 1.0f; }
|
|
// kind of rough to keep this in update, but seems most robust
|
|
if (requiredSkills.Any(s => s != null && s.Identifier == "mechanical"))
|
|
{
|
|
return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierMechanical);
|
|
}
|
|
if (requiredSkills.Any(s => s != null && s.Identifier == "electrical"))
|
|
{
|
|
return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierElectrical);
|
|
}
|
|
return 1.0f;
|
|
}
|
|
|
|
private bool IsTinkerable(Character character)
|
|
{
|
|
if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; }
|
|
if (item.GetComponent<Engine>() != null) { return true; }
|
|
if (item.GetComponent<Pump>() != null) { return true; }
|
|
if (item.HasTag("turretammosource")) { return true; }
|
|
if (!character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors)) { return false; }
|
|
if (item.GetComponent<Fabricator>() != null) { return true; }
|
|
if (item.GetComponent<Deconstructor>() != null) { return true; }
|
|
return false;
|
|
}
|
|
|
|
private Affliction GetTinkerExhaustion(Character character)
|
|
{
|
|
return character.CharacterHealth.GetAffliction("tinkerexhaustion");
|
|
}
|
|
|
|
private bool CanTinker(Character character)
|
|
{
|
|
if (!IsTinkerable(character)) { return false; }
|
|
if (GetTinkerExhaustion(character) is Affliction tinkerExhaustion && tinkerExhaustion.Strength <= tinkerExhaustion.Prefab.MaxStrength) { return false; }
|
|
return true;
|
|
}
|
|
|
|
partial void UpdateProjSpecific(float deltaTime);
|
|
|
|
public void AdjustPowerConsumption(ref float powerConsumption)
|
|
{
|
|
if (IsBelowRepairThreshold)
|
|
{
|
|
powerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
|
|
}
|
|
}
|
|
|
|
private bool ShouldDeteriorate()
|
|
{
|
|
if (Level.IsLoadedFriendlyOutpost) { return false; }
|
|
#if CLIENT
|
|
if (GameMain.GameSession?.GameMode is TutorialMode) { return false; }
|
|
#endif
|
|
|
|
if (LastActiveTime > Timing.TotalTime) { return true; }
|
|
foreach (ItemComponent ic in item.Components)
|
|
{
|
|
if (ic is Fabricator || ic is Deconstructor)
|
|
{
|
|
//fabricators and deconstructors rely on LastActiveTime
|
|
return false;
|
|
}
|
|
else if (ic is PowerTransfer pt)
|
|
{
|
|
//power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power
|
|
if (pt.Voltage > 0.1f) { return true; }
|
|
}
|
|
else if (ic is PowerContainer pc)
|
|
{
|
|
//batteries don't deteriorate if they're not charging/discharging
|
|
if (Math.Abs(pc.CurrPowerConsumption) > 0.1f || Math.Abs(pc.CurrPowerOutput) > 0.1f) { return true; }
|
|
}
|
|
else if (ic is Engine engine)
|
|
{
|
|
//engines don't deteriorate if they're not running
|
|
if (Math.Abs(engine.Force) > 1.0f) { return true; }
|
|
}
|
|
else if (ic is Pump pump)
|
|
{
|
|
//pumps don't deteriorate if they're not running
|
|
if (Math.Abs(pump.FlowPercentage) > 1.0f && pump.IsActive && pump.HasPower) { return true; }
|
|
}
|
|
else if (ic is Reactor reactor)
|
|
{
|
|
//reactors don't deteriorate if they're not powered up
|
|
if (reactor.Temperature > 0.1f) { return true; }
|
|
}
|
|
else if (ic is OxygenGenerator oxyGenerator)
|
|
{
|
|
//oxygen generators don't deteriorate if they're not running
|
|
if (oxyGenerator.CurrFlow > 0.1f) { return true; }
|
|
}
|
|
else if (ic is Powered powered && !(powered is LightComponent))
|
|
{
|
|
if (powered.Voltage >= powered.MinVoltage) { return true; }
|
|
}
|
|
}
|
|
|
|
return DeteriorateAlways;
|
|
}
|
|
|
|
private float GetDeteriorationDelayMultiplier()
|
|
{
|
|
foreach (ItemComponent ic in item.Components)
|
|
{
|
|
if (ic is Engine engine)
|
|
{
|
|
return Math.Abs(engine.Force) / 100.0f;
|
|
}
|
|
else if (ic is Pump pump)
|
|
{
|
|
return Math.Abs(pump.FlowPercentage) / 100.0f;
|
|
}
|
|
else if (ic is Reactor reactor)
|
|
{
|
|
return (reactor.FissionRate + reactor.TurbineOutput) / 200.0f;
|
|
}
|
|
}
|
|
return 1.0f;
|
|
}
|
|
|
|
private void UpdateFixAnimation(Character character)
|
|
{
|
|
if (character == null || character.IsDead || character.IsIncapacitated) { return; }
|
|
character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f));
|
|
}
|
|
|
|
public override void ReceiveSignal(Signal signal, Connection connection)
|
|
{
|
|
//do nothing
|
|
//Repairables should always stay active, so we don't want to use the default behavior
|
|
//where set_active/set_state signals can disable the component
|
|
}
|
|
}
|
|
|
|
internal sealed class AbilityRepairable : AbilityObject, IAbilityItem
|
|
{
|
|
public Item Item { get; set; }
|
|
|
|
public AbilityRepairable(Item item)
|
|
{
|
|
Item = item;
|
|
}
|
|
}
|
|
}
|