Introduce an AI shop system (#3610)

* Introduce an AI shop system

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* feetback

* condensation

* meow

* valid8

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
pathetic meowmeow 2025-05-02 17:15:13 -04:00 committed by GitHub
parent dad088edcb
commit 17e85c2c6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 595 additions and 1 deletions

View File

@ -0,0 +1,5 @@
using Content.Shared._DV.Silicons;
namespace Content.Client._DV.Silicons;
public sealed class StationAiShopSystem : SharedStationAiShopSystem;

View File

@ -202,7 +202,7 @@ public sealed partial class StoreSystem
EntityUid? actionId;
// I guess we just allow duplicate actions?
// Allow duplicate actions and just have a single list buy for the buy-once ones.
if (!_mind.TryGetMind(buyer, out var mind, out _))
if (!_mind.TryGetMind(buyer, out var mind, out _) || !component.GrantActionsToMind) // DeltaV - allow forcing actions to be on the entity
actionId = _actions.AddAction(buyer, listing.ProductAction);
else
actionId = _actionContainer.AddAction(mind, listing.ProductAction);

View File

@ -0,0 +1,71 @@
using Content.Server.Fluids.EntitySystems;
using Content.Server.Light.EntitySystems;
using Content.Server.Spreader;
using Content.Server.Store.Systems;
using Content.Shared._DV.Silicons;
using Content.Shared.Chemistry.Components;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Maps;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Server._DV.Silicons;
public sealed class StationAiShopSystem : SharedStationAiShopSystem
{
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly SmokeSystem _smoke = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SpreaderSystem _spreader = default!;
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StationAiShopComponent, StationAiShopActionEvent>(OnShopAction);
SubscribeLocalEvent<StationAiShopComponent, StationAiLightSynthesizerActionEvent>(OnLightSynthesizer);
SubscribeLocalEvent<StationAiShopComponent, StationAiSmokeActionEvent>(OnEmergencySealant);
}
private void OnShopAction(Entity<StationAiShopComponent> ent, ref StationAiShopActionEvent args)
{
_store.ToggleUi(args.Performer, ent);
}
private void OnLightSynthesizer(Entity<StationAiShopComponent> ent, ref StationAiLightSynthesizerActionEvent args)
{
if (_poweredLight.EjectBulb(args.Target) is { } oldBulb)
Del(oldBulb);
var bulb = Spawn(args.BulbPrototype);
if (!_poweredLight.InsertBulb(args.Target, bulb))
{
Del(bulb);
return;
}
args.Handled = true;
}
private void OnEmergencySealant(Entity<StationAiShopComponent> ent, ref StationAiSmokeActionEvent args)
{
var mapCoords = _transform.ToMapCoordinates(args.Target);
if (!_map.TryFindGridAt(mapCoords, out _, out var grid) ||
!grid.TryGetTileRef(args.Target, out var tileRef) ||
tileRef.Tile.IsEmpty)
{
return;
}
if (_spreader.RequiresFloorToSpread(args.SmokePrototype.ToString()) && tileRef.Tile.IsSpace())
return;
var coords = grid.MapToGrid(mapCoords);
var uid = Spawn(args.SmokePrototype, coords.SnapToGrid());
_smoke.StartSmoke(uid, args.Solution, args.Duration, args.SpreadAmount);
args.Handled = true;
}
}

View File

@ -73,6 +73,12 @@ public sealed partial class StoreComponent : Component
[ViewVariables, DataField]
public bool RefundAllowed;
/// <summary>
/// DeltaV: Should the store grant actions to the mind?
/// </summary>
[DataField]
public bool GrantActionsToMind = true;
/// <summary>
/// Checks if store can be opened by the account owner only.
/// Not meant to be used with uplinks.

View File

