Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weldbot #1703

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Weldbot #1703

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Threading;
using System.Threading.Tasks;
using Content.Server.NPC.Pathfinding;
using Content.Shared.Damage;
using Content.Shared.Emag.Components;
using Content.Shared.Interaction;
using Content.Shared.Silicons.Bots;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;

namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;

public sealed partial class PickNearbyWeldableOperator : HTNOperator
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private EntityLookupSystem _lookup = default!;
private WeldbotSystem _weldbot = default!;
private PathfindingSystem _pathfinding = default!;
private TagSystem _tagSystem = default!;

[DataField] public string RangeKey = NPCBlackboard.WeldbotWeldRange;

/// <summary>
/// Target entity to weld
/// </summary>
[DataField(required: true)]
public string TargetKey = string.Empty;

/// <summary>
/// Target entitycoordinates to move to.
/// </summary>
[DataField(required: true)]
public string TargetMoveKey = string.Empty;

public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
_lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
_weldbot = sysManager.GetEntitySystem<WeldbotSystem>();
_pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
_tagSystem = sysManager.GetEntitySystem<TagSystem>();
}

public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
CancellationToken cancelToken)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);

if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager))
return (false, null);

if (!_entManager.TryGetComponent<WeldbotComponent>(owner, out var weldbot))
return (false, null);

var damageQuery = _entManager.GetEntityQuery<DamageableComponent>();
var emagged = _entManager.HasComponent<EmaggedComponent>(owner);

foreach (var target in _lookup.GetEntitiesInRange(owner, range))
{
if (!damageQuery.TryGetComponent(target, out var damage))
continue;

var tagPrototype = _prototypeManager.Index<TagPrototype>(WeldbotWeldOperator.SiliconTag);

if (!_entManager.TryGetComponent<TagComponent>(target, out var tagComponent) || !_tagSystem.HasTag(tagComponent, tagPrototype))
continue;

// Only go towards a target if the bot can actually help them or if the weldbot is emagged
if (!emagged && damage.DamagePerGroup["Brute"].Value == 0)
continue;

//Needed to make sure it doesn't sometimes stop right outside it's interaction range
var pathRange = SharedInteractionSystem.InteractionRange - 1f;
var path = await _pathfinding.GetPath(owner, target, pathRange, cancelToken);

if (path.Result == PathResult.NoPath)
continue;

return (true, new Dictionary<string, object>()
{
{TargetKey, target},
{TargetMoveKey, _entManager.GetComponent<TransformComponent>(target).Coordinates},
{NPCBlackboard.PathfindKey, path},
});
}

return (false, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using Content.Server.Chat.Systems;
using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Emag.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Silicons.Bots;
using Content.Shared.Tag;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;

namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;

public sealed partial class WeldbotWeldOperator : HTNOperator
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private ChatSystem _chat = default!;
private WeldbotSystem _weldbot = default!;
private SharedAudioSystem _audio = default!;
private SharedInteractionSystem _interaction = default!;
private SharedPopupSystem _popup = default!;
private DamageableSystem _damageableSystem = default!;
private TagSystem _tagSystem = default!;

public const string SiliconTag = "SiliconMob";

/// <summary>
/// Target entity to inject.
/// </summary>
[DataField(required: true)]
public string TargetKey = string.Empty;

public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
_chat = sysManager.GetEntitySystem<ChatSystem>();
_weldbot = sysManager.GetEntitySystem<WeldbotSystem>();
_audio = sysManager.GetEntitySystem<SharedAudioSystem>();
_interaction = sysManager.GetEntitySystem<SharedInteractionSystem>();
_popup = sysManager.GetEntitySystem<SharedPopupSystem>();
_damageableSystem = sysManager.GetEntitySystem<DamageableSystem>();
_tagSystem = sysManager.GetEntitySystem<TagSystem>();
}

public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
{
base.TaskShutdown(blackboard, status);
blackboard.Remove<EntityUid>(TargetKey);
}

