Co-authored-by: stellar-novas <stellar_novas@riseup.net>
Co-authored-by: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com>
(cherry picked from commit 01d1ec4d4b04f6d76f239ec0da0970dc705736ef)
This commit is contained in:
Timfa
2025-01-21 15:56:35 +01:00
committed by Spatison
parent be167a8afe
commit 0df12ec24a
24 changed files with 407 additions and 2 deletions

View File

@@ -0,0 +1,28 @@
using Content.Shared.NPC.Components;
using Content.Shared.NPC.Events;
using Robust.Client.GameObjects;
using Robust.Shared.Reflection;
namespace Content.Client.NPC.Systems;
public sealed partial class NpcFactionSpriteStateSetterSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NpcFactionMemberComponent, NpcFactionAddedEvent>(OnFactionAdded);
}
private void OnFactionAdded(Entity<NpcFactionMemberComponent > entity, ref NpcFactionAddedEvent args)
{
if (!_entityManager.HasComponent(entity, typeof(NpcFactionSpriteStateSetterComponent)))
return;
SpriteComponent spriteComponent = _entityManager.GetComponent<SpriteComponent>(entity);
spriteComponent.LayerSetState(0, new Robust.Client.Graphics.RSI.StateId(args.FactionID));
}
}

View File

@@ -1,5 +1,6 @@
using System.Numerics;
using Content.Server.NPC.Components;
using Content.Server.NPC.HTN;
using Content.Shared.CombatMode;
using Content.Shared.NPC;
using Robust.Shared.Map;
@@ -10,6 +11,7 @@ namespace Content.Server.NPC.Systems;
public sealed partial class NPCCombatSystem
{
[Dependency] private readonly IRobustRandom _rng = default!;
private const float TargetMeleeLostRange = 14f;
private void InitializeMelee()
@@ -114,5 +116,8 @@ public sealed partial class NPCCombatSystem
{
_melee.AttemptLightAttack(uid, weaponUid, weapon, component.Target);
}
if (Comp<HTNComponent>(uid).Blackboard.TryGetValue<float>("AttackDelayDeviation", out var dev, EntityManager))
weapon.NextAttack += TimeSpan.FromSeconds(_rng.NextFloat(-dev, dev));
}
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.NPC.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared.NPC.Components;
[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSelectorSystem))]
public sealed partial class NpcFactionSelectorComponent : Component
{
[DataField]
public List<string> SelectableFactions = new();
}

View File

@@ -0,0 +1,7 @@
using Robust.Shared.GameStates;
namespace Content.Shared.NPC.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class NpcFactionSpriteStateSetterComponent : Component {}

View File

@@ -0,0 +1,25 @@
using Robust.Shared.Serialization;
namespace Content.Shared.NPC.Events;
/// <summary>
/// Raised from client to server to notify a faction was added to an NPC.
/// </summary>
[Serializable, NetSerializable]
public sealed class NpcFactionAddedEvent : EntityEventArgs
{
public string FactionID;
public NpcFactionAddedEvent(string factionId) => FactionID = factionId;
}
/// <summary>
/// Raised from client to server to notify a faction was removed from an NPC.
/// </summary>
[Serializable, NetSerializable]
public sealed class NpcFactionRemovedEvent : EntityEventArgs
{
public string FactionID;
public NpcFactionRemovedEvent(string factionId) => FactionID = factionId;
}

View File

