Delta-v/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs

418 lines
16 KiB
C#

using Content.Shared.Containers.ItemSlots;
using Content.Shared.ReverseEngineering;
using Content.Shared.Audio;
using Content.Shared.Examine;
using Content.Server.Research.TechnologyDisk.Components;
using Content.Server.UserInterface;
using Content.Server.Power.Components;
using Content.Server.Construction;
using Content.Server.Popups;
using Content.Shared.UserInterface;
using Robust.Shared.Containers;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Robust.Shared.Timing;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
namespace Content.Server.ReverseEngineering;
public sealed class ReverseEngineeringSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly ItemSlotsSystem _slots = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
private const string TargetSlot = "target_slot";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReverseEngineeringMachineComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, RefreshPartsEvent>(OnRefreshParts);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, UpgradeExamineEvent>(OnExamineParts);
SubscribeLocalEvent<ActiveReverseEngineeringMachineComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ActiveReverseEngineeringMachineComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, ReverseEngineeringMachineScanButtonPressedMessage>(OnScanButtonPressed);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, ReverseEngineeringMachineSafetyButtonToggledMessage>(OnSafetyButtonToggled);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, ReverseEngineeringMachineAutoScanButtonToggledMessage>(OnAutoScanButtonToggled);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, ReverseEngineeringMachineStopButtonPressedMessage>(OnStopButtonPressed);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, ReverseEngineeringMachineEjectButtonPressedMessage>(OnEjectButtonPressed);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<ReverseEngineeringComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<ReverseEngineeringMachineComponent, BeforeActivatableUIOpenEvent>((e,c,_) => UpdateUserInterface(e,c));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var (active, rev) in EntityQuery<ActiveReverseEngineeringMachineComponent, ReverseEngineeringMachineComponent>())
{
UpdateUserInterface(rev.Owner, rev);
if (_timing.CurTime - active.StartTime < rev.AnalysisDuration)
continue;
FinishProbe(rev.Owner, rev, active);
}
}
private void OnEntInserted(EntityUid uid, ReverseEngineeringMachineComponent component, EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != TargetSlot || !TryComp<ReverseEngineeringComponent>(args.Entity, out var rev))
return;
_slots.SetLock(uid, TargetSlot, true);
component.CurrentItem = args.Entity;
component.CurrentItemDifficulty = rev.Difficulty;
component.CachedMessage = GetReverseEngineeringScanMessage(component);
UpdateUserInterface(uid, component);
if (TryComp<AppearanceComponent>(uid, out var appearance))
_appearanceSystem.SetData(uid, ReverseEngineeringVisuals.ChamberOpen, false, appearance);
}
private void OnEntRemoved(EntityUid uid, ReverseEngineeringMachineComponent component, EntRemovedFromContainerMessage args)
{
if (args.Container.ID != TargetSlot)
return;
component.CurrentItem = null;
component.CurrentItemDifficulty = 0;
component.Progress = 0;
CancelProbe(uid, component);
if (TryComp<AppearanceComponent>(uid, out var appearance))
_appearanceSystem.SetData(uid, ReverseEngineeringVisuals.ChamberOpen, true, appearance);
}
private void OnRefreshParts(EntityUid uid, ReverseEngineeringMachineComponent component, RefreshPartsEvent args)
{
var bonusRating = args.PartRatings[component.MachinePartScanBonus];
var aversionRating = args.PartRatings[component.MachinePartDangerAversionScore];
component.ScanBonus = (int) bonusRating;
component.DangerAversionScore = (int) aversionRating;
}
private void OnExamineParts(EntityUid uid, ReverseEngineeringMachineComponent component, UpgradeExamineEvent args)
{
args.AddNumberUpgrade("reverse-engineering-machine-bonus-upgrade", component.ScanBonus - 1);
args.AddNumberUpgrade("reverse-engineering-machine-aversion-upgrade", component.DangerAversionScore - 1);
}
private void OnStartup(EntityUid uid, ActiveReverseEngineeringMachineComponent component, ComponentStartup args)
{
_ambientSoundSystem.SetAmbience(uid, true);
}
private void OnShutdown(EntityUid uid,ActiveReverseEngineeringMachineComponent component, ComponentShutdown args)
{
_ambientSoundSystem.SetAmbience(uid, false);
}
private void OnScanButtonPressed(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineScanButtonPressedMessage args)
{
if (component.CurrentItem == null)
return;
if (HasComp<ActiveReverseEngineeringMachineComponent>(uid))
return;
_audio.PlayPvs(component.ClickSound, uid);
component.CachedMessage = null;
var activeComp = EnsureComp<ActiveReverseEngineeringMachineComponent>(uid);
activeComp.StartTime = _timing.CurTime;
activeComp.Item = component.CurrentItem.Value;
}
private void OnSafetyButtonToggled(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineSafetyButtonToggledMessage args)
{
_audio.PlayPvs(component.ClickSound, uid);
component.SafetyOn = args.Safety;
component.CachedMessage = null;
UpdateUserInterface(uid, component);
}
private void OnAutoScanButtonToggled(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineAutoScanButtonToggledMessage args)
{
_audio.PlayPvs(component.ClickSound, uid);
component.AutoScan = args.AutoScan;
}
private void OnPowerChanged(EntityUid uid, ReverseEngineeringMachineComponent component, ref PowerChangedEvent args)
{
if (!args.Powered)
CancelProbe(uid, component);
}
private void OnExamined(EntityUid uid, ReverseEngineeringComponent component, ExaminedEvent args)
{
// TODO: Eventually this should probably get shoved into a contextual examine somewhere like health or machine upgrading.
// And this can be predicted I guess if difficulty becomes read only.
args.PushMarkup(Loc.GetString("reverse-engineering-examine", ("diff", component.Difficulty)));
}
private void OnStopButtonPressed(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineStopButtonPressedMessage args)
{
_audio.PlayPvs(component.ClickSound, uid);
CancelProbe(uid, component);
}
private void OnEjectButtonPressed(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineEjectButtonPressedMessage args)
{
_audio.PlayPvs(component.ClickSound, uid);
Eject(uid, component);
}
private void UpdateUserInterface(EntityUid uid, ReverseEngineeringMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (!_ui.TryGetUi(uid, ReverseEngineeringMachineUiKey.Key, out var bui))
return;
EntityUid? item = component.CurrentItem;
if (component.CachedMessage == null)
component.CachedMessage = GetReverseEngineeringScanMessage(component);
var totalTime = TimeSpan.Zero;
var scanning = TryComp<ActiveReverseEngineeringMachineComponent>(uid, out var active);
var canScan = (item != null && !scanning);
var remaining = active != null ? _timing.CurTime - active.StartTime : TimeSpan.Zero;
EntityManager.TryGetNetEntity(item, out var netItem);
var state = new ReverseEngineeringMachineScanUpdateState(netItem, canScan, component.CachedMessage, scanning, component.SafetyOn, component.AutoScan, component.Progress, remaining, component.AnalysisDuration);
_ui.SetUiState(bui, state);
}
private ReverseEngineeringTickResult Roll(ReverseEngineeringMachineComponent component, out int actualRoll)
{
int roll = (_random.Next(1, 6) + _random.Next(1, 6) + _random.Next(1, 6));
roll += component.ScanBonus;
if (!component.SafetyOn)
roll += component.DangerBonus;
roll -= component.CurrentItemDifficulty;
actualRoll = roll;
return roll switch
{
<= 9 => ReverseEngineeringTickResult.Destruction,
<= 10 => ReverseEngineeringTickResult.Stagnation,
<= 12 => ReverseEngineeringTickResult.SuccessMinor,
<= 15 => ReverseEngineeringTickResult.SuccessAverage,
<= 17 => ReverseEngineeringTickResult.SuccessMajor,
_ => ReverseEngineeringTickResult.InstantSuccess
};
}
private void FinishProbe(EntityUid uid, ReverseEngineeringMachineComponent? component = null, ActiveReverseEngineeringMachineComponent? active = null)
{
if (!Resolve(uid, ref component, ref active))
return;
if (!TryComp<ReverseEngineeringComponent>(component.CurrentItem, out var rev))
{
Logger.Error("We somehow scanned a " + component.CurrentItem + " for reverse engineering...");
return;
}
component.CachedMessage = null;
var result = Roll(component, out var actualRoll);
if (result == ReverseEngineeringTickResult.Destruction)
{
if (!component.SafetyOn && actualRoll + component.DangerAversionScore < 9)
{
Del(component.CurrentItem.Value);
component.CurrentItem = null;
if (TryComp<AppearanceComponent>(uid, out var appearance))
_appearanceSystem.SetData(uid, ReverseEngineeringVisuals.ChamberOpen, true, appearance);
_slots.SetLock(uid, TargetSlot, false);
_audio.PlayPvs(component.FailSound1, uid);
_audio.PlayPvs(component.FailSound2, uid);
_popupSystem.PopupEntity(Loc.GetString("reverse-engineering-popup-failure", ("machine", uid)), uid, Shared.Popups.PopupType.MediumCaution);
CancelProbe(uid, component);
} else
{
result = ReverseEngineeringTickResult.Stagnation;
}
}
component.LastResult = result;
int bonus = 0;
switch (result)
{
case ReverseEngineeringTickResult.Stagnation:
{
bonus += 1;
break;
}
case ReverseEngineeringTickResult.SuccessMinor:
{
bonus += 10;
break;
}
case ReverseEngineeringTickResult.SuccessAverage:
{
bonus += 25;
break;
}
case ReverseEngineeringTickResult.SuccessMajor:
{
bonus += 40;
break;
}
case ReverseEngineeringTickResult.InstantSuccess:
{
bonus += 100;
break;
}
}
component.Progress += bonus;
component.Progress = Math.Clamp(component.Progress, 0, 100);
if (component.Progress < 100)
{
if (component.AutoScan)
{
active.StartTime = _timing.CurTime;
}
else
{
RemComp<ActiveReverseEngineeringMachineComponent>(uid);
}
} else
{
CreateDisk(uid, component.DiskPrototype, rev.Recipes);
_audio.PlayPvs(component.SuccessSound, uid);
if (rev.NewItem == null)
{
Eject(uid, component);
} else
{
_slots.SetLock(uid, TargetSlot, false);
Spawn(rev.NewItem, Transform(uid).Coordinates);
if (component.CurrentItem != null)
Del(component.CurrentItem.Value);
}
RemComp<ActiveReverseEngineeringMachineComponent>(uid);
}
UpdateUserInterface(uid, component);
}
private void CreateDisk(EntityUid uid, string diskPrototype, List<string>? recipes)
{
var disk = Spawn(diskPrototype, Transform(uid).Coordinates);
if (!TryComp<TechnologyDiskComponent>(disk, out var diskComponent))
return;
diskComponent.Recipes = recipes;
}
private FormattedMessage? GetReverseEngineeringScanMessage(ReverseEngineeringMachineComponent component)
{
var msg = new FormattedMessage();
if (component.CurrentItem == null)
{
msg.AddMarkup(Loc.GetString("reverse-engineering-status-ready"));
return msg;
}
msg.AddMarkup(Loc.GetString("reverse-engineering-current-item", ("item", component.CurrentItem.Value)));
msg.PushNewline();
msg.PushNewline();
var analysisScore = component.ScanBonus;
if (!component.SafetyOn)
analysisScore += component.DangerBonus;
msg.AddMarkup(Loc.GetString("reverse-engineering-analysis-score", ("score", analysisScore)));
msg.PushNewline();
msg.AddMarkup(Loc.GetString("reverse-engineering-item-difficulty", ("difficulty", component.CurrentItemDifficulty)));
msg.PushNewline();
msg.AddMarkup(Loc.GetString("reverse-engineering-progress", ("progress", component.Progress)));
msg.PushNewline();
if (component.LastResult != null)
{
string lastProbe = string.Empty;
switch (component.LastResult)
{
case ReverseEngineeringTickResult.Destruction:
lastProbe = Loc.GetString("reverse-engineering-failure");
break;
case ReverseEngineeringTickResult.Stagnation:
lastProbe = Loc.GetString("reverse-engineering-stagnation");
break;
case ReverseEngineeringTickResult.SuccessMinor:
lastProbe = Loc.GetString("reverse-engineering-minor");
break;
case ReverseEngineeringTickResult.SuccessAverage:
lastProbe = Loc.GetString("reverse-engineering-average");
break;
case ReverseEngineeringTickResult.SuccessMajor:
lastProbe = Loc.GetString("reverse-engineering-major");
break;
case ReverseEngineeringTickResult.InstantSuccess:
lastProbe = Loc.GetString("reverse-engineering-success");
break;
}
msg.AddMarkup(Loc.GetString("reverse-engineering-last-attempt-result", ("result", lastProbe)));
}
return msg;
}
private void Eject(EntityUid uid, ReverseEngineeringMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
_slots.SetLock(uid, TargetSlot, false);
_slots.TryEject(uid, TargetSlot, null, out var item);
}
private void CancelProbe(EntityUid uid, ReverseEngineeringMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.CachedMessage = null;
component.LastResult = null;
RemComp<ActiveReverseEngineeringMachineComponent>(uid);
UpdateUserInterface(uid, component);
}
}