public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);

if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entMan) || _entMan.Deleted(target))
return HTNOperatorStatus.Failed;

var tagPrototype = _prototypeManager.Index<TagPrototype>(SiliconTag);

if (!_entMan.TryGetComponent<TagComponent>(target, out var tagComponent) || !_tagSystem.HasTag(tagComponent, tagPrototype))
return HTNOperatorStatus.Failed;

if (!_entMan.TryGetComponent<WeldbotComponent>(owner, out var botComp))
return HTNOperatorStatus.Failed;

if (!_entMan.TryGetComponent<DamageableComponent>(target, out var damage))
return HTNOperatorStatus.Failed;

if (!_interaction.InRangeUnobstructed(owner, target))
return HTNOperatorStatus.Failed;

if (damage.DamagePerGroup["Brute"].Value == 0 && !_entMan.HasComponent<EmaggedComponent>(owner))
return HTNOperatorStatus.Failed;

if (botComp.IsEmagged)
{
if (!_prototypeManager.TryIndex<DamageGroupPrototype>("Burn", out var prototype))
return HTNOperatorStatus.Failed;

_damageableSystem.TryChangeDamage(target, new DamageSpecifier(prototype, 10), true, false, damage);
}
else
{
if (!_prototypeManager.TryIndex<DamageGroupPrototype>("Brute", out var prototype))
return HTNOperatorStatus.Failed;

_damageableSystem.TryChangeDamage(target, new DamageSpecifier(prototype, -50), true, false, damage);
}

_audio.PlayPvs(botComp.WeldSound, target);

if(damage.DamagePerGroup["Brute"].Value == 0) //only say "all done if we're actually done!"
_chat.TrySendInGameICMessage(owner, Loc.GetString("weldbot-finish-weld"), InGameICChatType.Speak, hideChat: true, hideLog: true);

return HTNOperatorStatus.Finished;
}
}
2 changes: 2 additions & 0 deletions Content.Server/NPC/NPCBlackboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
{"InteractRange", SharedInteractionSystem.InteractionRange},
{"MaximumIdleTime", 7f},
{MedibotInjectRange, 4f},
{WeldbotWeldRange, 4f},
{MeleeMissChance, 0.3f},
{"MeleeRange", 1f},
{"MinimumIdleTime", 2f},
Expand Down Expand Up @@ -292,6 +293,7 @@ public string GetVisionRadiusKey(IEntityManager entMan)
public const string FollowTarget = "FollowTarget";
public const string Inventory = "Inventory";
public const string MedibotInjectRange = "MedibotInjectRange";
public const string WeldbotWeldRange = "WeldbotWeldRange";

public const string MeleeMissChance = "MeleeMissChance";

Expand Down
26 changes: 26 additions & 0 deletions Content.Shared/Silicons/Bots/WeldbotComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Robust.Shared.Audio;

namespace Content.Shared.Silicons.Bots;

/// <summary>
/// Used by the server for NPC Weldbot welding.
/// Currently no clientside prediction done, only exists in shared for emag handling.
/// </summary>
[RegisterComponent]
[Access(typeof(WeldbotSystem))]
public sealed partial class WeldbotComponent : Component
{
/// <summary>
/// Sound played after welding a patient.
/// </summary>
[DataField]
public SoundSpecifier WeldSound = new SoundPathSpecifier("/Audio/Items/welder2.ogg");

[DataField]
public SoundSpecifier EmagSparkSound = new SoundCollectionSpecifier("sparks")
{
Params = AudioParams.Default.WithVolume(8f)
};

public bool IsEmagged = false;
}
27 changes: 27 additions & 0 deletions Content.Shared/Silicons/Bots/WeldbotSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Content.Shared.Emag.Systems;
using Robust.Shared.Audio.Systems;

namespace Content.Shared.Silicons.Bots;