@@ -24,6 +24,9 @@ public sealed partial class NpcFactionPrototype : IPrototype
/// </summary>
public record struct FactionData
{
[ViewVariables]
public bool IsHostileToSelf;
[ViewVariables]
public HashSet<ProtoId<NpcFactionPrototype>> Friendly;

View File

@@ -0,0 +1,60 @@
using Content.Shared.Database;
using Content.Shared.NPC.Components;
using Content.Shared.NPC.Prototypes;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using System.Linq;
namespace Content.Shared.NPC.Systems;
public sealed partial class NpcFactionSelectorSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly NpcFactionSystem _factionSystem = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NpcFactionSelectorComponent, GetVerbsEvent<Verb>>(OnGetVerb);
}
private void OnGetVerb(Entity<NpcFactionSelectorComponent> entity, ref GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
return;
NpcFactionSelectorComponent factionSelectorComponent = _entityManager.GetComponent<NpcFactionSelectorComponent>(entity);
if (factionSelectorComponent.SelectableFactions.Count < 2)
return;
foreach (var type in factionSelectorComponent.SelectableFactions)
{
var proto = _prototype.Index<NpcFactionPrototype>(type);
var v = new Verb
{
Priority = 1,
Category = VerbCategory.SelectFaction,
Text = proto.ID,
Impact = LogImpact.Medium,
DoContactInteraction = true,
Act = () =>
{
_popup.PopupPredicted(Loc.GetString("npcfaction-component-faction-set", ("faction", proto.ID)), entity.Owner, null);
foreach (var type in factionSelectorComponent.SelectableFactions)
{
_factionSystem.RemoveFaction(entity.Owner, type);
}
_factionSystem.AddFaction(entity.Owner, proto.ID);
}
};
args.Verbs.Add(v);
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.NPC.Components;
using Content.Shared.NPC.Events;
using Content.Shared.NPC.Prototypes;
using Robust.Shared.Prototypes;
using System.Collections.Frozen;
@@ -106,6 +107,8 @@ public sealed partial class NpcFactionSystem : EntitySystem
if (!ent.Comp.Factions.Add(faction))
return;
RaiseLocalEvent(ent.Owner, new NpcFactionAddedEvent(faction));
if (dirty)
RefreshFactions((ent, ent.Comp));
}
@@ -125,6 +128,8 @@ public sealed partial class NpcFactionSystem : EntitySystem
continue;
}
RaiseLocalEvent(ent.Owner, new NpcFactionAddedEvent(faction));
ent.Comp.Factions.Add(faction);
}
@@ -149,6 +154,8 @@ public sealed partial class NpcFactionSystem : EntitySystem
if (!ent.Comp.Factions.Remove(faction))
return;
RaiseLocalEvent(ent.Owner, new NpcFactionRemovedEvent(faction));
if (dirty)
RefreshFactions((ent, ent.Comp));
}
@@ -217,7 +224,12 @@ public sealed partial class NpcFactionSystem : EntitySystem
if (!Resolve(ent, ref ent.Comp, false) || !Resolve(other, ref other.Comp, false))
return false;
return ent.Comp.Factions.Overlaps(other.Comp.Factions) || ent.Comp.FriendlyFactions.Overlaps(other.Comp.Factions);
var intersect = ent.Comp.Factions.Intersect(other.Comp.Factions); // factions which have both ent and other as members
foreach (var faction in intersect)
if (_factions[faction].IsHostileToSelf)
return false;
return intersect.Count() > 0 || ent.Comp.FriendlyFactions.Overlaps(other.Comp.Factions);
}
public bool IsFactionFriendly(string target, string with)
@@ -301,8 +313,9 @@ public sealed partial class NpcFactionSystem : EntitySystem
{
_factions = _proto.EnumeratePrototypes<NpcFactionPrototype>().ToFrozenDictionary(
faction => faction.ID,
faction => new FactionData
faction => new FactionData
{
IsHostileToSelf = faction.Hostile.Contains(faction.ID),
Friendly = faction.Friendly.ToHashSet(),
Hostile = faction.Hostile.ToHashSet()
});

View File

@@ -100,6 +100,8 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory SelectType = new("verb-categories-select-type");
public static readonly VerbCategory SelectFaction = new("verb-categories-select-faction");
public static readonly VerbCategory PowerLevel = new("verb-categories-power-level");
public static readonly VerbCategory Interaction = new("verb-categories-interaction");

View File

@@ -72,6 +72,7 @@ petting-success-syndicate-cyborg = You pet {THE($target)} on {POSS-ADJ($target)}
petting-success-derelict-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} rusty metal head.
petting-success-recycler = You pet {THE($target)} on {POSS-ADJ($target)} mildly threatening steel exterior.
petting-success-station-ai = You pet {THE($target)} on {POSS-ADJ($target)} cold, square screen.
petting-success-gladiabot = You pet {THE($target)} on {POSS-ADJ($target)} vicious cardboard head.
petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} honks in refusal!
petting-failure-cleanbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy mopping!
@@ -87,6 +88,7 @@ petting-failure-service-cyborg = You reach out to pet {THE($target)}, but {SUBJE
petting-failure-syndicate-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} treacherous affiliation makes you reconsider.
petting-failure-derelict-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} rusty and jagged exterior makes you reconsider.
petting-failure-station-ai = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "zap", "zaps")} your hand away.
petting-failure-gladiabot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} only wants to fight!
petting-success-station-ai-others = { CAPITALIZE(THE($user)) } pets {THE($target)} on {POSS-ADJ($target)} cold, square screen.

View File

@@ -0,0 +1 @@
npcfaction-component-faction-set = Faction set to: {$faction}

View File

@@ -26,6 +26,7 @@ verb-categories-set-sensor = Sensor
verb-categories-timer = Set Delay
verb-categories-lever = Lever
verb-categories-select-type = Select Type
verb-categories-select-faction = Select Faction
verb-categories-fax = Set Destination
verb-categories-power-level = Power Level
verb-categories-interaction = Interact

View File