@ -0,0 +1,68 @@
using Content.Shared.Actions;
using Content.Shared.Damage;
using Content.Shared.Light.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
namespace Content.Shared._DV.Silicons;
public abstract class SharedStationAiShopSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damage = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StationAiShopComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<StationAiShopComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<StationAiShopComponent, StationAiRgbLightingActionEvent>(OnRgbLighting);
SubscribeLocalEvent<StationAiShopComponent, StationAiPlaySoundActionEvent>(OnPlaySound);
SubscribeLocalEvent<StationAiShopComponent, StationAiHealthChangeActionEvent>(OnHealthChange);
SubscribeLocalEvent<StationAiShopComponent, StationAiSpawnEntityActionEvent>(OnHolopointer);
}
private void OnMapInit(Entity<StationAiShopComponent> ent, ref MapInitEvent args)
{
_actions.AddAction(ent, ref ent.Comp.ShopAction, ent.Comp.ShopActionId);
Dirty(ent);
}
private void OnShutdown(Entity<StationAiShopComponent> ent, ref ComponentShutdown args)
{
_actions.RemoveAction(ent, ent.Comp.ShopAction);
}
private void OnRgbLighting(Entity<StationAiShopComponent> ent, ref StationAiRgbLightingActionEvent args)
{
if (!RemComp<RgbLightControllerComponent>(args.Target))
AddComp<RgbLightControllerComponent>(args.Target);
args.Handled = true;
}
private void OnPlaySound(Entity<StationAiShopComponent> ent, ref StationAiPlaySoundActionEvent args)
{
_audio.PlayPredicted(args.Sound, args.Target, ent);
args.Handled = true;
}
private void OnHealthChange(Entity<StationAiShopComponent> ent, ref StationAiHealthChangeActionEvent args)
{
_damage.TryChangeDamage(args.Target, args.Damage, origin: ent);
args.Handled = true;
}
private void OnHolopointer(Entity<StationAiShopComponent> ent, ref StationAiSpawnEntityActionEvent args)
{
if (!_timing.IsFirstTimePredicted)
return;
Spawn(args.Entity, args.Target);
args.Handled = true;
}
}

View File

@ -0,0 +1,95 @@
using Content.Shared.Actions;
using Content.Shared.Chemistry.Components;
using Content.Shared.Damage;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.Silicons;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class StationAiShopComponent : Component
{
[DataField]
public EntProtoId ShopActionId = "ActionStationAiOpenShop";
[DataField, AutoNetworkedField]
public EntityUid? ShopAction;
}
public sealed partial class StationAiShopActionEvent : InstantActionEvent;
/// <summary>
/// Toggles the RGB light controller on the given entity
/// </summary>
public sealed partial class StationAiRgbLightingActionEvent : EntityTargetActionEvent;
/// <summary>
/// Replaces the light bulb in the target with the given prototype
/// </summary>
public sealed partial class StationAiLightSynthesizerActionEvent : EntityTargetActionEvent
{
[DataField(required: true)]
public EntProtoId BulbPrototype;
}
/// <summary>
/// Plays the given sound coming from the target entity
/// </summary>
public sealed partial class StationAiPlaySoundActionEvent : EntityTargetActionEvent
{
[DataField(required: true)]
public SoundSpecifier Sound;
}
/// <summary>
/// Changes the target entity's health by the given damage specifier
/// </summary>
public sealed partial class StationAiHealthChangeActionEvent : EntityTargetActionEvent
{
[DataField(required: true)]
public DamageSpecifier Damage;
}
/// <summary>
/// Spawns the given entity at the target location
/// </summary>
public sealed partial class StationAiSpawnEntityActionEvent : WorldTargetActionEvent
{
[DataField(required: true)]
public EntProtoId Entity;
}
/// <summary>
/// Triggers the given smoke effect at the target location
/// </summary>
public sealed partial class StationAiSmokeActionEvent : WorldTargetActionEvent
{
/// <summary>
/// How long the smoke stays for, after it has spread.
/// </summary>
[DataField]
public float Duration = 10;
/// <summary>
/// How much the smoke will spread.
/// </summary>
[DataField(required: true)]
public int SpreadAmount;
/// <summary>
/// Smoke entity to spawn.
/// Defaults to smoke but you can use foam if you want.
/// </summary>
[DataField]
public EntProtoId<SmokeComponent> SmokePrototype = "Smoke";
/// <summary>
/// Solution to add to each smoke cloud.
/// </summary>
/// <remarks>
/// When using repeating trigger this essentially gets multiplied so dont do anything crazy like omnizine or lexorin.
/// </remarks>
[DataField]
public Solution Solution = new();
}

View File

