Files
wwdpublic/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs
sleepyyapril 6249942d3e Goob Mechs (#1611)
# Description

<!--
Explain this PR in as much detail as applicable

Some example prompts to consider:
How might this affect the game? The codebase?
What might be some alternatives to this?
How/Who does this benefit/hurt [the game/codebase]?
-->

We like mechs here, yeah?

---

# Changelog

<!--
You can add an author after the `🆑` to change the name that appears
in the changelog (ex: `🆑 Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

🆑 Mocho, John Space
- tweak: The H.O.N.K. has received an airtight cabin for honk operations
in outer space.
- add: Added the Ripley MK-II, a heavy, slow all-purpose mech, featuring
a pressurized cabin for space operations.
- add: Added the Clarke, A fast moving mech for space travel, with built
in thrusters (not certain if they work properly though :trollface:)
- add: Added the Gygax, a lightly armored and highly mobile mech with
enough force to rip walls, or someone's head off.
- add: Added the Durand, a slow but beefy combat suit that you dont want
to fight in close quarters.
- add: Added the Marauder, a specialized mech issued to ERT operatives.
- add: Added the Seraph, a specialized combat suit issued to ???
operatives.
- add: The syndicate has started issuing units under the codenames "Dark
Gygax" and "Mauler" to syndicate agents at an introductory price.
- add: The exosuit fabricator can now be emagged to reveal new recipes.
- add: There are 4 new bounties cargo can fulfill for mechs. Feedback on
the cost/reward is welcome!

---------

Signed-off-by: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com>
Co-authored-by: John Space <bigdumb421@gmail.com>
Co-authored-by: gluesniffler <159397573+gluesniffler@users.noreply.github.com>
Co-authored-by: ScyronX <166930367+ScyronX@users.noreply.github.com>

(cherry picked from commit e3003b67014565816e83556c826a8bba344aac94)
2025-01-23 08:06:20 +03:00

198 lines
8.7 KiB
C#

using Robust.Shared.Random;
using Content.Shared.Silicon.Components;
using Content.Server.Power.Components;
using Content.Shared.Mobs.Systems;
using Content.Server.Temperature.Components;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Popups;
using Content.Shared.Silicon.Systems;
using Content.Shared.Movement.Systems;
using Content.Server.Body.Components;
using Robust.Shared.Containers;
using Content.Shared.Mind.Components;
using System.Diagnostics.CodeAnalysis;
using Content.Server.PowerCell;
using Robust.Shared.Timing;
using Robust.Shared.Configuration;
using Robust.Shared.Utility;
using Content.Shared.CCVar;
using Content.Shared.PowerCell.Components;
using Content.Shared.Alert;
namespace Content.Server.Silicon.Charge;
public sealed class SiliconChargeSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly MovementSpeedModifierSystem _moveMod = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SiliconComponent, ComponentStartup>(OnSiliconStartup);
}
public bool TryGetSiliconBattery(EntityUid silicon, [NotNullWhen(true)] out BatteryComponent? batteryComp)
{
batteryComp = null;
if (!HasComp<SiliconComponent>(silicon))
return false;
// try get a battery directly on the inserted entity
if (TryComp(silicon, out batteryComp)
|| _powerCell.TryGetBatteryFromSlot(silicon, out batteryComp))
return true;
//DebugTools.Assert("SiliconComponent does not contain Battery");
return false;
}
private void OnSiliconStartup(EntityUid uid, SiliconComponent component, ComponentStartup args)
{
if (!HasComp<PowerCellSlotComponent>(uid))
return;
if (component.EntityType.GetType() != typeof(SiliconType))
DebugTools.Assert("SiliconComponent.EntityType is not a SiliconType enum.");
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// For each siliconComp entity with a battery component, drain their charge.
var query = EntityQueryEnumerator<SiliconComponent>();
while (query.MoveNext(out var silicon, out var siliconComp))
{
if (_mobState.IsDead(silicon)
|| !siliconComp.BatteryPowered)
continue;
// Check if the Silicon is an NPC, and if so, follow the delay as specified in the CVAR.
if (siliconComp.EntityType.Equals(SiliconType.Npc))
{
var updateTime = _config.GetCVar(CCVars.SiliconNpcUpdateTime);
if (_timing.CurTime - siliconComp.LastDrainTime < TimeSpan.FromSeconds(updateTime))
continue;
siliconComp.LastDrainTime = _timing.CurTime;
}
// If you can't find a battery, set the indicator and skip it.
if (!TryGetSiliconBattery(silicon, out var batteryComp))
{
UpdateChargeState(silicon, 0, siliconComp);
if (_alerts.IsShowingAlert(silicon, siliconComp.BatteryAlert))
{
_alerts.ClearAlert(silicon, siliconComp.BatteryAlert);
_alerts.ShowAlert(silicon, siliconComp.NoBatteryAlert);
}
continue;
}
// If the silicon ghosted or is SSD while still being powered, skip it.
if (TryComp<MindContainerComponent>(silicon, out var mindContComp)
&& !mindContComp.HasMind)
continue;
var drainRate = siliconComp.DrainPerSecond;
// All multipliers will be subtracted by 1, and then added together, and then multiplied by the drain rate. This is then added to the base drain rate.
// This is to stop exponential increases, while still allowing for less-than-one multipliers.
var drainRateFinalAddi = 0f;
// TODO: Devise a method of adding multis where other systems can alter the drain rate.
// Maybe use something similar to refreshmovespeedmodifiers, where it's stored in the component.
// Maybe it doesn't matter, and stuff should just use static drain?
if (!siliconComp.EntityType.Equals(SiliconType.Npc)) // Don't bother checking heat if it's an NPC. It's a waste of time, and it'd be delayed due to the update time.
drainRateFinalAddi += SiliconHeatEffects(silicon, siliconComp, frameTime) - 1; // This will need to be changed at some point if we allow external batteries, since the heat of the Silicon might not be applicable.
// Ensures that the drain rate is at least 10% of normal,
// and would allow at least 4 minutes of life with a max charge, to prevent cheese.
drainRate += Math.Clamp(drainRateFinalAddi, drainRate * -0.9f, batteryComp.MaxCharge / 240);
// Drain the battery.
_powerCell.TryUseCharge(silicon, frameTime * drainRate);
// Figure out the current state of the Silicon.
var chargePercent = (short) MathF.Round(batteryComp.CurrentCharge / batteryComp.MaxCharge * 10f);
UpdateChargeState(silicon, chargePercent, siliconComp);
}
}
/// <summary>
/// Checks if anything needs to be updated, and updates it.
/// </summary>
public void UpdateChargeState(EntityUid uid, short chargePercent, SiliconComponent component)
{
component.ChargeState = chargePercent;
RaiseLocalEvent(uid, new SiliconChargeStateUpdateEvent(chargePercent));
_moveMod.RefreshMovementSpeedModifiers(uid);
// If the battery was replaced and the no battery indicator is showing, replace the indicator
if (_alerts.IsShowingAlert(uid, component.NoBatteryAlert) && chargePercent != 0)
{
_alerts.ClearAlert(uid, component.NoBatteryAlert);
_alerts.ShowAlert(uid, component.BatteryAlert, chargePercent);
}
}
private float SiliconHeatEffects(EntityUid silicon, SiliconComponent siliconComp, float frameTime)
{
if (!TryComp<TemperatureComponent>(silicon, out var temperComp)
|| !TryComp<ThermalRegulatorComponent>(silicon, out var thermalComp))
return 0;
// If the Silicon is hot, drain the battery faster, if it's cold, drain it slower, capped.
var upperThresh = thermalComp.NormalBodyTemperature + thermalComp.ThermalRegulationTemperatureThreshold;
var upperThreshHalf = thermalComp.NormalBodyTemperature + thermalComp.ThermalRegulationTemperatureThreshold * 0.5f;
// Check if the silicon is in a hot environment.
if (temperComp.CurrentTemperature > upperThreshHalf)
{
// Divide the current temp by the max comfortable temp capped to 4, then add that to the multiplier.
var hotTempMulti = Math.Min(temperComp.CurrentTemperature / upperThreshHalf, 4);
// If the silicon is hot enough, it has a chance to catch fire.
siliconComp.OverheatAccumulator += frameTime;
if (!(siliconComp.OverheatAccumulator >= 5))
return hotTempMulti;
siliconComp.OverheatAccumulator -= 5;
if (!EntityManager.TryGetComponent<FlammableComponent>(silicon, out var flamComp)
|| flamComp is { OnFire: true }
|| !(temperComp.CurrentTemperature > temperComp.HeatDamageThreshold))
return hotTempMulti;
_popup.PopupEntity(Loc.GetString("silicon-overheating"), silicon, silicon, PopupType.MediumCaution);
if (!_random.Prob(Math.Clamp(temperComp.CurrentTemperature / (upperThresh * 5), 0.001f, 0.9f)))
return hotTempMulti;
// Goobstation: Replaced by KillOnOverheatSystem
//_flammable.AdjustFireStacks(silicon, Math.Clamp(siliconComp.FireStackMultiplier, -10, 10), flamComp);
//_flammable.Ignite(silicon, silicon, flamComp);
return hotTempMulti;
}
// Check if the silicon is in a cold environment.
if (temperComp.CurrentTemperature < thermalComp.NormalBodyTemperature)
return 0.5f + temperComp.CurrentTemperature / thermalComp.NormalBodyTemperature * 0.5f;
return 0;
}
}