@@ -0,0 +1,90 @@
- type: entity
parent: MobSiliconBase
id: MobGladiaBot
name: gladiabot
description: For glory!
components:
- type: Sprite
sprite: Mobs/Silicon/Bots/gladiabot.rsi
state: GladiabotFFA
- type: Inventory
templateId: gladiabot
- type: InventorySlots
- type: UserInterface
interfaces:
enum.StrippingUiKey.Key:
type: StrippableBoundUserInterface
- type: Construction
graph: GladiaBot
node: bot
- type: Stripping
- type: Strippable
- type: SentienceTarget
flavorKind: station-event-random-sentience-flavor-mechanical
- type: UseDelay
delay: 1
- type: NpcFactionMember
factions:
- GladiabotFFA
- type: NpcFactionSelector
selectableFactions:
- GladiabotFFA
- GladiabotRed
- GladiabotBlue
- GladiabotGreen
- GladiabotYellow
- type: NpcFactionSpriteStateSetter
- type: CombatMode
- type: MeleeWeapon
altDisarm: false
soundHit:
path: /Audio/Weapons/bladeslice.ogg
angle: 0
animation: WeaponArcPunch
damage:
types:
Slash: 3
- type: MobThresholds
thresholds:
0: Alive
40: Dead
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 30
behaviors:
- !type:TriggerBehavior
- trigger:
!type:DamageTrigger
damage: 40
behaviors:
- !type:SpawnEntitiesBehavior
spawn:
ProximitySensor:
min: 1
max: 1
- !type:SpawnEntitiesBehavior
spawn:
MaterialCloth1:
min: 1
max: 1
- !type:EmptyContainersBehaviour
containers:
- head
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: MovementSpeedModifier
baseWalkSpeed: 2
baseSprintSpeed: 3
- type: HTN
rootTask:
task: GladiabotCompound
blackboard:
AttackDelayDeviation: !type:Single
0.3
- type: InteractionPopup
interactSuccessString: petting-success-gladiabot
interactFailureString: petting-failure-gladiabot
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg

View File

@@ -0,0 +1,10 @@
- type: inventoryTemplate
id: gladiabot
slots:
- name: head
slotTexture: head
slotFlags: HEAD
uiWindowPos: 0,1
strippingWindowPos: 0,0
displayName: Head
offset: 0.1, -0.15

View File

@@ -0,0 +1,12 @@
- type: htnCompound
id: GladiabotCompound
branches:
- tasks:
- !type:HTNPrimitiveTask
operator: !type:UtilityOperator
proto: NearbyMeleeTargets
- !type:HTNCompoundTask
task: MeleeAttackTargetCompound
- tasks:
- !type:HTNCompoundTask
task: IdleCompound

View File

@@ -0,0 +1,25 @@
- type: constructionGraph
id: GladiaBot
start: start
graph:
- node: start
edges:
- to: bot
steps:
- material: Cardboard
amount: 1
doAfter: 2
- tag: ProximitySensor
icon:
sprite: Objects/Misc/proximity_sensor.rsi
state: icon
name: proximity sensor
doAfter: 2
- tag: Shiv
icon:
sprite: Objects/Weapons/Melee/shiv.rsi
state: icon
name: shiv
doAfter: 2
- node: bot
entity: MobGladiaBot

View File

@@ -75,3 +75,16 @@
icon:
sprite: Mobs/Silicon/Bots/supplybot.rsi
state: supplybot
- type: construction
name: gladiabot
id: gladiabot
graph: GladiaBot
startNode: start
targetNode: bot
category: construction-category-utilities
objectType: Item
description: This bot fights for honour and glory!
icon:
sprite: Mobs/Silicon/Bots/gladiabot.rsi
state: GladiabotFFA

View File

@@ -107,3 +107,44 @@
- type: npcFaction
id: AnimalFriend
- type: npcFaction
id: GladiabotFFA
hostile:
- GladiabotFFA
- GladiabotRed
- GladiabotGreen
- GladiabotBlue
- GladiabotYellow
- type: npcFaction
id: GladiabotRed
hostile:
- GladiabotFFA
- GladiabotGreen
- GladiabotBlue
- GladiabotYellow
- type: npcFaction
id: GladiabotGreen
hostile:
- GladiabotFFA
- GladiabotRed
- GladiabotBlue
- GladiabotYellow
- type: npcFaction
id: GladiabotBlue
hostile:
- GladiabotFFA
- GladiabotRed
- GladiabotGreen
- GladiabotYellow
- type: npcFaction
id: GladiabotYellow
hostile:
- GladiabotFFA
- GladiabotRed
- GladiabotGreen
- GladiabotBlue

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,56 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Tim Falken",
"states": [
{
"name": "GladiabotFFA",
"delays": [
[
0.5,
0.2
]
]
},
{
"name": "GladiabotRed",
"delays": [
[
0.5,
0.2
]
]
},
{
"name": "GladiabotGreen",
"delays": [
[
0.5,
0.2
]
]
},
{
"name": "GladiabotBlue",
"delays": [
[
0.5,
0.2
]
]
},
{
"name": "GladiabotYellow",
"delays": [
[
0.5,
0.2
]
]
}
]
}