@ -0,0 +1,17 @@
ai-rgb-lighting-name = RGB Lighting
ai-rgb-lighting-desc = Adjust a light's color controller to add or remove an RGB effect
ai-light-synthesizer-name = Light Synthesizer
ai-light-synthesizer-desc = Send some nanites to synthesize a replacement light bulb in a fixture
ai-bike-horn-name = HONK.mp3
ai-bike-horn-desc = Play an amusing audio clip from a holopad
ai-holopointer-name = Holopointer
ai-holopointer-desc = Circle an area of interest with a hologram
ai-repair-nanites-name = Repair Nanites
ai-repair-nanites-desc = Send some nanites to give a cyborg some minor repairs
ai-emergency-sealant-name = Emergency Sealant
ai-emergency-sealant-desc = Deploy some metal foam to seal breaches

View File

@ -0,0 +1 @@
store-currency-display-ai-memory = TB

View File

@ -56,6 +56,8 @@
type: SiliconLawBoundUserInterface
enum.CommunicationsConsoleUiKey.Key:
type: CommunicationsConsoleBoundUserInterface
enum.StoreUiKey.Key: # DeltaV - AI shop system
type: StoreBoundUserInterface
- type: IntrinsicUI
uis:
enum.RadarConsoleUiKey.Key:
@ -83,6 +85,17 @@
- sprite: Mobs/Silicon/station_ai.rsi
state: default
- type: ShowJobIcons
# Begin DeltaV - AI Shop System
- type: StationAiShop
- type: Store
categories:
- AiAbilities
currencyWhitelist:
- AiMemory
balance:
AiMemory: 3
grantActionsToMind: false
# End DeltaV - AI Shop System
- type: entity
id: AiHeldIntellicard

View File

@ -10,6 +10,9 @@
description: "An always powered light."
suffix: Always powered
components:
- type: Tag # DeltaV - we need this for an action whitelist
tags:
- LightFixture
- type: MeleeSound
soundGroups:
Brute:

View File

@ -0,0 +1,154 @@
# Actions
- type: entity
id: ActionStationAiOpenShop
name: Choose programs
description: Configure your installed programs
components:
- type: InstantAction
itemIconStyle: BigAction
icon:
sprite: _DV/Interface/Actions/actions_ai.rsi
state: store
event: !type:StationAiShopActionEvent
- type: entity
id: ActionStationAiRgbLighting
name: ai-rgb-lighting-name
description: ai-rgb-lighting-desc
components:
- type: EntityTargetAction
whitelist:
tags:
- LightFixture
itemIconStyle: BigAction
icon:
sprite: _DV/Interface/Actions/actions_ai.rsi
state: rgb_lighting
range: -1
checkCanAccess: false
checkCanInteract: false
useDelay: 30
event: !type:StationAiRgbLightingActionEvent
- type: entity
id: ActionStationAiLightSynthesizer
name: ai-light-synthesizer-name
description: ai-light-synthesizer-desc
components:
- type: EntityTargetAction
whitelist:
tags:
- LightFixture
itemIconStyle: BigAction
icon:
sprite: _DV/Interface/Actions/actions_ai.rsi
state: light_synthesizer
range: -1
checkCanAccess: false
checkCanInteract: false
useDelay: 300
event: !type:StationAiLightSynthesizerActionEvent
bulbPrototype: LightTube
- type: entity
id: ActionStationAiBikeHorn
name: ai-bike-horn-name
description: ai-bike-horn-desc
components:
- type: EntityTargetAction
whitelist:
components:
- Holopad
itemIconStyle: BigAction
icon:
sprite: _DV/Interface/Actions/actions_ai.rsi
state: bike_horn
range: -1
checkCanAccess: false
checkCanInteract: false
useDelay: 60
event: !type:StationAiPlaySoundActionEvent
sound:
collection: BikeHorn
params:
variation: 0.125
- type: entity
id: ActionStationAiHoloPointer
name: ai-holopointer-name
description: ai-holopointer-desc
components:
- type: WorldTargetAction
itemIconStyle: BigAction
icon:
sprite: _DV/Interface/Actions/actions_ai.rsi
state: holopointer
range: -1
checkCanAccess: false
checkCanInteract: false
useDelay: 15
event: !type:StationAiSpawnEntityActionEvent
entity: StationAiHolopointer
- type: entity
id: StationAiHolopointer
name: holopointer
description: A hologram projected by the station's AI to bring your attention to something.
components:
- type: Transform
anchored: true
- type: Physics
bodyType: Static
canCollide: false
- type: Sprite
sprite: _DV/Objects/Misc/holopointer.rsi
state: target_circle
- type: TimedDespawn
lifetime: 15
- type: PointLight
enabled: true
radius: 1
color: red
- type: Clickable
- type: entity
id: ActionStationAiRepairNanites
name: ai-repair-nanites-name
description: ai-repair-nanites-desc
components:
- type: EntityTargetAction
whitelist:
components:
- BorgChassis
itemIconStyle: BigAction
icon:
sprite: _DV/Interface/Actions/actions_ai.rsi
state: nanites
range: -1
checkCanAccess: false
checkCanInteract: false
useDelay: 120
event: !type:StationAiHealthChangeActionEvent
damage:
groups:
Burn: -20
Brute: -20
- type: entity
id: ActionStationAiEmergencySealant
name: ai-emergency-sealant-name
description: ai-emergency-sealant-desc
components:
- type: WorldTargetAction
itemIconStyle: BigAction
icon:
sprite: _DV/Interface/Actions/actions_ai.rsi
state: emergency_sealant
range: -1
checkCanAccess: false
checkCanInteract: false
useDelay: 300
event: !type:StationAiSmokeActionEvent
duration: 10
spreadAmount: 8
smokePrototype: AluminiumMetalFoam

