using Content.Shared.Popups; using Content.Shared.Paint; using Content.Shared.Sprite; using Content.Shared.DoAfter; using Content.Shared.Interaction; using Robust.Shared.Audio.Systems; using Content.Shared.Humanoid; using Robust.Shared.Utility; using Content.Shared.Verbs; using Content.Shared.SubFloor; using Content.Shared.Inventory; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Whitelist; namespace Content.Server.Paint; /// /// Colors target and consumes reagent on each color success. /// public sealed class PaintSystem : SharedPaintSystem { [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly OpenableSystem _openable = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnPaint); SubscribeLocalEvent>(OnPaintVerb); } private void OnInteract(EntityUid uid, PaintComponent component, AfterInteractEvent args) { if (!args.CanReach || args.Target is not { Valid: true } target) return; PrepPaint(uid, component, target, args.User); } private void OnPaintVerb(EntityUid uid, PaintComponent component, GetVerbsEvent args) { if (!args.CanInteract || !args.CanAccess) return; var paintText = Loc.GetString("paint-verb"); var verb = new UtilityVerb() { Act = () => { PrepPaint(uid, component, args.Target, args.User); }, Text = paintText, Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/paint.svg.192dpi.png")) }; args.Verbs.Add(verb); } private void PrepPaint(EntityUid uid, PaintComponent component, EntityUid target, EntityUid user) => _doAfterSystem.TryStartDoAfter( new( EntityManager, user, component.Delay, new PaintDoAfterEvent(), uid, target: target, used: uid) { BreakOnMove = true, NeedHand = true, BreakOnHandChange = true, }); private void OnPaint(Entity entity, ref PaintDoAfterEvent args) { if (args.Target == null || args.Used == null || args.Handled || args.Cancelled || args.Target is not { Valid: true } target) return; Paint(entity, target, args.User); args.Handled = true; } public void Paint(Entity entity, EntityUid target, EntityUid user) { if (!_openable.IsOpen(entity)) { _popup.PopupEntity(Loc.GetString("paint-closed", ("used", entity)), user, user, PopupType.Medium); return; } if (HasComp(target) || HasComp(target)) { _popup.PopupEntity(Loc.GetString("paint-failure-painted", ("target", target)), user, user, PopupType.Medium); return; } if (_whitelist.IsWhitelistFail(entity.Comp.Whitelist, target) || _whitelist.IsBlacklistPass(entity.Comp.Blacklist, target) || HasComp(target) || HasComp(target)) { _popup.PopupEntity(Loc.GetString("paint-failure", ("target", target)), user, user, PopupType.Medium); return; } if (CanPaint(entity, target)) { EnsureComp(target, out var paint); EnsureComp(target); paint.Color = entity.Comp.Color; _audio.PlayPvs(entity.Comp.Spray, entity); paint.Enabled = true; // Paint any clothing the target is wearing if (HasComp(target) && _inventory.TryGetSlots(target, out var slotDefinitions)) { foreach (var slot in slotDefinitions) { if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt) || HasComp(slotEnt.Value) || _whitelist.IsWhitelistFail(entity.Comp.Whitelist, slotEnt.Value) || _whitelist.IsBlacklistPass(entity.Comp.Blacklist, slotEnt.Value) || HasComp(slotEnt.Value) || HasComp(slotEnt.Value)) continue; EnsureComp(slotEnt.Value, out var slotToPaint); EnsureComp(slotEnt.Value); slotToPaint.Color = entity.Comp.Color; _appearanceSystem.SetData(slotEnt.Value, PaintVisuals.Painted, true); Dirty(slotEnt.Value, slotToPaint); } } _popup.PopupEntity(Loc.GetString("paint-success", ("target", target)), user, user, PopupType.Medium); _appearanceSystem.SetData(target, PaintVisuals.Painted, true); Dirty(target, paint); return; } if (!CanPaint(entity, target)) _popup.PopupEntity(Loc.GetString("paint-empty", ("used", entity)), user, user, PopupType.Medium); } public void Paint(EntityWhitelist? whitelist, EntityWhitelist? blacklist, EntityUid target, Color color) { if (_whitelist.IsWhitelistFail(whitelist, target) || _whitelist.IsBlacklistPass(blacklist, target) || !HasComp(target)) // TODO: FIND THE STUPID RACE CONDITION THAT IS MAKING ME CHECK FOR THIS. return; EnsureComp(target, out var paint); EnsureComp(target); paint.Color = color; paint.Enabled = true; if (HasComp(target) && _inventory.TryGetSlots(target, out var slotDefinitions)) { foreach (var slot in slotDefinitions) { if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt) || _whitelist.IsWhitelistFail(whitelist, slotEnt.Value) || _whitelist.IsBlacklistPass(blacklist, slotEnt.Value)) continue; EnsureComp(slotEnt.Value, out var slotToPaint); EnsureComp(slotEnt.Value); slotToPaint.Color = color; _appearanceSystem.SetData(slotEnt.Value, PaintVisuals.Painted, true); Dirty(slotEnt.Value, slotToPaint); } } _appearanceSystem.SetData(target, PaintVisuals.Painted, true); Dirty(target, paint); } private bool CanPaint(Entity reagent, EntityUid target) { if (HasComp(target) || HasComp(target) || !_solutionContainer.TryGetSolution(reagent.Owner, reagent.Comp.Solution, out _, out var solution)) return false; var quantity = solution.RemoveReagent(reagent.Comp.Reagent, reagent.Comp.ConsumptionUnit); return (quantity > 0); } }