--- /dev/null
+using Content.Server.Friends.Systems;
+
+namespace Content.Server.Friends.Components;
+
+/// <summary>
+/// Pet something to become friends with it (use in hand, press Z)
+/// Uses FactionExceptionComponent behind the scenes
+/// </summary>
+[RegisterComponent, Access(typeof(PettableFriendSystem))]
+public sealed class PettableFriendComponent : Component
+{
+ /// <summary>
+ /// Localized popup sent when petting for the first time
+ /// </summary>
+ [DataField("successString", required: true), ViewVariables(VVAccess.ReadWrite)]
+ public string SuccessString = string.Empty;
+
+ /// <summary>
+ /// Localized popup sent when petting multiple times
+ /// </summary>
+ [DataField("failureString", required: true), ViewVariables(VVAccess.ReadWrite)]
+ public string FailureString = string.Empty;
+}
--- /dev/null
+using Content.Server.Chemistry.Components;
+using Content.Server.Friends.Components;
+using Content.Server.NPC.Components;
+using Content.Server.NPC.Systems;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Popups;
+
+namespace Content.Server.Friends.Systems;
+
+public sealed class PettableFriendSystem : EntitySystem
+{
+ [Dependency] private readonly FactionExceptionSystem _factionException = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<PettableFriendComponent, UseInHandEvent>(OnUseInHand);
+ SubscribeLocalEvent<PettableFriendComponent, GotRehydratedEvent>(OnRehydrated);
+ }
+
+ private void OnUseInHand(EntityUid uid, PettableFriendComponent comp, UseInHandEvent args)
+ {
+ var user = args.User;
+ if (args.Handled || !TryComp<FactionExceptionComponent>(uid, out var factionException))
+ return;
+
+ if (_factionException.IsIgnored(factionException, user))
+ {
+ _popup.PopupEntity(Loc.GetString(comp.FailureString, ("target", uid)), user, user);
+ return;
+ }
+
+ // you have made a new friend :)
+ _popup.PopupEntity(Loc.GetString(comp.SuccessString, ("target", uid)), user, user);
+ _factionException.IgnoreEntity(factionException, user);
+ args.Handled = true;
+ }
+
+ private void OnRehydrated(EntityUid uid, PettableFriendComponent _, ref GotRehydratedEvent args)
+ {
+ // can only pet before hydrating, after that the fish cannot be negotiated with
+ if (!TryComp<FactionExceptionComponent>(uid, out var comp))
+ return;
+
+ var targetComp = AddComp<FactionExceptionComponent>(args.Target);
+ _factionException.IgnoreEntities(targetComp, comp.Ignored);
+ }
+}
--- /dev/null
+using Content.Server.NPC.Systems;
+
+namespace Content.Server.NPC.Components;
+
+/// <summary>
+/// Prevents an NPC from attacking ignored entities from enemy factions.
+/// Can be added to if pettable, see PettableFriendComponent.
+/// </summary>
+[RegisterComponent, Access(typeof(FactionExceptionSystem))]
+public sealed class FactionExceptionComponent : Component
+{
+ /// <summary>
+ /// List of entities that this NPC will refuse to attack
+ /// </summary>
+ [DataField("ignored")]
+ public HashSet<EntityUid> Ignored = new();
+}
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Interaction;
+using Content.Server.NPC.Components;
using Content.Server.NPC.Pathfinding;
using Content.Server.NPC.Systems;
using Content.Shared.Examine;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Shared.Map;
+//using Robust.Shared.Prototypes;
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
{
[Dependency] protected readonly IEntityManager EntManager = default!;
private FactionSystem _factions = default!;
+ private FactionExceptionSystem _factionException = default!;
protected InteractionSystem Interaction = default!;
private PathfindingSystem _pathfinding = default!;
base.Initialize(sysManager);
sysManager.GetEntitySystem<ExamineSystemShared>();
_factions = sysManager.GetEntitySystem<FactionSystem>();
+ _factionException = sysManager.GetEntitySystem<FactionExceptionSystem>();
Interaction = sysManager.GetEntitySystem<InteractionSystem>();
_pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
}
paths.Add(UpdateTarget(owner, existingTarget, existingTarget, ownerCoordinates, blackboard, radius, canMove, xformQuery, targets));
}
+ EntManager.TryGetComponent<FactionExceptionComponent>(owner, out var factionException);
+
// TODO: Need a perception system instead
// TODO: This will be expensive so will be good to optimise and cut corners.
foreach (var target in _factions
if (mobQuery.TryGetComponent(target, out var mobState) &&
mobState.CurrentState > MobState.Alive ||
target == existingTarget ||
- target == owner)
+ target == owner ||
+ (factionException != null && _factionException.IsIgnored(factionException, target)))
{
continue;
}
--- /dev/null
+using Content.Server.NPC.Components;
+
+namespace Content.Server.NPC.Systems;
+
+/// <summary>
+/// Prevents an NPC from attacking some entities from an enemy faction.
+/// </summary>
+public sealed class FactionExceptionSystem : EntitySystem
+{
+ /// <summary>
+ /// Returns whether the entity from an enemy faction won't be attacked
+ /// </summary>
+ public bool IsIgnored(FactionExceptionComponent comp, EntityUid target)
+ {
+ return comp.Ignored.Contains(target);
+ }
+
+ /// <summary>
+ /// Prevents an entity from an enemy faction from being attacked
+ /// </summary>
+ public void IgnoreEntity(FactionExceptionComponent comp, EntityUid target)
+ {
+ comp.Ignored.Add(target);
+ }
+
+ /// <summary>
+ /// Prevents a list of entities from an enemy faction from being attacked
+ /// </summary>
+ public void IgnoreEntities(FactionExceptionComponent comp, IEnumerable<EntityUid> ignored)
+ {
+ comp.Ignored.UnionWith(ignored);
+ }
+}
petting-success-cat = You pet {THE($target)} on {POSS-ADJ($target)} fuzzy little head.
petting-success-corrupted-corgi = In an act of hubris, you pet {THE($target)} on {POSS-ADJ($target)} cursed little head.
petting-success-crab = You pet {THE($target)} on {POSS-ADJ($target)} smooth little head.
+petting-success-dehydrated-carp = You pet {THE($target)} on {POSS-ADJ($target)} dry little head. {CAPITALIZE(OBJECT($target))} seems to like you now!
petting-success-dog = You pet {THE($target)} on {POSS-ADJ($target)} soft floofy head.
petting-success-frog = You pet {THE($target)} on {POSS-ADJ($target)} slippery little head.
petting-success-goat = You pet {THE($target)} on {POSS-ADJ($target)} horned floofy head.
petting-failure-bat = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} too hard to catch!
petting-failure-corrupted-corgi = You reach out to pet {THE($target)}, but think better of it.
petting-failure-crab = You reach out to pet {THE($target)}, but {SUBJECT($target)} snaps {POSS-ADJ($target)} claws in your general direction!
+petting-failure-dehydrated-carp = You pet {THE($target)} on {POSS-ADJ($target)} dry little head.
petting-failure-goat = You reach out to pet {THE($target)}, but {SUBJECT($target)} stubbornly refuses!
petting-failure-goose = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} too horrible!
petting-failure-possum = You reach out to pet {THE($target)}, but are met with hisses and snarls!
hard: false
layer:
- LowImpassable
+ # pet fish before rehydrating and he will be nice to you
+ - type: FactionException
+ - type: PettableFriend
+ successString: petting-success-dehydrated-carp
+ failureString: petting-failure-dehydrated-carp
+ - type: EmitSoundOnUse
+ handle: false
+ sound:
+ path: /Audio/Effects/bite.ogg