/// <summary>
/// Handles emagging Weldbots
/// </summary>
public sealed class WeldbotSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<WeldbotComponent, GotEmaggedEvent>(OnEmagged);
}

private void OnEmagged(EntityUid uid, WeldbotComponent comp, ref GotEmaggedEvent args)
{
_audio.PlayPredicted(comp.EmagSparkSound, uid, args.UserUid);

comp.IsEmagged = true;
args.Handled = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ petting-success-honkbot = You pet {THE($target)} on {POSS-ADJ($target)} slippery
petting-success-mimebot = You pet {THE($target)} on {POSS-ADJ($target)} cold metal head.
petting-success-cleanbot = You pet {THE($target)} on {POSS-ADJ($target)} damp metal head.
petting-success-medibot = You pet {THE($target)} on {POSS-ADJ($target)} sterile metal head.
petting-success-weldbot = You pet {THE($target)} on {POSS-ADJ($target)} stained metal head.
petting-success-firebot = You pet {THE($target)} on {POSS-ADJ($target)} warm metal head.
petting-success-generic-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} metal head.
petting-success-salvage-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} dirty metal head.
Expand All @@ -80,6 +81,7 @@ petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($tar
petting-failure-cleanbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy mopping!
petting-failure-mimebot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy miming!
petting-failure-medibot = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} syringe nearly stabs your hand!
petting-failure-weldbot = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} welder nearly burns your hand!
petting-failure-firebot = You reach out to pet {THE($target)}, but {SUBJECT($target)} sprays you in the face before you can get close!
petting-failure-generic-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy stating laws!
petting-failure-salvage-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy drilling!
Expand Down
2 changes: 2 additions & 0 deletions Resources/Locale/en-US/npc/weldbot.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
weldbot-start-weld = Hold still, please.
weldbot-finish-weld = All done.
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@
- FootstepSound
- CanPilot
- SiliconEmotes
- SiliconMob
- type: Emoting
- type: GuideHelp
guides:
Expand Down
1 change: 1 addition & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
- type: Tag
tags:
- DoorBumpOpener
- SiliconMob
- type: MobState
allowedStates:
- Alive
Expand Down
54 changes: 54 additions & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/weldbot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
- type: entity
parent: MobSiliconBase
id: MobWeldbot
name: weldbot
description: No substitute for an engineer, but better than nothing.
components:
- type: Weldbot
- type: Sprite
sprite: Mobs/Silicon/Bots/weldbot.rsi
state: weldbot
- type: HTN
rootTask:
task: WeldbotCompound
- type: Construction
graph: WeldBot
node: bot
- type: NoSlip
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 110
behaviors:
- !type:TriggerBehavior
- trigger:
!type:DamageTrigger
damage: 120
behaviors:
- !type:SpillBehavior
solution: tank
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: SolutionContainerManager
solutions:
tank:
reagents:
- ReagentId: WeldingFuel
Quantity: 100
- type: SentienceTarget
flavorKind: station-event-random-sentience-flavor-mechanical
- type: Anchorable
- type: InteractionPopup
interactSuccessString: petting-success-weldbot
interactFailureString: petting-failure-weldbot
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: ShowHealthBars
damageContainers:
- Inorganic
- Silicon
- type: ShowHealthIcons
damageContainers:
- Inorganic
- Silicon
1 change: 1 addition & 0 deletions Resources/Prototypes/Entities/Mobs/Player/ipc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
- FootstepSound
- DoorBumpOpener
- SiliconEmotes
- SiliconMob

- type: entity
save: false
Expand Down
3 changes: 3 additions & 0 deletions Resources/Prototypes/Entities/Objects/Tools/welders.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
visible: false
shader: unshaded
map: ["enum.ToggleVisuals.Layer"]
- type: Tag
tags:
- WeldingTool
- type: GenericVisualizer
visuals:
enum.ToggleVisuals.Toggled:
Expand Down
Loading
Loading