diff --git a/Content.Client/_DV/Reputation/UI/ContractsWindow.xaml.cs b/Content.Client/_DV/Reputation/UI/ContractsWindow.xaml.cs index 5277ddc2577..285f14d677a 100644 --- a/Content.Client/_DV/Reputation/UI/ContractsWindow.xaml.cs +++ b/Content.Client/_DV/Reputation/UI/ContractsWindow.xaml.cs @@ -1,4 +1,3 @@ - using Content.Client.UserInterface.Controls; using Content.Shared._DV.Reputation; using Robust.Client.AutoGenerated; @@ -11,6 +10,7 @@ namespace Content.Client._DV.Reputation.UI; public sealed partial class ContractsWindow : FancyWindow { [Dependency] private EntityManager _entMan = default!; + private readonly ReputationSystem _reputation; public event Action? OnAccept; public event Action? OnComplete; @@ -22,13 +22,20 @@ public sealed partial class ContractsWindow : FancyWindow { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); + + _reputation = _entMan.System(); + UpdateState(); } public void UpdateState() { - if (!_entMan.TryGetComponent(Owner, out var comp)) + if (!_entMan.TryGetComponent(Owner, out var store)) return; + if (_reputation.GetContracts(store.Mind) is not {} contracts) + return; + + var comp = contracts.Comp; if (comp.CurrentLevel is {} level) Level.Text = Loc.GetString(level.Name); Reputation.Text = $"{comp.Reputation} Reputation"; diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index a18a860c2b4..fc6052715fc 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -183,13 +183,13 @@ public sealed class TraitorRuleSystem : GameRuleSystem Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink add"); var uplinked = _uplink.AddUplink(traitor, startingBalance, pda, true); + _reputation.AddContracts(traitor, pda); // DeltaV if (pda is not null && uplinked) { Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink is PDA"); // Codes are only generated if the uplink is a PDA var ev = new GenerateUplinkCodeEvent(); RaiseLocalEvent(pda.Value, ref ev); - _reputation.AddContracts(traitor, pda.Value); // DeltaV if (ev.Code is { } generatedCode) { diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index c1125e1b1be..1118c19482e 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -288,7 +288,7 @@ namespace Content.Server.PDA return; // check if its locked again to prevent malicious clients opening locked uplinks - if (HasComp(uid) && IsUnlocked(uid)) + if (HasComp(uid) && IsUnlocked(uid)) _reputation.ToggleUI(msg.Actor, uid); } // End DeltaV Additions diff --git a/Content.Server/_DV/Objectives/Components/ContractObjectiveComponent.cs b/Content.Server/_DV/Objectives/Components/ContractObjectiveComponent.cs index ebbca25f63f..2bd60e3ec19 100644 --- a/Content.Server/_DV/Objectives/Components/ContractObjectiveComponent.cs +++ b/Content.Server/_DV/Objectives/Components/ContractObjectiveComponent.cs @@ -43,9 +43,8 @@ public sealed partial class ContractObjectiveComponent : Component public ProtoId Currency = "Telecrystal"; /// - /// The PDA used to take this contract. - /// Might not always exist. + /// The mind with Contracts this contract belongs to. /// [DataField] - public EntityUid? Pda; + public EntityUid? Contracts; } diff --git a/Content.Server/_DV/Objectives/Systems/AssistRandomContractSystem.cs b/Content.Server/_DV/Objectives/Systems/AssistRandomContractSystem.cs index 5fc2d21bf05..98b4287cb31 100644 --- a/Content.Server/_DV/Objectives/Systems/AssistRandomContractSystem.cs +++ b/Content.Server/_DV/Objectives/Systems/AssistRandomContractSystem.cs @@ -35,7 +35,7 @@ public sealed class AssistRandomContractSystem : EntitySystem if (!_target.GetTarget(ent, out var target)) return; - if (_reputation.GetMindContracts(target.Value) is not {} contracts) + if (_reputation.GetContracts(target) is not {} contracts) return; _available.Clear(); diff --git a/Content.Server/_DV/Objectives/Systems/ContractObjectiveSystem.cs b/Content.Server/_DV/Objectives/Systems/ContractObjectiveSystem.cs index 4ef4ccbc58b..a8a4d0cb147 100644 --- a/Content.Server/_DV/Objectives/Systems/ContractObjectiveSystem.cs +++ b/Content.Server/_DV/Objectives/Systems/ContractObjectiveSystem.cs @@ -1,9 +1,15 @@ using Content.Server._DV.Objectives.Components; using Content.Server.Objectives.Systems; +using Content.Server.Stack; using Content.Server.Store.Systems; using Content.Shared._DV.Objectives.Systems; using Content.Shared._DV.Reputation; using Content.Shared.FixedPoint; +using Content.Shared.Ghost; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Mind; +using Robust.Shared.Prototypes; +using System.Linq; namespace Content.Server._DV.Objectives.Systems; @@ -13,7 +19,10 @@ namespace Content.Server._DV.Objectives.Systems; public sealed class ContractObjectiveSystem : SharedContractObjectiveSystem { [Dependency] private readonly CodeConditionSystem _codeCondition = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ReputationSystem _reputation = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly StoreSystem _store = default!; private Dictionary _currency = new(); @@ -28,24 +37,63 @@ public sealed class ContractObjectiveSystem : SharedContractObjectiveSystem private void OnTaken(Entity ent, ref ContractTakenEvent args) { - ent.Comp.Pda = args.Pda; - + ent.Comp.Contracts = args.Contracts; if (ent.Comp.Prepaid) - Pay(ent, args.Pda); + Pay(ent); } private void OnCompleted(Entity ent, ref ContractCompletedEvent args) { - _reputation.GiveReputation(args.Pda, ent.Comp.Reputation); + _reputation.GiveReputation(args.Contracts, ent.Comp.Reputation); if (!ent.Comp.Prepaid) - Pay(ent, args.Pda); + Pay(ent); } - private void Pay(Entity ent, EntityUid pda) + private void Pay(Entity ent) { - _currency.Clear(); - _currency[ent.Comp.Currency] = ent.Comp.Payment; - _store.TryAddCurrency(_currency, pda); + if (_reputation.GetContracts(ent.Comp.Contracts) is not {} contracts) + return; + + if (contracts.Comp.Store is {} store) + { + _currency.Clear(); + _currency[ent.Comp.Currency] = ent.Comp.Payment; + _store.TryAddCurrency(_currency, store); + return; + } + + // try give them TC item there's no store + var mind = Comp(contracts); + // no mob unlucky + if (mind.OwnedEntity is not {} mob) + return; + + // don't spawn tc under ghosts, give the dead body TC instead + if (HasComp(mob)) + { + // cremated, no TC for you! + if (GetEntity(mind.OriginalOwnedEntity) is not {} original) + return; + + mob = original; + } + + if (!Exists(mob)) + return; + + // this is copy pasted from store system because it has no API for spawning cash entities + var coords = Transform(mob).Coordinates; + var amountRemaining = ent.Comp.Payment; + var proto = _proto.Index(ent.Comp.Currency); + foreach (var value in proto.Cash!.Keys.OrderByDescending(x => x)) + { + var cashId = proto.Cash[value]; + var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / value)); + var ents = _stack.SpawnMultiple(cashId, amountToSpawn, coords); + if (ents.FirstOrDefault() is {} cash) + _hands.PickupOrDrop(mob, cash); + amountRemaining -= value * amountToSpawn; + } } /// @@ -59,8 +107,8 @@ public sealed class ContractObjectiveSystem : SharedContractObjectiveSystem if (_codeCondition.IsCompleted(uid) || !pred((uid, comp))) continue; - if (contract.Pda is {} pda && TryComp(pda, out var contracts)) - _reputation.TryFailContract((pda, contracts), uid); + if (_reputation.GetContracts(contract.Contracts) is {} contracts) + _reputation.TryFailContract(contracts, uid); } } @@ -70,9 +118,8 @@ public sealed class ContractObjectiveSystem : SharedContractObjectiveSystem public bool TryFailContract(Entity objective) { return Resolve(objective, ref objective.Comp) && - objective.Comp.Pda is {} pda && - TryComp(pda, out var comp) && - _reputation.TryFailContract((pda, comp), objective); + _reputation.GetContracts(objective.Comp.Contracts) is {} contracts && + _reputation.TryFailContract(contracts, objective); } public override string ContractName(EntityUid objective) diff --git a/Content.Server/_DV/Store/Conditions/ReputationCondition.cs b/Content.Server/_DV/Store/Conditions/ReputationCondition.cs index 422fd3778fb..955c04a44ca 100644 --- a/Content.Server/_DV/Store/Conditions/ReputationCondition.cs +++ b/Content.Server/_DV/Store/Conditions/ReputationCondition.cs @@ -18,10 +18,13 @@ public sealed partial class ReputationCondition : ListingCondition public override bool Condition(ListingConditionArgs args) { - var reputation = args.EntityManager.System(); - if (args.StoreEntity is not {} pda || reputation.GetReputation(pda) is not {} rep) + if (!args.EntityManager.TryGetComponent(args.StoreEntity, out var store)) return true; // nukie uplink or a surplus + var reputation = args.EntityManager.System(); + if (reputation.GetMindReputation(store.Mind) is not {} rep) + return false; // uplink implant in non-traitor, no epic goodies allowed + return rep >= Reputation; } } diff --git a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs index c4e8b878258..5881693ae9f 100644 --- a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs +++ b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs @@ -49,7 +49,7 @@ public abstract class SharedObjectivesSystem : EntitySystem return false; } // Begin DeltaV Additions - check available contracts too - if (_reputation.GetMindContracts(mindId) is {} contracts) + if (_reputation.GetContracts(mindId) is {} contracts) { foreach (var objective in contracts.Comp.Offerings) { diff --git a/Content.Shared/_DV/Implants/UplinkImplantSystem.cs b/Content.Shared/_DV/Implants/UplinkImplantSystem.cs new file mode 100644 index 00000000000..59fd29f6daf --- /dev/null +++ b/Content.Shared/_DV/Implants/UplinkImplantSystem.cs @@ -0,0 +1,52 @@ +using Content.Shared._DV.Reputation; +using Content.Shared.Actions; +using Content.Shared.Implants; +using Content.Shared.Mind; + +namespace Content.Shared._DV.Implants; + +/// +/// Assigns an uplink implant's contracts mind to the contracts mind. +/// +public sealed class UplinkImplantSystem : EntitySystem +{ + [Dependency] private readonly ReputationSystem _reputation = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnImplanted); + SubscribeLocalEvent(OnRemoved); + } + + private void OnImplanted(Entity ent, ref ImplantImplantedEvent args) + { + if (args.Implanted is not {} mob) + return; + + // don't overwrite if the mind is valid + if (_reputation.GetContracts(ent.Comp.Mind) != null) + return; + + // implanting into SSD people won't let them use contracts but whatever + if (_mind.GetMind(mob) is not {} mind) + return; + + // giving non-traitors an uplink implant won't let them buy rep-gated gear + if (_reputation.GetContracts(mind) is not {} contracts) + return; + + _actions.AddAction(mob, ref ent.Comp.ActionId, ent.Comp.Action, ent.Owner); + _reputation.SetStoreMind(ent, contracts); + } + + private void OnRemoved(Entity ent, ref ImplantRemovedEvent args) + { + if (args.Implanted is {} mob) + _actions.RemoveProvidedActions(mob, ent.Owner); + _reputation.SetStoreMind(ent, null); + } +} diff --git a/Content.Shared/_DV/Reputation/ContractsComponent.cs b/Content.Shared/_DV/Reputation/ContractsComponent.cs index 4abe9b5f34d..fa699e6ea11 100644 --- a/Content.Shared/_DV/Reputation/ContractsComponent.cs +++ b/Content.Shared/_DV/Reputation/ContractsComponent.cs @@ -7,7 +7,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared._DV.Reputation; /// -/// Component added to traitor PDAs to store contract data. +/// Component added to traitor minds to store contract data. /// [RegisterComponent, NetworkedComponent, Access(typeof(ReputationSystem))] [AutoGenerateComponentState(true)] @@ -20,10 +20,17 @@ public sealed partial class ContractsComponent : Component public int Reputation; /// - /// The mind of the traitor. + /// The stores assigned to this mind. + /// Usually only ever contains a PDA and maybe an uplink implant. /// [DataField] - public EntityUid? Mind; + public List Stores = new(); + + /// + /// The most recently assigned store. + /// + [ViewVariables] + public EntityUid? Store => Stores.Count == 0 ? null : Stores[Stores.Count - 1]; /// /// The current reputation level, updated when it changes. diff --git a/Content.Shared/_DV/Reputation/Events.cs b/Content.Shared/_DV/Reputation/Events.cs index 357f572dafb..05a2b15c930 100644 --- a/Content.Shared/_DV/Reputation/Events.cs +++ b/Content.Shared/_DV/Reputation/Events.cs @@ -6,17 +6,17 @@ namespace Content.Shared._DV.Reputation; /// Event that gets raised on an objective after it has been added and taken. /// [ByRefEvent] -public record struct ContractTakenEvent(Entity Pda, Entity Mind); +public record struct ContractTakenEvent(Entity Contracts, Entity Mind); /// /// Event that gets raised on an objective after it becomes impossible to completed. /// It gets deleted afterwards. /// [ByRefEvent] -public record struct ContractFailedEvent(Entity Pda); +public record struct ContractFailedEvent(Entity Contracts); /// /// Event that gets raised on an objective after it has been completed. /// [ByRefEvent] -public record struct ContractCompletedEvent(Entity Pda); +public record struct ContractCompletedEvent(Entity Contracts); diff --git a/Content.Shared/_DV/Reputation/MindReputationComponent.cs b/Content.Shared/_DV/Reputation/MindReputationComponent.cs deleted file mode 100644 index f1c7f593e28..00000000000 --- a/Content.Shared/_DV/Reputation/MindReputationComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared._DV.Reputation; - -/// -/// Stores reputation-related data for mind entities. -/// Has a backup reputation value incase their PDA is deleted. -/// -[RegisterComponent, NetworkedComponent, Access(typeof(ReputationSystem))] -public sealed partial class MindReputationComponent : Component -{ - /// - /// The traitor's PDA with , might not always exist. - /// - [DataField] - public EntityUid? Pda; - - [DataField] - public int Reputation; -} diff --git a/Content.Shared/_DV/Reputation/ReputationSystem.cs b/Content.Shared/_DV/Reputation/ReputationSystem.cs index 07bbbb1bef9..5eddf1631d5 100644 --- a/Content.Shared/_DV/Reputation/ReputationSystem.cs +++ b/Content.Shared/_DV/Reputation/ReputationSystem.cs @@ -2,6 +2,7 @@ using Content.Shared._DV.Objectives.Systems; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Objectives.Systems; +using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -10,6 +11,7 @@ namespace Content.Shared._DV.Reputation; public sealed class ReputationSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedContractObjectiveSystem _contract = default!; [Dependency] private readonly SharedMindSystem _mind = default!; @@ -23,13 +25,15 @@ public sealed class ReputationSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnStoreInit); + SubscribeLocalEvent(OnStoreShutdown); + SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnHandleState); - Subs.BuiEvents(ContractsUiKey.Key, subs => + Subs.BuiEvents(ContractsUiKey.Key, subs => { + subs.Event(OnUIOpened); subs.Event(OnAcceptMessage); subs.Event(OnCompleteMessage); subs.Event(OnRejectMessage); @@ -44,6 +48,9 @@ public sealed class ReputationSystem : EntitySystem { base.Update(frameTime); + if (_net.IsClient) // only server does the rng + return; + var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { @@ -53,11 +60,32 @@ public sealed class ReputationSystem : EntitySystem #region Event Handlers - private void OnInit(Entity ent, ref ComponentInit args) + private void OnStoreInit(Entity ent, ref ComponentInit args) { _ui.SetUi(ent.Owner, ContractsUiKey.Key, new InterfaceData("ContractsBUI")); } + private void OnStoreShutdown(Entity ent, ref ComponentShutdown args) + { + if (GetContracts(ent.Comp.Mind) is not {} contracts) + return; + + // if the PDA is cremated or eaten by a singulo or something, + // delete all the offerings and fail the active contracts + foreach (var uid in contracts.Comp.Offerings) + { + Del(uid); + } + + foreach (var obj in contracts.Comp.Objectives) + { + ContractFailed(contracts, obj); + } + + // don't try to pay TC to this store now that it's deleted + contracts.Comp.Stores.Remove(ent.Owner); + } + private void OnMapInit(Entity ent, ref MapInitEvent args) { // creates the slots for fresh pdas @@ -65,25 +93,6 @@ public sealed class ReputationSystem : EntitySystem PickOfferings(ent); } - private void OnShutdown(Entity ent, ref ComponentShutdown args) - { - // if the PDA is cremated or thrown in a singulo or something, - // delete all the offerings and fail the active contracts - foreach (var uid in ent.Comp.Offerings) - { - Del(uid); - } - - foreach (var obj in ent.Comp.Objectives) - { - ContractFailed(ent, obj); - } - - // unlink it from the mind - if (TryComp(ent.Comp.Mind, out var mind)) - mind.Pda = null; - } - private void OnUnpaused(Entity ent, ref EntityUnpausedEvent args) { for (var i = 0; i < ent.Comp.Slots.Count; i++) @@ -109,30 +118,27 @@ public sealed class ReputationSystem : EntitySystem UpdateUI(ent); } - private void OnAcceptMessage(Entity ent, ref ContractsAcceptMessage args) + private void OnUIOpened(Entity ent, ref BoundUIOpenedEvent args) { - var i = args.Index; - if (i < 0 || i >= ent.Comp.Offerings.Count) - return; - - if (ent.Comp.Offerings[i] is not {} objective || !TryTakeContract(ent, objective)) - return; - - ent.Comp.Offerings[i] = null; - ent.Comp.OfferingSlots[i] = new OfferingSlot - { - NextUnlock = _timing.CurTime + ent.Comp.AcceptDelay - }; + UpdateStoreUI(ent); } - private void OnCompleteMessage(Entity ent, ref ContractsCompleteMessage args) + private void OnAcceptMessage(Entity ent, ref ContractsAcceptMessage args) { - TryCompleteContract(ent, args.Index); + if (GetContracts(ent.Comp.Mind) is {} contracts) + TryAcceptContract(contracts, args.Index); } - private void OnRejectMessage(Entity ent, ref ContractsRejectMessage args) + private void OnCompleteMessage(Entity ent, ref ContractsCompleteMessage args) { - TryRejectOffering(ent, args.Index); + if (GetContracts(ent.Comp.Mind) is {} contracts) + TryCompleteContract(contracts, args.Index); + } + + private void OnRejectMessage(Entity ent, ref ContractsRejectMessage args) + { + if (GetContracts(ent.Comp.Mind) is {} contracts) + TryRejectOffering(contracts, args.Index); } #endregion @@ -140,39 +146,52 @@ public sealed class ReputationSystem : EntitySystem #region Public API /// - /// Add contracts to a traitor's PDA. + /// Add contracts to a traitor's mind and PDA. /// Throws if you call this multiple times on the same mind or pda. /// - public void AddContracts(EntityUid mob, EntityUid pda) + public void AddContracts(EntityUid mob, EntityUid? pda) { if (_mind.GetMind(mob) is not {} mindId) return; // AddComp so it will throw if you are trying to bulldoze a used mind or pda - var contracts = AddComp(pda); - var mind = AddComp(mindId); - contracts.Mind = mindId; - mind.Pda = pda; - PickOfferings((pda, contracts)); + var contracts = AddComp(mindId); + PickOfferings((mindId, contracts)); + + if (pda is not {} uid) + return; + + var store = AddComp(uid); + SetStoreMind((uid, store), mindId); } - public void ToggleUI(EntityUid user, EntityUid uid) + public void ToggleUI(EntityUid user, EntityUid store) { - UpdateUI(uid); - _ui.TryToggleUi(uid, ContractsUiKey.Key, user); + _ui.TryToggleUi(store, ContractsUiKey.Key, user); } - private void UpdateUI(EntityUid uid) + private void UpdateStoreUI(EntityUid uid) { _ui.SetUiState(uid, ContractsUiKey.Key, new ContractsState()); } + private void UpdateUI(Entity ent) + { + foreach (var store in ent.Comp.Stores) + { + UpdateStoreUI(store); + } + } + /// /// Pick new offerings for open offering slots. /// public void PickOfferings(Entity ent) { - if (GetMind(ent) is not {} mind || ent.Comp.CurrentLevel is not {} level) + if (!TryComp(ent, out var mind)) + return; + + if (ent.Comp.CurrentLevel is not {} level) return; var difficulty = level.MaxDifficulty; @@ -183,7 +202,7 @@ public sealed class ReputationSystem : EntitySystem if (ent.Comp.Offerings[i] != null || IsLocked(ent.Comp.OfferingSlots[i].NextUnlock)) continue; - if (_objectives.GetRandomObjective(mind, mind, groups, difficulty) is not {} objective) + if (_objectives.GetRandomObjective(ent.Owner, mind, groups, difficulty) is not {} objective) { // prevent spinlock ent.Comp.OfferingSlots[i] = new OfferingSlot @@ -201,6 +220,8 @@ public sealed class ReputationSystem : EntitySystem }; Dirty(ent); } + + UpdateUI(ent); } /// @@ -208,13 +229,13 @@ public sealed class ReputationSystem : EntitySystem /// public bool TryTakeContract(Entity ent, EntityUid objective) { - if (GetMind(ent) is not {} mind || + if (!TryComp(ent, out var mind) || FindOpenSlot(ent) is not {} index) { return false; } - _mind.AddObjective(mind, mind, objective); + _mind.AddObjective(ent.Owner, mind, objective); ent.Comp.Objectives[index] = objective; var slot = ent.Comp.Slots[index]; @@ -222,11 +243,30 @@ public sealed class ReputationSystem : EntitySystem ent.Comp.Slots[index] = slot; Dirty(ent); - var ev = new ContractTakenEvent(ent, mind); + var ev = new ContractTakenEvent(ent, (ent.Owner, mind)); RaiseLocalEvent(objective, ref ev); return true; } + public bool TryAcceptContract(Entity ent, int i) + { + if (i < 0 || i >= ent.Comp.Offerings.Count) + return false; + + if (ent.Comp.Offerings[i] is not {} objective || !TryTakeContract(ent, objective)) + return false; + + ent.Comp.Offerings[i] = null; + ent.Comp.OfferingSlots[i] = new OfferingSlot + { + NextUnlock = _timing.CurTime + ent.Comp.AcceptDelay + }; + Dirty(ent); + UpdateUI(ent); + return true; + } + + /// /// If a contract's objective is complete, pays out etc and removes it. /// @@ -235,8 +275,8 @@ public sealed class ReputationSystem : EntitySystem if (index < 0 || index >= ent.Comp.Slots.Count || ent.Comp.Objectives[index] is not {} objective || - GetMind(ent) is not {} mind || - !_objectives.IsCompleted(objective, mind)) + !TryComp(ent, out var mind) || + !_objectives.IsCompleted(objective, (ent.Owner, mind))) { return false; } @@ -264,6 +304,7 @@ public sealed class ReputationSystem : EntitySystem NextUnlock = _timing.CurTime + ent.Comp.RejectDelay }; Dirty(ent); + UpdateUI(ent); Del(objective); return true; } @@ -283,59 +324,57 @@ public sealed class ReputationSystem : EntitySystem } /// - /// Get the mind that belongs to a contracts PDA. + /// Get the contracts for a mind, if it exists. /// - public Entity? GetMind(Entity ent) + public Entity? GetContracts(EntityUid? mindId) { - if (ent.Comp.Mind is not {} mindId) + if (mindId is not {} mind) return null; - if (!TryComp(mindId, out var mind)) + if (!TryComp(mind, out var comp)) return null; - return (mindId, mind); - } - - /// - /// Get the contracts pda for a mind, if it exists. - /// - public Entity? GetMindContracts(EntityUid mindId) - { - if (CompOrNull(mindId)?.Pda is not {} pda) - return null; - - if (!TryComp(pda, out var comp)) - return null; - - return (pda, comp); + return (mind, comp); } /// /// Gets the reputation for a mind, null if it had no . /// - public int? GetMindReputation(EntityUid mindId) + public int? GetMindReputation(EntityUid? mindId) { - if (CompOrNull(mindId)?.Pda is not {} pda) - return null; - - return GetReputation(pda); + return GetContracts(mindId)?.Comp.Reputation; } /// - /// Gets the reputation for a PDA, null if it had no . + /// Gets the reputation for a store, null if it had no . /// - public int? GetReputation(Entity ent) + public int? GetStoreReputation(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return null; - return ent.Comp.Reputation; + return GetMindReputation(ent.Comp.Mind); + } + + public void SetStoreMind(Entity ent, EntityUid? mind) + { + if (ent.Comp.Mind == mind) + return; + + if (GetContracts(ent.Comp.Mind) is {} oldContracts) + oldContracts.Comp.Stores.Remove(ent.Owner); + + ent.Comp.Mind = mind; + Dirty(ent); + + if (GetContracts(mind) is {} contracts) + contracts.Comp.Stores.Add(ent.Owner); } public bool GiveMindReputation(EntityUid mindId, int amount) { return amount != 0 && - GetMindContracts(mindId) is {} contracts && + GetContracts(mindId) is {} contracts && GiveReputation(contracts, amount); } @@ -346,8 +385,6 @@ public sealed class ReputationSystem : EntitySystem ent.Comp.Reputation = Math.Clamp(ent.Comp.Reputation + amount, 0, 100); Dirty(ent); - if (TryComp(ent.Comp.Mind, out var mind)) - mind.Reputation = ent.Comp.Reputation; UpdateLevel(ent); return true; } @@ -409,6 +446,7 @@ public sealed class ReputationSystem : EntitySystem NextUnlock = _timing.CurTime + delay }; Dirty(ent); + UpdateUI(ent); } private void UpdateLevel(Entity ent) @@ -448,6 +486,7 @@ public sealed class ReputationSystem : EntitySystem } } Dirty(ent); + UpdateUI(ent); } private void UpdateOfferingSlots(Entity ent) @@ -479,19 +518,20 @@ public sealed class ReputationSystem : EntitySystem } } Dirty(ent); + UpdateUI(ent); } private void ContractFailed(Entity ent, EntityUid? uid) { - if (GetMind(ent) is not {} mind) + if (uid is not {} objective) return; - if (uid is not {} objective) + if (!TryComp(ent, out var mind)) return; var ev = new ContractFailedEvent(ent); RaiseLocalEvent(objective, ref ev); - _mind.TryRemoveObjective(mind, objective); + _mind.TryRemoveObjective((ent.Owner, mind), objective); } private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) diff --git a/Content.Shared/_DV/Reputation/StoreContractsComponent.cs b/Content.Shared/_DV/Reputation/StoreContractsComponent.cs new file mode 100644 index 00000000000..28c6d1443df --- /dev/null +++ b/Content.Shared/_DV/Reputation/StoreContractsComponent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Reputation; + +/// +/// Ties a store entity (pda or uplink implant) to a mind's . +/// Limits what it can buy with ReputationCondition. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(ReputationSystem))] +[AutoGenerateComponentState] +public sealed partial class StoreContractsComponent : Component +{ + [DataField, AutoNetworkedField] + public EntityUid? Mind; + + /// + /// Action given for uplink implants. + /// + [DataField] + public EntProtoId Action = "ActionOpenContractsImplant"; + + [DataField, AutoNetworkedField] + public EntityUid? ActionId; +} diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 489a4c7bfa3..79e102dd7f1 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -164,7 +164,7 @@ - type: Store balance: Telecrystal: 0 - - type: Contracts # DeltaV - Forced 0 reputation to prevent buying bloodreds etc on it + - type: StoreContracts # DeltaV - can't buy bloodred with uplink implant - type: UserInterface interfaces: enum.StoreUiKey.Key: diff --git a/Resources/Prototypes/_DV/Actions/uplink.yml b/Resources/Prototypes/_DV/Actions/uplink.yml new file mode 100644 index 00000000000..97cf48f248e --- /dev/null +++ b/Resources/Prototypes/_DV/Actions/uplink.yml @@ -0,0 +1,8 @@ +- type: entity + parent: ActionOpenUplinkImplant + id: ActionOpenContractsImplant + name: Open Contract Hub + components: + - type: InstantAction + event: !type:OpenUiActionEvent + key: enum.ContractsUiKey.Key