From f16ff3a2d943845853cee0238216135b704857a5 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:01:05 -0400 Subject: [PATCH] Rat King Milsim + Buffs (#20190) * rat king update * rummaging * buuuuunnnnncccchhh of shit * the last of it * make rat servants not ghost roles * pissma buff and cooldown --- Content.Client/RatKing/RatKingSystem.cs | 9 + .../Preconditions/HasOrdersPrecondition.cs | 13 ++ .../Operators/Combat/Melee/MeleeOperator.cs | 14 ++ Content.Server/NPC/NPCBlackboard.cs | 10 ++ .../Considerations/OrderedTargetCon.cs | 6 + .../NPC/Systems/NPCUtilitySystem.cs | 10 ++ .../Pointing/EntitySystems/PointingSystem.cs | 5 + Content.Server/RatKing/RatKingComponent.cs | 50 ------ Content.Server/RatKing/RatKingSystem.cs | 72 ++++++-- Content.Shared/Actions/SharedActionsSystem.cs | 13 ++ Content.Shared/Pointing/PointingEvents.cs | 14 ++ Content.Shared/RatKing/RatKingActions.cs | 9 + Content.Shared/RatKing/RatKingComponent.cs | 111 ++++++++++++ .../RatKing/RatKingRummageableComponent.cs | 42 +++++ .../RatKing/RatKingServantComponent.cs | 15 ++ Content.Shared/RatKing/SharedRatKingSystem.cs | 163 ++++++++++++++++++ .../en-US/animals/rat-king/rat-king.ftl | 2 + .../Prototypes/Datasets/rat_king_commands.yml | 26 +++ .../Markers/Spawners/Random/trash.yml | 11 ++ .../Entities/Mobs/NPCs/regalrat.yml | 131 +++++++++++--- .../Objects/Specific/rehydrateable.yml | 1 - .../Structures/Piping/Disposal/units.yml | 1 + Resources/Prototypes/GameRules/events.yml | 8 +- Resources/Prototypes/NPCs/Combat/melee.yml | 55 ++++++ Resources/Prototypes/NPCs/mob.yml | 10 ++ Resources/Prototypes/NPCs/regalrat.yml | 28 +++ Resources/Prototypes/NPCs/utility_queries.yml | 21 +++ Resources/Prototypes/Reagents/gases.yml | 7 +- .../Actions/actions_rat_king.rsi/attack.png | Bin 0 -> 636 bytes .../actions_rat_king.rsi/attackOff.png | Bin 0 -> 617 bytes .../Actions/actions_rat_king.rsi/follow.png | Bin 0 -> 432 bytes .../actions_rat_king.rsi/followOff.png | Bin 0 -> 437 bytes .../Actions/actions_rat_king.rsi/loose.png | Bin 0 -> 522 bytes .../Actions/actions_rat_king.rsi/looseOff.png | Bin 0 -> 529 bytes .../Actions/actions_rat_king.rsi/meta.json | 41 +++++ .../ratKingArmy.png | Bin .../ratKingDomain.png | Bin .../Actions/actions_rat_king.rsi/stay.png | Bin 0 -> 503 bytes .../Actions/actions_rat_king.rsi/stayOff.png | Bin 0 -> 483 bytes .../Textures/Interface/Actions/meta.json | 6 - .../Mobs/Animals/regalrat.rsi/critical.png | Bin 0 -> 942 bytes .../Mobs/Animals/regalrat.rsi/meta.json | 3 + 42 files changed, 801 insertions(+), 106 deletions(-) create mode 100644 Content.Client/RatKing/RatKingSystem.cs create mode 100644 Content.Server/NPC/HTN/Preconditions/HasOrdersPrecondition.cs create mode 100644 Content.Server/NPC/Queries/Considerations/OrderedTargetCon.cs delete mode 100644 Content.Server/RatKing/RatKingComponent.cs create mode 100644 Content.Shared/RatKing/RatKingComponent.cs create mode 100644 Content.Shared/RatKing/RatKingRummageableComponent.cs create mode 100644 Content.Shared/RatKing/RatKingServantComponent.cs create mode 100644 Content.Shared/RatKing/SharedRatKingSystem.cs create mode 100644 Resources/Prototypes/Datasets/rat_king_commands.yml create mode 100644 Resources/Prototypes/NPCs/regalrat.yml create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/attack.png create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/attackOff.png create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/follow.png create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/followOff.png create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/loose.png create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/looseOff.png create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/meta.json rename Resources/Textures/Interface/Actions/{ => actions_rat_king.rsi}/ratKingArmy.png (100%) rename Resources/Textures/Interface/Actions/{ => actions_rat_king.rsi}/ratKingDomain.png (100%) create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/stay.png create mode 100644 Resources/Textures/Interface/Actions/actions_rat_king.rsi/stayOff.png create mode 100644 Resources/Textures/Mobs/Animals/regalrat.rsi/critical.png diff --git a/Content.Client/RatKing/RatKingSystem.cs b/Content.Client/RatKing/RatKingSystem.cs new file mode 100644 index 0000000000..25cc9fdf55 --- /dev/null +++ b/Content.Client/RatKing/RatKingSystem.cs @@ -0,0 +1,9 @@ +using Content.Shared.RatKing; + +namespace Content.Client.RatKing; + +/// +public sealed class RatKingSystem : SharedRatKingSystem +{ + +} diff --git a/Content.Server/NPC/HTN/Preconditions/HasOrdersPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/HasOrdersPrecondition.cs new file mode 100644 index 0000000000..d2976c98a6 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/HasOrdersPrecondition.cs @@ -0,0 +1,13 @@ +namespace Content.Server.NPC.HTN.Preconditions; + +public sealed partial class HasOrdersPrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [DataField("orders", required: true)] public Enum Orders = default!; + + public override bool IsMet(NPCBlackboard blackboard) + { + return Equals(blackboard.GetValueOrDefault(NPCBlackboard.CurrentOrders, _entManager), Orders); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs index c40b037d98..32be027ec4 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs @@ -68,6 +68,20 @@ public sealed partial class MeleeOperator : HTNOperator, IHtnConditionalShutdown blackboard.Remove(TargetKey); } + public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.TaskShutdown(blackboard, status); + + ConditionalShutdown(blackboard); + } + + public override void PlanShutdown(NPCBlackboard blackboard) + { + base.PlanShutdown(blackboard); + + ConditionalShutdown(blackboard); + } + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) { base.Update(blackboard, frameTime); diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs index 294d9d8027..322ff0f85b 100644 --- a/Content.Server/NPC/NPCBlackboard.cs +++ b/Content.Server/NPC/NPCBlackboard.cs @@ -320,6 +320,16 @@ public sealed partial class NPCBlackboard : IEnumerable + /// A configurable "order" enum that can be given to an NPC from an external source. + /// + public const string CurrentOrders = "CurrentOrders"; + + /// + /// A configurable target that's ordered by external sources. + /// + public const string CurrentOrderedTarget = "CurrentOrderedTarget"; + public IEnumerator> GetEnumerator() { return _blackboard.GetEnumerator(); diff --git a/Content.Server/NPC/Queries/Considerations/OrderedTargetCon.cs b/Content.Server/NPC/Queries/Considerations/OrderedTargetCon.cs new file mode 100644 index 0000000000..be56d953c3 --- /dev/null +++ b/Content.Server/NPC/Queries/Considerations/OrderedTargetCon.cs @@ -0,0 +1,6 @@ +namespace Content.Server.NPC.Queries.Considerations; + +public sealed partial class OrderedTargetCon : UtilityConsideration +{ + +} diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index d9aec892ce..d764ab2c2e 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -191,6 +191,16 @@ public sealed class NPCUtilitySystem : EntitySystem return 1f; } + case OrderedTargetCon: + { + if (!blackboard.TryGetValue(NPCBlackboard.CurrentOrderedTarget, out var orderedTarget, EntityManager)) + return 0f; + + if (targetUid != orderedTarget) + return 0f; + + return 1f; + } case TargetAccessibleCon: { if (_container.TryGetContainingContainer(targetUid, out var container)) diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index dc610ef409..8756134310 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -204,6 +204,11 @@ namespace Content.Server.Pointing.EntitySystems viewerPointedAtMessage = Loc.GetString("pointing-system-point-at-you-other", ("otherName", playerName)); + var ev = new AfterPointedAtEvent(pointed); + RaiseLocalEvent(player, ref ev); + var gotev = new AfterGotPointedAtEvent(player); + RaiseLocalEvent(pointed, ref gotev); + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(player):user} pointed at {ToPrettyString(pointed):target} {Transform(pointed).Coordinates}"); } else diff --git a/Content.Server/RatKing/RatKingComponent.cs b/Content.Server/RatKing/RatKingComponent.cs deleted file mode 100644 index 22e9de4135..0000000000 --- a/Content.Server/RatKing/RatKingComponent.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.RatKing -{ - [RegisterComponent] - public sealed partial class RatKingComponent : Component - { - [DataField("actionRaiseArmy", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ActionRaiseArmy = "ActionRatKingRaiseArmy"; - - /// - /// The action for the Raise Army ability - /// - [DataField("actionRaiseArmyEntity")] public EntityUid? ActionRaiseArmyEntity; - - /// - /// The amount of hunger one use of Raise Army consumes - /// - [ViewVariables(VVAccess.ReadWrite), DataField("hungerPerArmyUse", required: true)] - public float HungerPerArmyUse = 25f; - - /// - /// The entity prototype of the mob that Raise Army summons - /// - [ViewVariables(VVAccess.ReadWrite), DataField("armyMobSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ArmyMobSpawnId = "MobRatServant"; - - [DataField("actionDomain", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ActionDomain = "ActionRatKingDomain"; - - /// - /// The action for the Domain ability - /// - [DataField("actionDomainEntity")] - public EntityUid? ActionDomainEntity; - - /// - /// The amount of hunger one use of Domain consumes - /// - [ViewVariables(VVAccess.ReadWrite), DataField("hungerPerDomainUse", required: true)] - public float HungerPerDomainUse = 50f; - - /// - /// How many moles of Miasma are released after one us of Domain - /// - [DataField("molesMiasmaPerDomain")] - public float MolesMiasmaPerDomain = 100f; - } -} diff --git a/Content.Server/RatKing/RatKingSystem.cs b/Content.Server/RatKing/RatKingSystem.cs index cd3cbbb691..3cf6d48751 100644 --- a/Content.Server/RatKing/RatKingSystem.cs +++ b/Content.Server/RatKing/RatKingSystem.cs @@ -1,19 +1,30 @@ -using Content.Server.Actions; +using System.Numerics; using Content.Server.Atmos.EntitySystems; +using Content.Server.Chat.Systems; +using Content.Server.NPC; +using Content.Server.NPC.HTN; +using Content.Server.NPC.Systems; using Content.Server.Popups; using Content.Shared.Atmos; +using Content.Shared.Dataset; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Pointing; using Content.Shared.RatKing; using Robust.Server.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Random; namespace Content.Server.RatKing { - public sealed class RatKingSystem : EntitySystem + /// + public sealed class RatKingSystem : SharedRatKingSystem { - [Dependency] private readonly ActionsSystem _action = default!; [Dependency] private readonly AtmosphereSystem _atmos = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly HTNSystem _htn = default!; [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly NPCSystem _npc = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly TransformSystem _xform = default!; @@ -21,16 +32,9 @@ namespace Content.Server.RatKing { base.Initialize(); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnRaiseArmy); SubscribeLocalEvent(OnDomain); - } - - private void OnMapInit(EntityUid uid, RatKingComponent component, MapInitEvent args) - { - _action.AddAction(uid, ref component.ActionRaiseArmyEntity, component.ActionRaiseArmy); - _action.AddAction(uid, ref component.ActionDomainEntity, component.ActionDomain); + SubscribeLocalEvent(OnPointedAt); } /// @@ -52,7 +56,14 @@ namespace Content.Server.RatKing } args.Handled = true; _hunger.ModifyHunger(uid, -component.HungerPerArmyUse, hunger); - Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates); //spawn the little mouse boi + var servant = Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates); + var comp = EnsureComp(servant); + comp.King = uid; + Dirty(servant, comp); + + component.Servants.Add(servant); + _npc.SetBlackboard(servant, NPCBlackboard.FollowTarget, new EntityCoordinates(uid, Vector2.Zero)); + UpdateServantNpc(servant, component.CurrentOrder); } /// @@ -83,5 +94,42 @@ namespace Content.Server.RatKing var tileMix = _atmos.GetTileMixture(transform.GridUid, transform.MapUid, indices, true); tileMix?.AdjustMoles(Gas.Miasma, component.MolesMiasmaPerDomain); } + + private void OnPointedAt(EntityUid uid, RatKingComponent component, ref AfterPointedAtEvent args) + { + if (component.CurrentOrder != RatKingOrderType.CheeseEm) + return; + + foreach (var servant in component.Servants) + { + _npc.SetBlackboard(servant, NPCBlackboard.CurrentOrderedTarget, args.Pointed); + } + } + + public override void UpdateServantNpc(EntityUid uid, RatKingOrderType orderType) + { + base.UpdateServantNpc(uid, orderType); + + if (!TryComp(uid, out var htn)) + return; + + if (htn.Plan != null) + _htn.ShutdownPlan(htn); + + _npc.SetBlackboard(uid, NPCBlackboard.CurrentOrders, orderType); + _htn.Replan(htn); + } + + public override void DoCommandCallout(EntityUid uid, RatKingComponent component) + { + base.DoCommandCallout(uid, component); + + if (!component.OrderCallouts.TryGetValue(component.CurrentOrder, out var datasetId) || + !PrototypeManager.TryIndex(datasetId, out var datasetPrototype)) + return; + + var msg = Random.Pick(datasetPrototype.Values); + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true); + } } } diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 3d476dd48c..dc0afc8af7 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -204,6 +204,19 @@ public abstract class SharedActionsSystem : EntitySystem Dirty(actionId.Value, action); } + public void StartUseDelay(EntityUid? actionId) + { + if (actionId == null) + return; + + var action = GetActionData(actionId); + if (action == null || action.UseDelay == null) + return; + + action.Cooldown = (GameTiming.CurTime, GameTiming.CurTime + action.UseDelay.Value); + Dirty(actionId.Value, action); + } + #region ComponentStateManagement public virtual void Dirty(EntityUid? actionId) { diff --git a/Content.Shared/Pointing/PointingEvents.cs b/Content.Shared/Pointing/PointingEvents.cs index 90c31bcfec..878bd35c7e 100644 --- a/Content.Shared/Pointing/PointingEvents.cs +++ b/Content.Shared/Pointing/PointingEvents.cs @@ -17,3 +17,17 @@ public sealed class PointingAttemptEvent : EntityEventArgs Target = target; } } + +/// +/// Raised on the entity who is pointing after they point at something. +/// +/// +[ByRefEvent] +public readonly record struct AfterPointedAtEvent(EntityUid Pointed); + +/// +/// Raised on an entity after they are pointed at by another entity. +/// +/// +[ByRefEvent] +public readonly record struct AfterGotPointedAtEvent(EntityUid Pointer); diff --git a/Content.Shared/RatKing/RatKingActions.cs b/Content.Shared/RatKing/RatKingActions.cs index 551b27a7a4..e2031e972b 100644 --- a/Content.Shared/RatKing/RatKingActions.cs +++ b/Content.Shared/RatKing/RatKingActions.cs @@ -11,3 +11,12 @@ public sealed partial class RatKingDomainActionEvent : InstantActionEvent { } + +public sealed partial class RatKingOrderActionEvent : InstantActionEvent +{ + /// + /// The type of order being given + /// + [DataField("type")] + public RatKingOrderType Type; +} diff --git a/Content.Shared/RatKing/RatKingComponent.cs b/Content.Shared/RatKing/RatKingComponent.cs new file mode 100644 index 0000000000..43c2858de8 --- /dev/null +++ b/Content.Shared/RatKing/RatKingComponent.cs @@ -0,0 +1,111 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.RatKing; + +[RegisterComponent, NetworkedComponent, Access(typeof(SharedRatKingSystem))] +[AutoGenerateComponentState] +public sealed partial class RatKingComponent : Component +{ + [DataField("actionRaiseArmy", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionRaiseArmy = "ActionRatKingRaiseArmy"; + + /// + /// The action for the Raise Army ability + /// + [DataField("actionRaiseArmyEntity")] + public EntityUid? ActionRaiseArmyEntity; + + /// + /// The amount of hunger one use of Raise Army consumes + /// + [ViewVariables(VVAccess.ReadWrite), DataField("hungerPerArmyUse", required: true)] + public float HungerPerArmyUse = 25f; + + /// + /// The entity prototype of the mob that Raise Army summons + /// + [ViewVariables(VVAccess.ReadWrite), DataField("armyMobSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ArmyMobSpawnId = "MobRatServant"; + + [DataField("actionDomain", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionDomain = "ActionRatKingDomain"; + + /// + /// The action for the Domain ability + /// + [DataField("actionDomainEntity")] + public EntityUid? ActionDomainEntity; + + /// + /// The amount of hunger one use of Domain consumes + /// + [DataField("hungerPerDomainUse", required: true), ViewVariables(VVAccess.ReadWrite)] + public float HungerPerDomainUse = 50f; + + /// + /// How many moles of Miasma are released after one us of Domain + /// + [DataField("molesMiasmaPerDomain"), ViewVariables(VVAccess.ReadWrite)] + public float MolesMiasmaPerDomain = 100f; + + /// + /// The current order that the Rat King assigned. + /// + [DataField("currentOrders"), ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public RatKingOrderType CurrentOrder = RatKingOrderType.Loose; + + /// + /// The servants that the rat king is currently controlling + /// + [DataField("servants")] + public HashSet Servants = new(); + + [DataField("actionOrderStay", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionOrderStay = "ActionRatKingOrderStay"; + + [DataField("actionOrderStayEntity")] + public EntityUid? ActionOrderStayEntity; + + [DataField("actionOrderFollow", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionOrderFollow = "ActionRatKingOrderFollow"; + + [DataField("actionOrderFollowEntity")] + public EntityUid? ActionOrderFollowEntity; + + [DataField("actionOrderCheeseEm", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionOrderCheeseEm = "ActionRatKingOrderCheeseEm"; + + [DataField("actionOrderCheeseEmEntity")] + public EntityUid? ActionOrderCheeseEmEntity; + + [DataField("actionOrderLoose", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionOrderLoose = "ActionRatKingOrderLoose"; + + [DataField("actionOrderLooseEntity")] + public EntityUid? ActionOrderLooseEntity; + + /// + /// A dictionary with an order type to the corresponding callout dataset. + /// + [DataField("orderCallouts")] + public Dictionary OrderCallouts = new() + { + { RatKingOrderType.Stay, "RatKingCommandStay" }, + { RatKingOrderType.Follow, "RatKingCommandFollow" }, + { RatKingOrderType.CheeseEm, "RatKingCommandCheeseEm" }, + { RatKingOrderType.Loose, "RatKingCommandLoose" } + }; +} + +[Serializable, NetSerializable] +public enum RatKingOrderType : byte +{ + Stay, + Follow, + CheeseEm, + Loose +} diff --git a/Content.Shared/RatKing/RatKingRummageableComponent.cs b/Content.Shared/RatKing/RatKingRummageableComponent.cs new file mode 100644 index 0000000000..f3a389ef76 --- /dev/null +++ b/Content.Shared/RatKing/RatKingRummageableComponent.cs @@ -0,0 +1,42 @@ +using Content.Shared.Random; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.RatKing; + +/// +/// This is used for entities that can be +/// rummaged through by the rat king to get loot. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedRatKingSystem))] +[AutoGenerateComponentState] +public sealed partial class RatKingRummageableComponent : Component +{ + /// + /// Whether or not this entity has been rummaged through already. + /// + [DataField("looted"), ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public bool Looted; + + /// + /// How long it takes to rummage through a rummageable container. + /// + [DataField("rummageDuration"), ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public float RummageDuration = 3f; + + /// + /// A weighted random entity prototype containing the different loot that rummaging can provide. + /// + [DataField("rummageLoot", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public string RummageLoot = "RatKingLoot"; + + /// + /// Sound played on rummage completion. + /// + [DataField("sound")] + public SoundSpecifier? Sound = new SoundCollectionSpecifier("storageRustle"); +} diff --git a/Content.Shared/RatKing/RatKingServantComponent.cs b/Content.Shared/RatKing/RatKingServantComponent.cs new file mode 100644 index 0000000000..f6e8d0110e --- /dev/null +++ b/Content.Shared/RatKing/RatKingServantComponent.cs @@ -0,0 +1,15 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.RatKing; + +[RegisterComponent, NetworkedComponent, Access(typeof(SharedRatKingSystem))] +[AutoGenerateComponentState] +public sealed partial class RatKingServantComponent : Component +{ + /// + /// The king this rat belongs to. + /// + [DataField("king")] + [AutoNetworkedField] + public EntityUid? King; +} diff --git a/Content.Shared/RatKing/SharedRatKingSystem.cs b/Content.Shared/RatKing/SharedRatKingSystem.cs new file mode 100644 index 0000000000..2d39815387 --- /dev/null +++ b/Content.Shared/RatKing/SharedRatKingSystem.cs @@ -0,0 +1,163 @@ +using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Content.Shared.Random; +using Content.Shared.Random.Helpers; +using Content.Shared.Verbs; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Serialization; + +namespace Content.Shared.RatKing; + +public abstract class SharedRatKingSystem : EntitySystem +{ + [Dependency] private readonly INetManager _net = default!; + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; + [Dependency] protected readonly IRobustRandom Random = default!; + [Dependency] private readonly SharedActionsSystem _action = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnOrderAction); + + SubscribeLocalEvent(OnServantShutdown); + + SubscribeLocalEvent>(OnGetVerb); + SubscribeLocalEvent(OnDoAfterComplete); + } + + private void OnStartup(EntityUid uid, RatKingComponent component, ComponentStartup args) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.AddAction(uid, ref component.ActionRaiseArmyEntity, component.ActionRaiseArmy, holderComp: comp); + _action.AddAction(uid, ref component.ActionDomainEntity, component.ActionDomain, holderComp: comp); + _action.AddAction(uid, ref component.ActionOrderStayEntity, component.ActionOrderStay, holderComp: comp); + _action.AddAction(uid, ref component.ActionOrderFollowEntity, component.ActionOrderFollow, holderComp: comp); + _action.AddAction(uid, ref component.ActionOrderCheeseEmEntity, component.ActionOrderCheeseEm, holderComp: comp); + _action.AddAction(uid, ref component.ActionOrderLooseEntity, component.ActionOrderLoose, holderComp: comp); + + UpdateActions(uid, component); + } + + private void OnShutdown(EntityUid uid, RatKingComponent component, ComponentShutdown args) + { + foreach (var servant in component.Servants) + { + if (TryComp(servant, out RatKingServantComponent? servantComp)) + servantComp.King = null; + } + + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, component.ActionRaiseArmyEntity, comp); + _action.RemoveAction(uid, component.ActionDomainEntity, comp); + _action.RemoveAction(uid, component.ActionOrderStayEntity, comp); + _action.RemoveAction(uid, component.ActionOrderFollowEntity, comp); + _action.RemoveAction(uid, component.ActionOrderCheeseEmEntity, comp); + _action.RemoveAction(uid, component.ActionOrderLooseEntity, comp); + } + + private void OnOrderAction(EntityUid uid, RatKingComponent component, RatKingOrderActionEvent args) + { + if (component.CurrentOrder == args.Type) + return; + args.Handled = true; + + component.CurrentOrder = args.Type; + Dirty(uid, component); + + DoCommandCallout(uid, component); + UpdateActions(uid, component); + UpdateAllServants(uid, component); + } + + private void OnServantShutdown(EntityUid uid, RatKingServantComponent component, ComponentShutdown args) + { + if (TryComp(component.King, out RatKingComponent? ratKingComponent)) + ratKingComponent.Servants.Remove(uid); + } + + private void UpdateActions(EntityUid uid, RatKingComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + _action.SetToggled(component.ActionOrderStayEntity, component.CurrentOrder == RatKingOrderType.Stay); + _action.SetToggled(component.ActionOrderFollowEntity, component.CurrentOrder == RatKingOrderType.Follow); + _action.SetToggled(component.ActionOrderCheeseEmEntity, component.CurrentOrder == RatKingOrderType.CheeseEm); + _action.SetToggled(component.ActionOrderLooseEntity, component.CurrentOrder == RatKingOrderType.Loose); + _action.StartUseDelay(component.ActionOrderStayEntity); + _action.StartUseDelay(component.ActionOrderFollowEntity); + _action.StartUseDelay(component.ActionOrderCheeseEmEntity); + _action.StartUseDelay(component.ActionOrderLooseEntity); + } + + private void OnGetVerb(EntityUid uid, RatKingRummageableComponent component, GetVerbsEvent args) + { + if (!HasComp(args.User) || component.Looted) + return; + + args.Verbs.Add(new AlternativeVerb + { + Text = Loc.GetString("rat-king-rummage-text"), + Priority = 0, + Act = () => + { + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.RummageDuration, + new RatKingRummageDoAfterEvent(), uid, uid) + { + BlockDuplicate = true, + BreakOnDamage = true, + BreakOnUserMove = true + }); + } + }); + } + + private void OnDoAfterComplete(EntityUid uid, RatKingRummageableComponent component, RatKingRummageDoAfterEvent args) + { + if (args.Cancelled || component.Looted) + return; + + component.Looted = true; + Dirty(uid, component); + _audio.PlayPvs(component.Sound, uid); + + var spawn = PrototypeManager.Index(component.RummageLoot).Pick(Random); + if (_net.IsServer) + Spawn(spawn, Transform(uid).Coordinates); + } + + public void UpdateAllServants(EntityUid uid, RatKingComponent component) + { + foreach (var servant in component.Servants) + { + UpdateServantNpc(servant, component.CurrentOrder); + } + } + + public virtual void UpdateServantNpc(EntityUid uid, RatKingOrderType orderType) + { + + } + + public virtual void DoCommandCallout(EntityUid uid, RatKingComponent component) + { + + } +} + +[Serializable, NetSerializable] +public sealed partial class RatKingRummageDoAfterEvent : SimpleDoAfterEvent +{ + +} diff --git a/Resources/Locale/en-US/animals/rat-king/rat-king.ftl b/Resources/Locale/en-US/animals/rat-king/rat-king.ftl index 1c587f4328..f6235a2708 100644 --- a/Resources/Locale/en-US/animals/rat-king/rat-king.ftl +++ b/Resources/Locale/en-US/animals/rat-king/rat-king.ftl @@ -1,3 +1,5 @@ rat-king-domain-popup = A cloud of miasma is released into the air! rat-king-too-hungry = You are too hungry to use this ability! + +rat-king-rummage-text = Rummage diff --git a/Resources/Prototypes/Datasets/rat_king_commands.yml b/Resources/Prototypes/Datasets/rat_king_commands.yml new file mode 100644 index 0000000000..c53d1e135b --- /dev/null +++ b/Resources/Prototypes/Datasets/rat_king_commands.yml @@ -0,0 +1,26 @@ +- type: dataset + id: RatKingCommandStay + values: + - "Sit!" + - "Stay!" + - "Stop!" + +- type: dataset + id: RatKingCommandFollow + values: + - "Heel!" + - "Follow!" + +- type: dataset + id: RatKingCommandCheeseEm + values: + - "Attack!" + - "Sic!" + - "Kill!" + - "Cheese 'Em!" + +- type: dataset + id: RatKingCommandLoose + values: + - "Free!" + - "Loose!" diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/trash.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/trash.yml index 60a7098328..4806755a11 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/trash.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/trash.yml @@ -2,6 +2,7 @@ name: Trash Spawner id: RandomSpawner parent: MarkerBase + suffix: 50 components: - type: Sprite layers: @@ -35,3 +36,13 @@ offset: 0.2 placement: mode: AlignTileAny + +- type: entity + parent: RandomSpawner + id: RandomSpawner100 + suffix: 100 + placement: + mode: AlignTileAny + components: + - type: RandomSpawner + chance: 1 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index 9472954cbb..a421a42abc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -21,7 +21,7 @@ factions: - SimpleHostile - type: Sprite - drawdepth: Mobs + drawdepth: SmallMobs sprite: Mobs/Animals/regalrat.rsi layers: - map: ["enum.DamageStateVisualLayers.Base"] @@ -78,6 +78,8 @@ states: Alive: Base: regalrat + Critical: + Base: critical Dead: Base: dead - type: GhostRole @@ -89,7 +91,6 @@ - type: Tag tags: - CannotSuicide - - DoorBumpOpener - FootstepSound - type: NoSlip - type: RatKing @@ -118,7 +119,6 @@ suffix: Buff components: - type: Sprite - drawdepth: Mobs sprite: Mobs/Animals/buffrat.rsi scale: 1.2, 1.2 layers: @@ -139,17 +139,6 @@ damage: types: Blunt: 66 #oof ouch owie my bones - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeCircle - radius: 0.35 - density: 400 - mask: - - MobMask - layer: - - MobLayer - type: SlowOnDamage speedModifierThresholds: 200: 0.7 @@ -163,6 +152,7 @@ id: MobRatServant parent: SimpleMobBase description: He's da mini rat. He don't make da roolz. + noSpawn: true #Must be configured to a King or the AI breaks. components: - type: CombatMode - type: MovementSpeedModifier @@ -172,7 +162,14 @@ - type: MobMover - type: HTN rootTask: - task: SimpleHostileCompound + task: RatServantCompound + blackboard: + IdleRange: !type:Single + 3.5 + FollowCloseRange: !type:Single + 2.0 + FollowRange: !type:Single + 3.0 - type: Reactive groups: Flammable: [Touch] @@ -254,16 +251,9 @@ Female: Mouse Unsexed: Mouse wilhelmProbability: 0.001 - - type: GhostRole - makeSentient: true - name: ghost-role-information-rat-servant-name - description: ghost-role-information-rat-servant-description - rules: ghost-role-information-rat-servant-rules - - type: GhostTakeoverAvailable - type: Tag tags: - CannotSuicide - - DoorBumpOpener - FootstepSound - type: NoSlip - type: MobPrice @@ -276,6 +266,13 @@ guides: - MinorAntagonists +- type: weightedRandomEntity + id: RatKingLoot + weights: + RandomSpawner100: 66 #garbage + FoodCheese: 28 #food + IngotGold1: 5 #loot + - type: entity id: ActionRatKingRaiseArmy name: Raise Army @@ -283,10 +280,11 @@ noSpawn: true components: - type: InstantAction - icon: Interface/Actions/ratKingArmy.png - itemIconStyle: NoItem - event: !type:RatKingRaiseArmyActionEvent useDelay: 4 + icon: + sprite: Interface/Actions/actions_rat_king.rsi + state: ratKingArmy + event: !type:RatKingRaiseArmyActionEvent - type: entity id: ActionRatKingDomain @@ -295,7 +293,84 @@ noSpawn: true components: - type: InstantAction - useDelay: 10 - icon: Interface/Actions/ratKingDomain.png - itemIconStyle: NoItem + useDelay: 6 + icon: + sprite: Interface/Actions/actions_rat_king.rsi + state: ratKingDomain event: !type:RatKingDomainActionEvent + +- type: entity + id: ActionRatKingOrderStay + name: Stay + description: Command your army to stand in place. + noSpawn: true + components: + - type: InstantAction + useDelay: 1 + icon: + sprite: Interface/Actions/actions_rat_king.rsi + state: stayOff + iconOn: + sprite: Interface/Actions/actions_rat_king.rsi + state: stay + event: + !type:RatKingOrderActionEvent + type: Stay + priority: 5 + +- type: entity + id: ActionRatKingOrderFollow + name: Follow + description: Command your army to follow you around. + noSpawn: true + components: + - type: InstantAction + useDelay: 1 + icon: + sprite: Interface/Actions/actions_rat_king.rsi + state: followOff + iconOn: + sprite: Interface/Actions/actions_rat_king.rsi + state: follow + event: + !type:RatKingOrderActionEvent + type: Follow + priority: 6 + +- type: entity + id: ActionRatKingOrderCheeseEm + name: Cheese 'Em + description: Command your army to attack whoever you point at. + noSpawn: true + components: + - type: InstantAction + useDelay: 1 + icon: + sprite: Interface/Actions/actions_rat_king.rsi + state: attackOff + iconOn: + sprite: Interface/Actions/actions_rat_king.rsi + state: attack + event: + !type:RatKingOrderActionEvent + type: CheeseEm + priority: 7 + +- type: entity + id: ActionRatKingOrderLoose + name: Loose + description: Command your army to act at their own will. + noSpawn: true + components: + - type: InstantAction + useDelay: 1 + icon: + sprite: Interface/Actions/actions_rat_king.rsi + state: looseOff + iconOn: + sprite: Interface/Actions/actions_rat_king.rsi + state: loose + event: + !type:RatKingOrderActionEvent + type: Loose + priority: 8 diff --git a/Resources/Prototypes/Entities/Objects/Specific/rehydrateable.yml b/Resources/Prototypes/Entities/Objects/Specific/rehydrateable.yml index 6521fdf61c..8a2e4a29ce 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/rehydrateable.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/rehydrateable.yml @@ -132,7 +132,6 @@ solution: cube - type: Rehydratable possibleSpawns: - - MobRatServant - MobCarpHolo - MobXenoRavager - MobAngryBee diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml index c8792bc3ba..40fb09f7ef 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml @@ -86,6 +86,7 @@ interfaces: - key: enum.DisposalUnitUiKey.Key type: DisposalUnitBoundUserInterface + - type: RatKingRummageable - type: entity id: MailingUnit diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index a8f5d50684..23732b9762 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -176,13 +176,11 @@ - type: VentCrittersRule entries: - id: MobMouse - prob: 0.015 + prob: 0.02 - id: MobMouse1 - prob: 0.015 + prob: 0.02 - id: MobMouse2 - prob: 0.015 - - id: MobRatServant - prob: 0.015 + prob: 0.02 specialEntries: - id: SpawnPointGhostRatKing prob: 0.005 diff --git a/Resources/Prototypes/NPCs/Combat/melee.yml b/Resources/Prototypes/NPCs/Combat/melee.yml index e35dc82050..282e794ee7 100644 --- a/Resources/Prototypes/NPCs/Combat/melee.yml +++ b/Resources/Prototypes/NPCs/Combat/melee.yml @@ -25,6 +25,28 @@ - !type:HTNCompoundTask task: MeleeAttackTargetCompound +- type: htnCompound + id: RatServantCombatCompound + branches: + - preconditions: + - !type:ActiveHandComponentPrecondition + components: + - type: MeleeWeapon + damage: + types: + Brute: 0 + invert: true + tasks: + - !type:HTNCompoundTask + task: PickupMeleeCompound + + - tasks: + - !type:HTNPrimitiveTask + operator: !type:UtilityOperator + proto: OrderedTargets + - !type:HTNCompoundTask + task: MeleeAttackOrderedTargetCompound + - type: htnCompound id: PickupMeleeCompound branches: @@ -79,3 +101,36 @@ id: MeleeService proto: NearbyMeleeTargets key: Target + +- type: htnCompound + id: MeleeAttackOrderedTargetCompound + preconditions: + - !type:KeyExistsPrecondition + key: Target + branches: + - tasks: + - !type:HTNPrimitiveTask + operator: !type:MoveToOperator + shutdownState: PlanFinished + pathfindInPlanning: true + removeKeyOnFinish: false + targetKey: TargetCoordinates + pathfindKey: TargetPathfind + rangeKey: MeleeRange + - !type:HTNPrimitiveTask + operator: !type:JukeOperator + jukeType: Away + - !type:HTNPrimitiveTask + operator: !type:MeleeOperator + targetKey: Target + preconditions: + - !type:KeyExistsPrecondition + key: Target + - !type:TargetInRangePrecondition + targetKey: Target + rangeKey: MeleeRange + services: + - !type:UtilityService + id: MeleeService + proto: OrderedTargets + key: Target diff --git a/Resources/Prototypes/NPCs/mob.yml b/Resources/Prototypes/NPCs/mob.yml index 61ab47c5b2..bba5a76ad8 100644 --- a/Resources/Prototypes/NPCs/mob.yml +++ b/Resources/Prototypes/NPCs/mob.yml @@ -9,6 +9,16 @@ - !type:HTNCompoundTask task: IdleCompound +- type: htnCompound + id: RatServantTargetAttackCompound + branches: + - tasks: + - !type:HTNCompoundTask + task: RatServantCombatCompound + - tasks: + - !type:HTNCompoundTask + task: IdleCompound + - type: htnCompound id: MouseCompound branches: diff --git a/Resources/Prototypes/NPCs/regalrat.yml b/Resources/Prototypes/NPCs/regalrat.yml new file mode 100644 index 0000000000..8685a51236 --- /dev/null +++ b/Resources/Prototypes/NPCs/regalrat.yml @@ -0,0 +1,28 @@ +- type: htnCompound + id: RatServantCompound + branches: + - preconditions: + - !type:HasOrdersPrecondition + orders: enum.RatKingOrderType.Stay + tasks: + - !type:HTNCompoundTask + task: IdleCompound + - preconditions: + - !type:HasOrdersPrecondition + orders: enum.RatKingOrderType.Follow + tasks: + - !type:HTNCompoundTask + task: FollowCompound + - preconditions: + - !type:HasOrdersPrecondition + orders: enum.RatKingOrderType.CheeseEm + tasks: + - !type:HTNCompoundTask + task: RatServantTargetAttackCompound + - preconditions: + - !type:HasOrdersPrecondition + orders: enum.RatKingOrderType.Loose + tasks: + - !type:HTNCompoundTask + task: SimpleHostileCompound + diff --git a/Resources/Prototypes/NPCs/utility_queries.yml b/Resources/Prototypes/NPCs/utility_queries.yml index b6e037fdd0..d62e1a7e8c 100644 --- a/Resources/Prototypes/NPCs/utility_queries.yml +++ b/Resources/Prototypes/NPCs/utility_queries.yml @@ -99,6 +99,27 @@ - !type:TargetInLOSOrCurrentCon curve: !type:BoolCurve +- type: utilityQuery + id: OrderedTargets + query: + - !type:NearbyHostilesQuery + considerations: + - !type:TargetIsAliveCon + curve: !type:BoolCurve + - !type:TargetDistanceCon + curve: !type:PresetCurve + preset: TargetDistance + - !type:TargetHealthCon + curve: !type:PresetCurve + preset: TargetHealth + - !type:TargetAccessibleCon + curve: !type:BoolCurve + - !type:TargetInLOSOrCurrentCon + curve: !type:BoolCurve + # they gotta be what we ordered + - !type:OrderedTargetCon + curve: !type:BoolCurve + - type: utilityQuery id: NearbyMeleeWeapons query: diff --git a/Resources/Prototypes/Reagents/gases.yml b/Resources/Prototypes/Reagents/gases.yml index f3f298a721..9fa06f632c 100644 --- a/Resources/Prototypes/Reagents/gases.yml +++ b/Resources/Prototypes/Reagents/gases.yml @@ -234,10 +234,9 @@ scaleByQuantity: true ignoreResistances: true damage: - types: - Blunt: -4 - Slash: -3 - Piercing: -3 + Groups: + Brute: -5 + Burn: -5 - type: reagent id: NitrousOxide diff --git a/Resources/Textures/Interface/Actions/actions_rat_king.rsi/attack.png b/Resources/Textures/Interface/Actions/actions_rat_king.rsi/attack.png new file mode 100644 index 0000000000000000000000000000000000000000..35f663c66ee9512af33b23bf681256c3da238358 GIT binary patch literal 636 zcmV-?0)zdDP)Px%Hc3Q5R9J=0S1oS>K@fd37)S$x9_qvfL2{y^;*$Og(jS3rY7vzcSc0tUA7~K$ z163p|Dw>Am1UYev3j#ulYz54AcG>%YqqI-$$LzkhGqZEIz#pRo0C$&HU)@^uq{NTQ zPYmAQN@W1()~a}1eq!SKCn5JDiEgcm0e~|aJQcagb(Uh63g5qm?_U=dQ_dm*04c{B zSr|?chEruD<@jI$i5?)rFr311n%bNYEeO$4^F@#=1Vom1{AdEgaH>dF&qLh5K4JAd z)NJB-_B#Tk9Pga4H(w}WgrbR<=>Z!q-d?o6nIlIN5XZCBB%?VYS_g|bxDFf(sRGPX z^&55c8+C20s25TT5hoEhw|Ln0&yk!Tj?>)hW+V!5oF;^5DWW98q6I0_12zJFJx$_$ z>q4pkUWm95PFl1OB?^e+862mX>x83qpxT{E8(t)}WbIViJ2sgDlE`d?=BE8dUF+M3 z37K_(lbOVP!_hi`7fAq{l08}n+L&oW&^*9h*PAcYpNm?=+&WqZnoXzD2Hefu#P!i3 zyGEee+{P+^yN)k3i;rzH@{Sqr-h83#xQ$i74uR3Og~JSmxt7Rn@dr|JfRow8abk)$ zZ&Tlkq&8>ffF^(^4}JS>o-jeSra)j5KH9f8U+jca6EJaowRoT3^oi?hBFvN0Uba1R zeT?#oAlR~f?;I!so&#A>9`GW0l-E(}jlfT-#`Y~*1~w1HZmkL|KMD)Th3#APU*H=7 W1|Qai*4Oa>0000Px%BS}O-R9J<@SHFwmKotIV2`R6O;8uf$?G_?zY~>VT!FK<|ZG{CfVr%4rg{_55 zwFd?c_DTud6*9ALUM7=h-0WA)k2mvu^WK}63GmIh1AynBqkp|Y*eLOK9ph~o-FX1e z8-&=dW1KwE2)Un0^adf`mJ#mNU{mCr>%Q2`jeh?L{r*#JFiYS^Ud@oNMm z$tHKg`6AN76zV2osRwNM`2EZ1^BlREfHXZilT6JC(Yad0)pg)ja0=k3nv6%7j7P>; zT`xEb5hZyHU-16-+xyyPKRCHo~`!qGifC2Y_-qXlnN*!vk~%5C*zUPw-GCp)&WY! ziRp&ZVHbWT0h~+rbl5ezB^S6Ivh-1%G)NA)v0Ck~S|s zYBP|{ly`W%=Zi?&Q5&m(906n;WPx$Px$YDq*vR9J=WS3zonKoorjFHm9J1tdbZF3QkcAkejk$UY}=?;%_lyg-9wk!`^u zbe92hgmj@Z{f=X!CbeDoAjJ9k|NZyhj6s1vh6Ml*&)W+N!buy8V}kQOwrl`kK?re7 zz@2HDXJys%ypOSv!Ks?-y%ybR(_j`b3z!AW0%ifd1u*7{<{+VfV( zvbVd`^md>qJ`@oEaK64v&5r^YbHVvq5lBJ_6XDRBP62Z7=oHja6Zx1L;~9`d`V@36 z#OdZ5KyyHzT0ODL&apKV4F@QR5GSKzJvOF>qTxXKv}=6yI9;2rujQK7tux>pqEgCzFxe~0BHucYAs0000IHOIb#+);K$!FZWu>l6}u?jqD5O}Wl(N1RfdV!_IcFRlp{{E{K@XKdE^{Jbi zvEjKc`@#hw#OGUoMbG^ zdU`<5y3|rhimyW{v@{@0XZ_0p7ZaI^$`}*7PrEF_d-IR3dbZa1!>?~Ql~gjr`oj}n zm*0(OyB2)FYQ{XTePvrR-*11mE@EeKTOK=Gjqm!|kstB}&Hr;wHhH|xpqkMnCMh{I zXHr^3{I=x1T+&hpo@R-;UbOuG<63?o^S{G9g)-517z@w;mrBx;UZLNWroY@+WcQtf decC@5Kf3LjsJlvKHZUF-JYD@<);T3K0RXFdvZ(+7 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_rat_king.rsi/loose.png b/Resources/Textures/Interface/Actions/actions_rat_king.rsi/loose.png new file mode 100644 index 0000000000000000000000000000000000000000..bfbc45daed736351fa95f3516abb61ff2853b397 GIT binary patch literal 522 zcmV+l0`>igP)Px$#7RU!R9J=Wmp^X8FcikWAZ}2?Y(uiuUX z0Pk=2`&qcd$JetJ@kV`)PP>AK$4{q3Nn7HwRw-wkmG}p^2*7o#aNX)z+Zq4>2;sr^ zTM)uaL9769Q4T|WML`MyWGx=Yn-qeaF$F1`iu-;mvrl$}=aOWbyRNYValFy?tH$V@ zFa}7Htx|vsXPu3_FfIW>kfnTQvJeLn!pkkgP5y|uGqM}H(p?3Rw{z3D+5wdqf#`M* z6g&eeA*!qf{^YZoMzjfk(#(^kWWH66IU0BZbx;3SU zDpv>q;9s(<%-m$U#(0FZP5lI``trWX8Q?(u07p@z?Slhq7cv2OedV12>Kv~(<>a;G zNMA;S;giWGZx+MB;`C+IsL!p5bFDIPcqkqorwdC670kk&B`r<-0NRw@ghrZZ&j0`b M07*qoM6N<$g5nPx$%Sl8*R9J=Wm*0wlKp4f3i}VHt-3?y!0^yR~#umYXj}$$U5Qv7i87sYj7vbGP zzCi@-MSU_&Gdlg**_%B#ELVwT1HfS9W4q39 zu$d9_&auQ`GQ{ zFf?{A$Xx)c$;)zALr^lhAhxc#=M9x(Vj_I4s`icZY9~;ZyVh~pIc-U3160*sYQP(- z?u{mCw}2ohQhsHk5f8+e+t`MeJc#&Y#5CGk_Y^?W%60X00x~lKXCMNFQzCi3c?_WKViIjhz6CnhX#wbnqSlFA z3m_Qx5vIjW4Zu}htaXkhX46SM9;QWoj(BY&fD9;#lk@-Sq@Gx;%`U&9WHplZbKqdJ zn>S~%7R3pIaUa3BkIu2YS^$6i5P&XZm=<*!q%bX%ul>pv@coVA1i@v(C{6&INo#z+ zsV2@P$^wA-e16KlzO`5S3iPx$u}MThR9J=Wmp_WaKorKmh@L>65vmxxP?siW!#yVQQ{VUAc-??=KbEE3<>aH1pv@~Z=WckS{t`g4xI9qI+p&V}d}u3D?r zccuNjN}!!exr3Cu!dx5=D(zJ%1A24QDVJ-`8-S=&NHxFb4yD{x?uk+W)43@jx;cbT zLplL?w{w|kzU4`<0+`*-WtM=b;|<|;4%lFPAW9#@Yr9&AuC-)00ceOa5IrBqgGy(C zwFF8XE+XO({4nBcv`h%w3F+(sS50kmdi>gcaF$>#<~UBb`42$hzaHauYcbF5_|+z7 z2EuWO)~Ht)YmIt1PPa%WY5sD_?ZPR5(Qg1DLMMPT1L-8ah|DIQ5xq7Xhe#)>bDc8- zksRjH@W}p>&VYu9YrXdU+=!TOZ$%{HgB6C?}4%! t-&5RI6F7e;QbO2cSqlK+{4M$~^aEnq$;RDSET#Ye002ovPDHLkV1liS+VTJZ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_rat_king.rsi/stayOff.png b/Resources/Textures/Interface/Actions/actions_rat_king.rsi/stayOff.png new file mode 100644 index 0000000000000000000000000000000000000000..aa739f71d77a819fa191acb684637c8a491b26dc GIT binary patch literal 483 zcmV<90UZ8`P)Px$ok>JNR9J=WmqCicFcgM=rFer1vkNY~fD~-EG8JKdVww^YkLC` zW)_n25+|vR5I6ptrp-(KFL^J40&ms?0DivX-&GVkHEyaJ`$IAD0ALk`XsQ}lk2x`) z-8fcJi2b3!RD5)0u6j-NVQ%yR_yD{ez_S_T`}4ltbr-h)!kn>%^b|Usj-%$YN`Wxt z{0z>|QeP0PN6j^A2gKy6DV3?u8o>AG(pJ+pHRSv}x+VqzRC85~EqVx>f;0lqYNt}S z`IVEP1yHM?g1T)4aDFve733?TWC}E5`cgh0ulW|upV_6Xj9O2mxmQm z2znXOF+wVY?uInhfGMZCKJoq8dN2aaZXCO9+`p$~i)6EuV$!me;Px&Xh}ptR9J=WR!wLcK^T22iS@9C1&v$7iWun8Q=u4yP$4uIY4FrbXekm%k(Qh! zf!T6DUe99wbC+O;b7~LW@$i2RqZ9-C1oQheGpU@n>h| zy?Ng^-#5Vj4$1CCA{NyLP0SU`wsWt75j*mQxf%Am)o#PDs@x|4{W?Tg7NF#coCl@1zL62&=?h(-04r6j7A_sCbOgKXAY7bkE!N1-SR06>=A z0D$Q!=vNWpFq5A*PKJ@gOm2jx1u#7o8kj#uNM`cvPJxjd6OsNGxvS}Ba#?nx)460W z06?>O%DxYBgbRV0JRS*iNoTk7PUn(+QZbqqVDG1=pxX&?7i_^oPDQmYPIRLK zvg}5)dCCbQga{LJkYI;-P$Cx9*D}kN$^Nk*y4@bz-7s=vLW+nfMgUmiUw`)j-cXPe z)h|AL0ivX5XJ6p+jkEEZ7O*803NVrZz?}!TkxupP|3ePsD^*qly4{{|L0NX==;+9{ zPMF~UtPZ48+gu^?t12r_mBXgg?e>@mLJ0h0K`hQb#LLC!_Gmlv|^79K-Ku2`0`I|cYvRk+Kwjd`v{P~?hbDgEpf3*YMc zChL-ulat&Uv?|h*VcaNJ2Y%lHSf9@o%Mw!JzE8vRt$vG?1PL^Kq1!N0kZ zQE@bU9@hj)2MwuyoYCvY8Owa*v818nLSS?v9s%cs2s8|(Hy`;sghNB}=?hEj0bpx? z7arFHfc|*l;^u3aoe0r=0q9##$rVLGt@h_?_vHR8{Hn?lTiA@K6UNvM7$+3(4F&N! z{}#1cmCFh9X|wHyRK!39Ht>dmtR@%>PwAi`d0YkEPW-iDSOVYR2LBX(0c`;O@eKr^ QA^-pY07*qoM6N<$g8s3jasU7T literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Animals/regalrat.rsi/meta.json b/Resources/Textures/Mobs/Animals/regalrat.rsi/meta.json index 260803a448..5c80471313 100644 --- a/Resources/Textures/Mobs/Animals/regalrat.rsi/meta.json +++ b/Resources/Textures/Mobs/Animals/regalrat.rsi/meta.json @@ -7,6 +7,9 @@ "license": "CC-BY-SA-3.0", "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b Changed by Alekshhh", "states": [ + { + "name": "critical" + }, { "name": "dead" }, -- 2.51.2