View File

@ -0,0 +1,77 @@
- type: listing
id: AiRgbLighting
name: ai-rgb-lighting-name
description: ai-rgb-lighting-desc
productAction: ActionStationAiRgbLighting
cost:
AiMemory: 1
categories:
- AiAbilities
conditions:
- !type:ListingLimitedStockCondition
stock: 1
- type: listing
id: AiHolopointer
name: ai-holopointer-name
description: ai-holopointer-desc
productAction: ActionStationAiHoloPointer
cost:
AiMemory: 1
categories:
- AiAbilities
conditions:
- !type:ListingLimitedStockCondition
stock: 1
- type: listing
id: AiLightSynthesizer
name: ai-light-synthesizer-name
description: ai-light-synthesizer-desc
productAction: ActionStationAiLightSynthesizer
cost:
AiMemory: 1
categories:
- AiAbilities
conditions:
- !type:ListingLimitedStockCondition
stock: 1
- type: listing
id: AiBikeHorn
name: ai-bike-horn-name
description: ai-bike-horn-desc
productAction: ActionStationAiBikeHorn
cost:
AiMemory: 1
categories:
- AiAbilities
conditions:
- !type:ListingLimitedStockCondition
stock: 1
- type: listing
id: AiEmergencySealant
name: ai-emergency-sealant-name
description: ai-emergency-sealant-desc
productAction: ActionStationAiEmergencySealant
cost:
AiMemory: 2
categories:
- AiAbilities
conditions:
- !type:ListingLimitedStockCondition
stock: 1
- type: listing
id: AiRepairNanites
name: ai-repair-nanites-name
description: ai-repair-nanites-desc
productAction: ActionStationAiRepairNanites
cost:
AiMemory: 2
categories:
- AiAbilities
conditions:
- !type:ListingLimitedStockCondition
stock: 1

View File

@ -2,3 +2,7 @@
id: UplinkObjectives
name: store-category-objectives
priority: 11
- type: storeCategory
id: AiAbilities
name: store-category-abilities

View File

@ -0,0 +1,4 @@
- type: currency
id: AiMemory
displayName: store-currency-display-ai-memory
canWithdraw: false

View File

@ -96,6 +96,9 @@
- type: Tag # prevents ID chips being used for an id computer privileged id
id: IdChip
- type: Tag
id: LightFixture
- type: Tag
id: MagazineLaser

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

View File

@ -0,0 +1,32 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Janet Blackquill <uhhadd@gmail.com> 2025",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "bike_horn"
},
{
"name": "emergency_sealant"
},
{
"name": "holopointer"
},
{
"name": "light_synthesizer"
},
{
"name": "nanites"
},
{
"name": "rgb_lighting"
},
{
"name": "store"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

View File

@ -0,0 +1,41 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from Paradise Station at https://github.com/ParadiseSS13/Paradise/blob/a126858e60caebc7c3a4b25400a5969dcc63f092/icons/mob/telegraphing/telegraph_holographic.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "target_box",
"delays": [
[
0.125,
0.125,
0.125,
0.125,
0.125,
0.125,
0.125,
0.125
]
]
},
{
"name": "target_circle",
"delays": [
[
0.125,
0.125,
0.125,
0.125,
0.125,
0.125,
0.125,
0.125
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB