Adds HugBot (#37557)
* - hugbot
- bdy with two arms because it needs two arms to hug
- is constructable from:
- box of hugs
- proximity sensor
- two borg arms
- lots of voice lines
- kinda like a medibot, it chases you down and then hugs you
- except if it's emagged, then it punches you :)
- it has a 2m cooldown per person by default
- MeleeAttackOperator
- Read the doc, but it's an operator which makes the NPC hit a target exactly once assuming it's in range.
- Used to make the hugbot attack
- RaiseEventForOwnerOperator
- Read the doc, but it's an operator which raises an event on the owning NPC.
- Used to make the hugbot hug extra code, specifically for the cooldown
- Changes to existing code:
- `ComponentFilter : UtilityQueryFilter` gets `RetainWithComp` added which, as the name implies, retains entities with the specified comps rather than removing them. Basically, it lets you negate the filter.
- `SpeakOperator : HTNOperator`'s `speech` field can use a `LocalizedDataSet` instead of just a locstring now
- (I updated all of the existing usages for this)
-
* two arms
* wait what if we just used mimebot arms so it doesn't look awful
* smort
This commit is contained in:
5
Content.Client/Silicons/Bots/HugBotSystem.cs
Normal file
5
Content.Client/Silicons/Bots/HugBotSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Silicons.Bots;
|
||||
|
||||
namespace Content.Client.Silicons.Bots;
|
||||
|
||||
public sealed partial class HugBotSystem : SharedHugBotSystem;
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
|
||||
namespace Content.Server.NPC.HTN.Preconditions;
|
||||
|
||||
/// <summary>
|
||||
/// A precondition which is met if the NPC is emagged with <see cref="EmagType"/>, as computed by
|
||||
/// <see cref="EmagSystem.CheckFlag"/>. This is useful for changing NPC behavior in the case that the NPC is emagged,
|
||||
/// eg. like a helper NPC bot turning evil.
|
||||
/// </summary>
|
||||
public sealed partial class IsEmaggedPrecondition : HTNPrecondition
|
||||
{
|
||||
private EmagSystem _emag;
|
||||
|
||||
/// <summary>
|
||||
/// The type of emagging to check for.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EmagType EmagType = EmagType.Interaction;
|
||||
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
{
|
||||
base.Initialize(sysManager);
|
||||
_emag = sysManager.GetEntitySystem<EmagSystem>();
|
||||
}
|
||||
|
||||
public override bool IsMet(NPCBlackboard blackboard)
|
||||
{
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
return _emag.CheckFlag(owner, EmagType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Melee;
|
||||
|
||||
/// <summary>
|
||||
/// Something between <see cref="MeleeOperator"/> and <see cref="InteractWithOperator"/>, this operator causes the NPC
|
||||
/// to attempt a SINGLE <see cref="SharedMeleeWeaponSystem.AttemptLightAttack">melee attack</see> on the specified
|
||||
/// <see cref="TargetKey">target</see>.
|
||||
/// </summary>
|
||||
public sealed partial class MeleeAttackOperator : HTNOperator
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private SharedMeleeWeaponSystem _melee;
|
||||
|
||||
/// <summary>
|
||||
/// Key that contains the target entity.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string TargetKey = default!;
|
||||
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
{
|
||||
base.Initialize(sysManager);
|
||||
_melee = sysManager.GetEntitySystem<SharedMeleeWeaponSystem>();
|
||||
}
|
||||
|
||||
public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
||||
{
|
||||
base.TaskShutdown(blackboard, status);
|
||||
|
||||
ExitCombatMode(blackboard);
|
||||
}
|
||||
|
||||
public override void PlanShutdown(NPCBlackboard blackboard)
|
||||
{
|
||||
base.PlanShutdown(blackboard);
|
||||
|
||||
ExitCombatMode(blackboard);
|
||||
}
|
||||
|
||||
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||
{
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
|
||||
if (!_entManager.TryGetComponent<CombatModeComponent>(owner, out var combatMode) ||
|
||||
!_melee.TryGetWeapon(owner, out var weaponUid, out var weapon))
|
||||
{
|
||||
return HTNOperatorStatus.Failed;
|
||||
}
|
||||
|
||||
_entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, true, combatMode);
|
||||
|
||||
|
||||
if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) ||
|
||||
!_melee.AttemptLightAttack(owner, weaponUid, weapon, target))
|
||||
{
|
||||
return HTNOperatorStatus.Continuing;
|
||||
}
|
||||
|
||||
return HTNOperatorStatus.Finished;
|
||||
}
|
||||
|
||||
private void ExitCombatMode(NPCBlackboard blackboard)
|
||||
{
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
_entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, false);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using static Content.Server.NPC.HTN.PrimitiveTasks.Operators.SpeakOperator.SpeakOperatorSpeech;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||
|
||||
public sealed partial class SpeakOperator : HTNOperator
|
||||
{
|
||||
private ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
[DataField(required: true)]
|
||||
public string Speech = string.Empty;
|
||||
public SpeakOperatorSpeech Speech;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to hide message from chat window and logs.
|
||||
@@ -18,15 +26,51 @@ public sealed partial class SpeakOperator : HTNOperator
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
{
|
||||
base.Initialize(sysManager);
|
||||
|
||||
_chat = sysManager.GetEntitySystem<ChatSystem>();
|
||||
}
|
||||
|
||||
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||
{
|
||||
LocId speechLocId;
|
||||
switch (Speech)
|
||||
{
|
||||
case LocalizedSetSpeakOperatorSpeech localizedDataSet:
|
||||
if (!_proto.TryIndex(localizedDataSet.LineSet, out var speechSet))
|
||||
return HTNOperatorStatus.Failed;
|
||||
speechLocId = _random.Pick(speechSet);
|
||||
break;
|
||||
case SingleSpeakOperatorSpeech single:
|
||||
speechLocId = single.Line;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(Speech));
|
||||
}
|
||||
|
||||
var speaker = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
_chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden);
|
||||
_chat.TrySendInGameICMessage(
|
||||
speaker,
|
||||
Loc.GetString(speechLocId),
|
||||
InGameICChatType.Speak,
|
||||
hideChat: Hidden,
|
||||
hideLog: Hidden
|
||||
);
|
||||
|
||||
return base.Update(blackboard, frameTime);
|
||||
}
|
||||
|
||||
[ImplicitDataDefinitionForInheritors, MeansImplicitUse]
|
||||
public abstract partial class SpeakOperatorSpeech
|
||||
{
|
||||
public sealed partial class SingleSpeakOperatorSpeech : SpeakOperatorSpeech
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public string Line;
|
||||
}
|
||||
|
||||
public sealed partial class LocalizedSetSpeakOperatorSpeech : SpeakOperatorSpeech
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public ProtoId<LocalizedDatasetPrototype> LineSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
|
||||
|
||||
/// <summary>
|
||||
/// Raises an <see cref="HTNRaisedEvent"/> on the <see cref="NPCBlackboard.Owner">owner</see>. The event will contain
|
||||
/// the specified <see cref="Args"/>, and if not null, the value of <see cref="TargetKey"/>.
|
||||
/// </summary>
|
||||
public sealed partial class RaiseEventForOwnerOperator : HTNOperator
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The conceptual "target" of this event. Note that this is NOT the entity for which the event is raised. If null,
|
||||
/// <see cref="HTNRaisedEvent.Target"/> will be null.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? TargetKey;
|
||||
|
||||
/// <summary>
|
||||
/// The data contained in the raised event. Since <see cref="HTNRaisedEvent"/> is itself pretty meaningless, this is
|
||||
/// included to give some context of what the event is actually supposed to mean.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntityEventArgs Args;
|
||||
|
||||
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(
|
||||
blackboard.GetValue<EntityUid>(NPCBlackboard.Owner),
|
||||
new HTNRaisedEvent(
|
||||
blackboard.GetValue<EntityUid>(NPCBlackboard.Owner),
|
||||
TargetKey is { } targetKey ? blackboard.GetValue<EntityUid>(targetKey) : null,
|
||||
Args
|
||||
)
|
||||
);
|
||||
|
||||
return HTNOperatorStatus.Finished;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class HTNRaisedEvent(EntityUid owner, EntityUid? target, EntityEventArgs args) : EntityEventArgs
|
||||
{
|
||||
// Owner and target are both included here in case we want to add a "RaiseEventForTargetOperator" in the future
|
||||
// while reusing this event.
|
||||
public EntityUid Owner = owner;
|
||||
public EntityUid? Target = target;
|
||||
public EntityEventArgs Args = args;
|
||||
}
|
||||
@@ -9,4 +9,11 @@ public sealed partial class ComponentFilter : UtilityQueryFilter
|
||||
/// </summary>
|
||||
[DataField("components", required: true)]
|
||||
public ComponentRegistry Components = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true, this filter retains entities with ALL of the specified components. If false, this filter removes
|
||||
/// entities with ANY of the specified components.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool RetainWithComp = true;
|
||||
}
|
||||
|
||||
@@ -512,13 +512,14 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
{
|
||||
foreach (var comp in compFilter.Components)
|
||||
{
|
||||
if (HasComp(ent, comp.Value.Component.GetType()))
|
||||
continue;
|
||||
|
||||
var hasComp = HasComp(ent, comp.Value.Component.GetType());
|
||||
if (!compFilter.RetainWithComp == hasComp)
|
||||
{
|
||||
_entityList.Add(ent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ent in _entityList)
|
||||
{
|
||||
|
||||
65
Content.Server/Silicons/Bots/HugBotSystem.cs
Normal file
65
Content.Server/Silicons/Bots/HugBotSystem.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
|
||||
using Content.Shared.Silicons.Bots;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Silicons.Bots;
|
||||
|
||||
/// <summary>
|
||||
/// Beyond what <see cref="SharedHugBotSystem"/> does, this system manages the "lifecycle" of
|
||||
/// <see cref="RecentlyHuggedByHugBotComponent"/>.
|
||||
/// </summary>
|
||||
public sealed class HugBotSystem : SharedHugBotSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HugBotComponent, HTNRaisedEvent>(OnHtnRaisedEvent);
|
||||
}
|
||||
|
||||
private void OnHtnRaisedEvent(Entity<HugBotComponent> entity, ref HTNRaisedEvent args)
|
||||
{
|
||||
if (args.Args is not HugBotDidHugEvent ||
|
||||
args.Target is not {} target)
|
||||
return;
|
||||
|
||||
var ev = new HugBotHugEvent(GetNetEntity(entity));
|
||||
RaiseLocalEvent(target, ev);
|
||||
|
||||
ApplyHugBotCooldown(entity, target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies <see cref="RecentlyHuggedByHugBotComponent"/> to <paramref name="target"/> based on the configuration of
|
||||
/// <paramref name="hugBot"/>.
|
||||
/// </summary>
|
||||
public void ApplyHugBotCooldown(Entity<HugBotComponent> hugBot, EntityUid target)
|
||||
{
|
||||
var hugged = EnsureComp<RecentlyHuggedByHugBotComponent>(target);
|
||||
hugged.CooldownCompleteAfter = _gameTiming.CurTime + hugBot.Comp.HugCooldown;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
// Iterate through all RecentlyHuggedByHugBot entities...
|
||||
var huggedEntities = AllEntityQuery<RecentlyHuggedByHugBotComponent>();
|
||||
while (huggedEntities.MoveNext(out var huggedEnt, out var huggedComp))
|
||||
{
|
||||
// ... and if their cooldown is complete...
|
||||
if (huggedComp.CooldownCompleteAfter <= _gameTiming.CurTime)
|
||||
{
|
||||
// ... remove it, allowing them to receive the blessing of hugs once more.
|
||||
RemCompDeferred<RecentlyHuggedByHugBotComponent>(huggedEnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is indirectly raised (by being <see cref="HTNRaisedEvent.Args"/>) on a HugBot when it hugs (or emaggedly
|
||||
/// punches) an entity.
|
||||
/// </summary>
|
||||
[Serializable, DataDefinition]
|
||||
public sealed partial class HugBotDidHugEvent : EntityEventArgs;
|
||||
@@ -0,0 +1,15 @@
|
||||
using Content.Shared.Silicons.Bots;
|
||||
|
||||
namespace Content.Server.Silicons.Bots;
|
||||
|
||||
/// <summary>
|
||||
/// This marker component indicates that its entity has been recently hugged by a HugBot and should not be hugged again
|
||||
/// before <see cref="CooldownCompleteAfter">a cooldown period</see> in order to prevent hug spam.
|
||||
/// </summary>
|
||||
/// <see cref="SharedHugBotSystem"/>
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
public sealed partial class RecentlyHuggedByHugBotComponent : Component
|
||||
{
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan CooldownCompleteAfter = TimeSpan.MinValue;
|
||||
}
|
||||
12
Content.Shared/Silicons/Bots/HugBotComponent.cs
Normal file
12
Content.Shared/Silicons/Bots/HugBotComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Content.Shared.Silicons.Bots;
|
||||
|
||||
/// <summary>
|
||||
/// This component describes how a HugBot hugs.
|
||||
/// </summary>
|
||||
/// <see cref="SharedHugBotSystem"/>
|
||||
[RegisterComponent, AutoGenerateComponentState]
|
||||
public sealed partial class HugBotComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan HugCooldown = TimeSpan.FromMinutes(2);
|
||||
}
|
||||
38
Content.Shared/Silicons/Bots/SharedHugBotSystem.cs
Normal file
38
Content.Shared/Silicons/Bots/SharedHugBotSystem.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Silicons.Bots;
|
||||
|
||||
/// <summary>
|
||||
/// This system handles HugBots.
|
||||
/// </summary>
|
||||
public abstract class SharedHugBotSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<HugBotComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<HugBotComponent> entity, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction) ||
|
||||
_emag.CheckFlag(entity, EmagType.Interaction) ||
|
||||
!TryComp<HugBotComponent>(entity, out var hugBot))
|
||||
return;
|
||||
|
||||
// HugBot HTN checks for emag state within its own logic, so we don't need to change anything here.
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised on an entity when it is hugged by a HugBot.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class HugBotHugEvent(NetEntity hugBot) : EntityEventArgs
|
||||
{
|
||||
public readonly NetEntity HugBot = hugBot;
|
||||
}
|
||||
1
Resources/Locale/en-US/_Moffstation/recipes/tags.ftl
Normal file
1
Resources/Locale/en-US/_Moffstation/recipes/tags.ftl
Normal file
@@ -0,0 +1 @@
|
||||
construction-graph-tag-boxhug = a box of hugs
|
||||
26
Resources/Locale/en-US/npc/hugbot.ftl
Normal file
26
Resources/Locale/en-US/npc/hugbot.ftl
Normal file
@@ -0,0 +1,26 @@
|
||||
hugbot-start-hug-1 = LEVEL 5 HUG DEFICIENCY DETECTED!
|
||||
hugbot-start-hug-2 = You look like you need a hug!
|
||||
hugbot-start-hug-3 = Aww, somebody needs a hug!
|
||||
hugbot-start-hug-4 = Target acquired; Initiating hug routine.
|
||||
hugbot-start-hug-5 = Hold still, please.
|
||||
hugbot-start-hug-6 = Hugs!
|
||||
hugbot-start-hug-7 = Deploying HUG.
|
||||
hugbot-start-hug-8 = I am designed to hug, and you WILL be hugged.
|
||||
|
||||
hugbot-finish-hug-1 = All done.
|
||||
hugbot-finish-hug-2 = Hug routine terminated.
|
||||
hugbot-finish-hug-3 = Feel better?
|
||||
hugbot-finish-hug-4 = Feel better soon!
|
||||
hugbot-finish-hug-5 = You are loved.
|
||||
hugbot-finish-hug-6 = You matter.
|
||||
hugbot-finish-hug-7 = It always gets better!
|
||||
hugbot-finish-hug-8 = Hug: COMPLETE.
|
||||
|
||||
hugbot-emagged-finish-hug-1 = Actually, fuck you.
|
||||
hugbot-emagged-finish-hug-2 = Nobody loves you.
|
||||
hugbot-emagged-finish-hug-3 = Ewww... no.
|
||||
hugbot-emagged-finish-hug-4 = It can only get worse from here!
|
||||
hugbot-emagged-finish-hug-5 = Fucking crybaby.
|
||||
hugbot-emagged-finish-hug-6 = Go die.
|
||||
hugbot-emagged-finish-hug-7 = Drop dead.
|
||||
hugbot-emagged-finish-hug-8 = You are alone in this universe.
|
||||
@@ -5,3 +5,19 @@
|
||||
slots:
|
||||
hand 1:
|
||||
part: LeftArmBorg
|
||||
|
||||
# It's like a medibot or a cleanbot except it has two arms to hug :)
|
||||
- type: body
|
||||
id: HugBot
|
||||
name: "hugBot"
|
||||
root: box
|
||||
slots:
|
||||
box:
|
||||
part: TorsoBorg
|
||||
connections:
|
||||
- right_arm
|
||||
- left_arm
|
||||
right_arm:
|
||||
part: RightArmBorg
|
||||
left_arm:
|
||||
part: LeftArmBorg
|
||||
|
||||
17
Resources/Prototypes/Catalog/hugbot.yml
Normal file
17
Resources/Prototypes/Catalog/hugbot.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
- type: localizedDataset
|
||||
id: HugBotStarts
|
||||
values:
|
||||
prefix: hugbot-start-hug-
|
||||
count: 8
|
||||
|
||||
- type: localizedDataset
|
||||
id: HugBotFinishes
|
||||
values:
|
||||
prefix: hugbot-finish-hug-
|
||||
count: 8
|
||||
|
||||
- type: localizedDataset
|
||||
id: EmaggedHugBotFinishes
|
||||
values:
|
||||
prefix: hugbot-emagged-finish-hug-
|
||||
count: 8
|
||||
@@ -460,3 +460,40 @@
|
||||
- Supply
|
||||
- type: Puller
|
||||
needsHands: false
|
||||
|
||||
- type: entity
|
||||
parent: [ MobSiliconBase, MobCombat ]
|
||||
id: MobHugBot
|
||||
name: hugbot
|
||||
description: Awww, who needs a hug?
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Silicon/Bots/hugbot.rsi
|
||||
state: hugbot
|
||||
- type: Construction
|
||||
graph: HugBot
|
||||
node: bot
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed: 2
|
||||
baseSprintSpeed: 3
|
||||
- type: MeleeWeapon
|
||||
soundHit:
|
||||
path: /Audio/Weapons/boxingpunch1.ogg
|
||||
angle: 30
|
||||
animation: WeaponArcPunch
|
||||
damage:
|
||||
types:
|
||||
Blunt: 2
|
||||
- type: Anchorable
|
||||
- type: Hands # This probably REALLY needs hand whitelisting, but we NEED hands for hugs, so...
|
||||
- type: ComplexInteraction # Hugging is a complex interaction, apparently.
|
||||
- type: HugBot
|
||||
- type: Body
|
||||
prototype: HugBot
|
||||
- type: HTN
|
||||
rootTask:
|
||||
task: HugBotCompound
|
||||
- type: InteractionPopup
|
||||
interactSuccessString: hugging-success-generic
|
||||
interactSuccessSound: /Audio/Effects/thudswoosh.ogg
|
||||
messagePerceivedByOthers: hugging-success-generic-others
|
||||
|
||||
@@ -85,6 +85,5 @@
|
||||
- tasks:
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:SpeakOperator
|
||||
speech: "fuck!"
|
||||
|
||||
|
||||
speech: !type:SingleSpeakOperatorSpeech
|
||||
line: "fuck!"
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:SpeakOperator
|
||||
speech: firebot-fire-detected
|
||||
speech: !type:SingleSpeakOperatorSpeech
|
||||
line: firebot-fire-detected
|
||||
hidden: true
|
||||
|
||||
- !type:HTNPrimitiveTask
|
||||
|
||||
134
Resources/Prototypes/NPCs/hugbot.yml
Normal file
134
Resources/Prototypes/NPCs/hugbot.yml
Normal file
@@ -0,0 +1,134 @@
|
||||
- type: htnCompound
|
||||
id: HugBotCompound
|
||||
branches:
|
||||
- tasks:
|
||||
- !type:HTNCompoundTask
|
||||
task: HugNearbyCompound
|
||||
- tasks:
|
||||
- !type:HTNCompoundTask
|
||||
task: IdleCompound
|
||||
|
||||
- type: htnCompound
|
||||
id: HugNearbyCompound
|
||||
branches:
|
||||
- tasks:
|
||||
# Locate hug recipient.
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:UtilityOperator
|
||||
proto: NearbyNeedingHug
|
||||
|
||||
# Announce intent to hug
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:SpeakOperator
|
||||
speech: !type:LocalizedSetSpeakOperatorSpeech
|
||||
lineSet: HugBotStarts
|
||||
hidden: true
|
||||
|
||||
# Approach hug recipient
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:MoveToOperator
|
||||
pathfindInPlanning: true
|
||||
removeKeyOnFinish: false
|
||||
targetKey: TargetCoordinates
|
||||
pathfindKey: TargetPathfind
|
||||
rangeKey: MeleeRange
|
||||
|
||||
# HUG!!
|
||||
- !type:HTNCompoundTask
|
||||
task: HugBotHugCompound
|
||||
|
||||
# Stick around to enjoy the hug instead of just running up, squeezing and leaving.
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:SetFloatOperator
|
||||
targetKey: IdleTime
|
||||
amount: 1
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:WaitOperator
|
||||
key: IdleTime
|
||||
preconditions:
|
||||
- !type:KeyExistsPrecondition
|
||||
key: IdleTime
|
||||
|
||||
# Special case operator which applies the hugbot cooldown.
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:RaiseEventForOwnerOperator
|
||||
args: !type:HugBotDidHugEvent
|
||||
targetKey: Target
|
||||
|
||||
# Announce that the hug is completed.
|
||||
- !type:HTNCompoundTask
|
||||
task: HugBotFinishSpeakCompound
|
||||
|
||||
- type: htnCompound
|
||||
id: HugBotHugCompound
|
||||
branches:
|
||||
# Hit if emagged
|
||||
- preconditions:
|
||||
- !type:IsEmaggedPrecondition
|
||||
tasks:
|
||||
- !type:HTNPrimitiveTask
|
||||
preconditions:
|
||||
- !type:TargetInRangePrecondition
|
||||
targetKey: Target
|
||||
rangeKey: InteractRange
|
||||
operator: !type:MeleeAttackOperator
|
||||
targetKey: Target
|
||||
services:
|
||||
- !type:UtilityService
|
||||
id: HugService
|
||||
proto: NearbyNeedingHug
|
||||
key: Target
|
||||
# Hug otherwise
|
||||
- tasks:
|
||||
- !type:HTNPrimitiveTask
|
||||
preconditions:
|
||||
- !type:TargetInRangePrecondition
|
||||
targetKey: Target
|
||||
rangeKey: InteractRange
|
||||
operator: !type:InteractWithOperator
|
||||
targetKey: Target
|
||||
services:
|
||||
- !type:UtilityService
|
||||
id: HugService
|
||||
proto: NearbyNeedingHug
|
||||
key: Target
|
||||
|
||||
- type: htnCompound
|
||||
id: HugBotFinishSpeakCompound
|
||||
branches:
|
||||
# Say mean things if emagged
|
||||
- preconditions:
|
||||
- !type:IsEmaggedPrecondition
|
||||
tasks:
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:SpeakOperator
|
||||
speech: !type:LocalizedSetSpeakOperatorSpeech
|
||||
lineSet: EmaggedHugBotFinishes
|
||||
hidden: true
|
||||
# Say nice thing otherwise
|
||||
- tasks:
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:SpeakOperator
|
||||
speech: !type:LocalizedSetSpeakOperatorSpeech
|
||||
lineSet: HugBotFinishes
|
||||
hidden: true
|
||||
|
||||
- type: utilityQuery
|
||||
id: NearbyNeedingHug
|
||||
query:
|
||||
- !type:ComponentQuery
|
||||
components:
|
||||
- type: HumanoidAppearance
|
||||
species: Human # This specific value isn't actually used, so don't worry about it being just `Human`.
|
||||
- !type:ComponentFilter
|
||||
retainWithComp: false
|
||||
components:
|
||||
- type: RecentlyHuggedByHugBot
|
||||
considerations:
|
||||
- !type:TargetDistanceCon
|
||||
curve: !type:PresetCurve
|
||||
preset: TargetDistance
|
||||
- !type:TargetAccessibleCon
|
||||
curve: !type:BoolCurve
|
||||
- !type:TargetInLOSOrCurrentCon
|
||||
curve: !type:BoolCurve
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
- !type:HTNPrimitiveTask
|
||||
operator: !type:SpeakOperator
|
||||
speech: medibot-start-inject
|
||||
speech: !type:SingleSpeakOperatorSpeech
|
||||
line: medibot-start-inject
|
||||
hidden: true
|
||||
|
||||
- !type:HTNPrimitiveTask
|
||||
|
||||
33
Resources/Prototypes/Recipes/Crafting/Graphs/bots/hugbot.yml
Normal file
33
Resources/Prototypes/Recipes/Crafting/Graphs/bots/hugbot.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
- type: constructionGraph
|
||||
id: HugBot
|
||||
start: start
|
||||
graph:
|
||||
- node: start
|
||||
edges:
|
||||
- to: bot
|
||||
steps:
|
||||
- tag: BoxHug
|
||||
icon:
|
||||
sprite: Objects/Storage/boxes.rsi
|
||||
state: box_hug
|
||||
name: construction-graph-tag-boxhug
|
||||
- tag: ProximitySensor
|
||||
icon:
|
||||
sprite: Objects/Misc/proximity_sensor.rsi
|
||||
state: icon
|
||||
name: construction-graph-tag-proximity-sensor
|
||||
doAfter: 2
|
||||
- tag: BorgArm
|
||||
icon:
|
||||
sprite: Mobs/Silicon/drone.rsi
|
||||
state: l_hand
|
||||
name: construction-graph-tag-borg-arm
|
||||
doAfter: 2
|
||||
- tag: BorgArm
|
||||
icon:
|
||||
sprite: Mobs/Silicon/drone.rsi
|
||||
state: l_hand
|
||||
name: construction-graph-tag-borg-arm
|
||||
doAfter: 2
|
||||
- node: bot
|
||||
entity: MobHugBot
|
||||
@@ -53,3 +53,11 @@
|
||||
targetNode: bot
|
||||
category: construction-category-utilities
|
||||
objectType: Item
|
||||
|
||||
- type: construction
|
||||
id: hugbot
|
||||
graph: HugBot
|
||||
startNode: start
|
||||
targetNode: bot
|
||||
category: construction-category-utilities
|
||||
objectType: Item
|
||||
|
||||
BIN
Resources/Textures/Mobs/Silicon/Bots/hugbot.rsi/hugbot.png
Normal file
BIN
Resources/Textures/Mobs/Silicon/Bots/hugbot.rsi/hugbot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 342 B |
14
Resources/Textures/Mobs/Silicon/Bots/hugbot.rsi/meta.json
Normal file
14
Resources/Textures/Mobs/Silicon/Bots/hugbot.rsi/meta.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-4.0",
|
||||
"copyright": "Original sprite made by compilatron (Discord) for SS13, relicensed for SS14/Moffstation; modified by Centronias (GitHub) to have two arms",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "hugbot"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user