Light replacer (#3701)
* Add graphics * Barebone light replacer * Now light replacer stores bulbs * More interactions * Added text messages * Better light refil * Add light replacer to locker and lathe * Added sound * Fixed ignore error * Better inhand * You don't like this commas? * Fixed typo * Deferring spawning * Moved interaction logic to system Co-authored-by: Alex Evgrashin <evgrashin.adl@gmail.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
@@ -233,6 +233,7 @@ namespace Content.Client
|
|||||||
"FoamSolutionAreaEffect",
|
"FoamSolutionAreaEffect",
|
||||||
"GasFilter",
|
"GasFilter",
|
||||||
"Recyclable",
|
"Recyclable",
|
||||||
|
"LightReplacer",
|
||||||
"SecretStash",
|
"SecretStash",
|
||||||
"Toilet",
|
"Toilet",
|
||||||
"ClusterFlash",
|
"ClusterFlash",
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
|
using Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerReceiverUsers;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Janitorial
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Device that allows user to quikly change bulbs in <see cref="PoweredLightComponent"/>
|
||||||
|
/// Can be reloaded by new light tubes or light bulbs
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class LightReplacerComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "LightReplacer";
|
||||||
|
public override uint? NetID => ContentNetIDs.LIGHT_REPLACER;
|
||||||
|
|
||||||
|
[DataField("sound")] private string _sound = "/Audio/Weapons/click.ogg";
|
||||||
|
|
||||||
|
// bulbs that were inside light replacer when it spawned
|
||||||
|
[DataField("contents")] private List<LightReplacerEntity> _contents = new();
|
||||||
|
// bulbs that were inserted inside light replacer
|
||||||
|
[ViewVariables] private IContainer _insertedBulbs = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_insertedBulbs = ContainerHelpers.EnsureContainer<Container>(Owner, "light_replacer_storage");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryReplaceBulb(PoweredLightComponent fixture, IEntity? user = null)
|
||||||
|
{
|
||||||
|
// check if light bulb is broken or missing
|
||||||
|
if (fixture.LightBulb != null && fixture.LightBulb.State == LightBulbState.Normal) return false;
|
||||||
|
|
||||||
|
// try get first inserted bulb of the same type as targeted light fixtutre
|
||||||
|
var bulb = _insertedBulbs.ContainedEntities.FirstOrDefault(
|
||||||
|
(e) => e.GetComponentOrNull<LightBulbComponent>()?.Type == fixture.BulbType);
|
||||||
|
|
||||||
|
// found bulb in inserted storage
|
||||||
|
if (bulb != null)
|
||||||
|
{
|
||||||
|
// try to remove it
|
||||||
|
var hasRemoved = _insertedBulbs.Remove(bulb);
|
||||||
|
if (!hasRemoved)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// try to create new instance of bulb from LightReplacerEntity
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var bulbEnt = _contents.FirstOrDefault((e) => e.Type == fixture.BulbType && e.Amount > 0);
|
||||||
|
|
||||||
|
// found right bulb, let's spawn it
|
||||||
|
if (bulbEnt != null)
|
||||||
|
{
|
||||||
|
bulb = Owner.EntityManager.SpawnEntity(bulbEnt.PrototypeName, Owner.Transform.Coordinates);
|
||||||
|
bulbEnt.Amount--;
|
||||||
|
}
|
||||||
|
// not found any light bulbs
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
var msg = Loc.GetString("comp-light-replacer-missing-light", ("light-replacer", Owner));
|
||||||
|
user.PopupMessage(msg);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert it into fixture
|
||||||
|
var wasReplaced = fixture.ReplaceBulb(bulb);
|
||||||
|
if (wasReplaced)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().Play(Filter.Broadcast(), _sound,
|
||||||
|
Owner, AudioParams.Default.WithVolume(-4f));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return wasReplaced;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertBulb(LightBulbComponent bulb, IEntity? user = null, bool showTooltip = false)
|
||||||
|
{
|
||||||
|
// only normal lights can be inserted inside light replacer
|
||||||
|
if (bulb.State != LightBulbState.Normal)
|
||||||
|
{
|
||||||
|
if (showTooltip && user != null)
|
||||||
|
{
|
||||||
|
var msg = Loc.GetString("comp-light-replacer-insert-broken-light");
|
||||||
|
user.PopupMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try insert light and show message
|
||||||
|
var hasInsert = _insertedBulbs.Insert(bulb.Owner);
|
||||||
|
if (hasInsert && showTooltip && user != null)
|
||||||
|
{
|
||||||
|
var msg = Loc.GetString("comp-light-replacer-insert-light",
|
||||||
|
("light-replacer", Owner), ("bulb", bulb.Owner));
|
||||||
|
user.PopupMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return hasInsert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertBulb(ServerStorageComponent storage, IEntity? user = null)
|
||||||
|
{
|
||||||
|
if (storage.StoredEntities == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var insertedBulbs = 0;
|
||||||
|
var storagedEnts = storage.StoredEntities.ToArray();
|
||||||
|
foreach (var ent in storagedEnts)
|
||||||
|
{
|
||||||
|
if (ent.TryGetComponent(out LightBulbComponent? bulb))
|
||||||
|
{
|
||||||
|
if (TryInsertBulb(bulb))
|
||||||
|
insertedBulbs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show some message if success
|
||||||
|
if (insertedBulbs > 0 && user != null)
|
||||||
|
{
|
||||||
|
var msg = Loc.GetString("comp-light-replacer-refill-from-storage", ("light-replacer", Owner));
|
||||||
|
user.PopupMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return insertedBulbs > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
[DataDefinition]
|
||||||
|
public class LightReplacerEntity
|
||||||
|
{
|
||||||
|
[DataField("name", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
|
public string? PrototypeName;
|
||||||
|
|
||||||
|
[DataField("amount")]
|
||||||
|
public int Amount;
|
||||||
|
|
||||||
|
[DataField("type")]
|
||||||
|
public LightBulbType Type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,12 +62,13 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
[ViewVariables] [DataField("ignoreGhostsBoo")]
|
[ViewVariables] [DataField("ignoreGhostsBoo")]
|
||||||
private bool _ignoreGhostsBoo;
|
private bool _ignoreGhostsBoo;
|
||||||
|
|
||||||
[DataField("bulb")]
|
[DataField("bulb")] private LightBulbType _bulbType = LightBulbType.Tube;
|
||||||
private LightBulbType BulbType = LightBulbType.Tube;
|
public LightBulbType BulbType => _bulbType;
|
||||||
|
|
||||||
[ViewVariables] private ContainerSlot _lightBulbContainer = default!;
|
[ViewVariables] private ContainerSlot _lightBulbContainer = default!;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private LightBulbComponent? LightBulb
|
public LightBulbComponent? LightBulb
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -126,6 +127,15 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to replace current bulb with a new one
|
||||||
|
/// </summary>
|
||||||
|
public bool ReplaceBulb(IEntity bulb)
|
||||||
|
{
|
||||||
|
EjectBulb();
|
||||||
|
return InsertBulb(bulb);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inserts the bulb if possible.
|
/// Inserts the bulb if possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -134,7 +144,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
{
|
{
|
||||||
if (LightBulb != null) return false;
|
if (LightBulb != null) return false;
|
||||||
if (!bulb.TryGetComponent(out LightBulbComponent? lightBulb)) return false;
|
if (!bulb.TryGetComponent(out LightBulbComponent? lightBulb)) return false;
|
||||||
if (lightBulb.Type != BulbType) return false;
|
if (lightBulb.Type != _bulbType) return false;
|
||||||
|
|
||||||
var inserted = _lightBulbContainer.Insert(bulb);
|
var inserted = _lightBulbContainer.Insert(bulb);
|
||||||
|
|
||||||
@@ -149,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ejects the bulb to a mob's hand if possible.
|
/// Ejects the bulb to a mob's hand if possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void EjectBulb(IEntity user)
|
private void EjectBulb(IEntity? user = null)
|
||||||
{
|
{
|
||||||
if (LightBulb == null) return;
|
if (LightBulb == null) return;
|
||||||
|
|
||||||
@@ -160,9 +170,17 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
|
|
||||||
if (!_lightBulbContainer.Remove(bulb.Owner)) return;
|
if (!_lightBulbContainer.Remove(bulb.Owner)) return;
|
||||||
|
|
||||||
if (!user.TryGetComponent(out HandsComponent? hands)
|
if (user != null)
|
||||||
|| !hands.PutInHand(bulb.Owner.GetComponent<ItemComponent>()))
|
{
|
||||||
bulb.Owner.Transform.Coordinates = user.Transform.Coordinates;
|
if (!user.TryGetComponent(out HandsComponent? hands)
|
||||||
|
|| !hands.PutInHand(bulb.Owner.GetComponent<ItemComponent>()))
|
||||||
|
bulb.Owner.Transform.Coordinates = user.Transform.Coordinates;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bulb.Owner.Transform.Coordinates = Owner.Transform.Coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -259,7 +277,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
{
|
{
|
||||||
if (_hasLampOnSpawn)
|
if (_hasLampOnSpawn)
|
||||||
{
|
{
|
||||||
var prototype = BulbType switch
|
var prototype = _bulbType switch
|
||||||
{
|
{
|
||||||
LightBulbType.Bulb => "LightBulb",
|
LightBulbType.Bulb => "LightBulb",
|
||||||
LightBulbType.Tube => "LightTube",
|
LightBulbType.Tube => "LightTube",
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
|
using Content.Server.GameObjects.Components.Janitorial;
|
||||||
|
using Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerReceiverUsers;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||||
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems.Janitorial
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class LightReplacerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<LightReplacerComponent, InteractUsingMessage>(HandleInteract);
|
||||||
|
SubscribeLocalEvent<LightReplacerComponent, AfterInteractMessage>(HandleAfterInteract);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
|
||||||
|
UnsubscribeLocalEvent<LightReplacerComponent, InteractUsingMessage>(HandleInteract);
|
||||||
|
UnsubscribeLocalEvent<LightReplacerComponent, AfterInteractMessage>(HandleAfterInteract);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleAfterInteract(EntityUid uid, LightReplacerComponent component, AfterInteractMessage eventArgs)
|
||||||
|
{
|
||||||
|
// standard interaction checks
|
||||||
|
if (!ActionBlockerSystem.CanUse(eventArgs.User)) return;
|
||||||
|
if (!eventArgs.CanReach) return;
|
||||||
|
|
||||||
|
// behaviour will depends on target type
|
||||||
|
if (eventArgs.Attacked != null)
|
||||||
|
{
|
||||||
|
// replace broken light in fixture?
|
||||||
|
if (eventArgs.Attacked.TryGetComponent(out PoweredLightComponent? fixture))
|
||||||
|
component.TryReplaceBulb(fixture, eventArgs.User);
|
||||||
|
// add new bulb to light replacer container?
|
||||||
|
else if (eventArgs.Attacked.TryGetComponent(out LightBulbComponent? bulb))
|
||||||
|
component.TryInsertBulb(bulb, eventArgs.User, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleInteract(EntityUid uid, LightReplacerComponent component, InteractUsingMessage eventArgs)
|
||||||
|
{
|
||||||
|
// standard interaction checks
|
||||||
|
if (!ActionBlockerSystem.CanInteract(eventArgs.User)) return;
|
||||||
|
|
||||||
|
if (eventArgs.ItemInHand != null)
|
||||||
|
{
|
||||||
|
// want to insert a new light bulb?
|
||||||
|
if (eventArgs.ItemInHand.TryGetComponent(out LightBulbComponent? bulb))
|
||||||
|
component.TryInsertBulb(bulb, eventArgs.User, true);
|
||||||
|
// add bulbs from storage?
|
||||||
|
else if (eventArgs.ItemInHand.TryGetComponent(out ServerStorageComponent? storage))
|
||||||
|
component.TryInsertBulb(storage, eventArgs.User);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,7 @@ namespace Content.Shared.GameObjects
|
|||||||
public const uint DOOR = 1087;
|
public const uint DOOR = 1087;
|
||||||
public const uint SPAWN_AFTER_INTERACT = 1088;
|
public const uint SPAWN_AFTER_INTERACT = 1088;
|
||||||
public const uint DISASSEMBLE_ON_ACTIVATE = 1089;
|
public const uint DISASSEMBLE_ON_ACTIVATE = 1089;
|
||||||
|
public const uint LIGHT_REPLACER = 1090;
|
||||||
|
|
||||||
// Net IDs for integration tests.
|
// Net IDs for integration tests.
|
||||||
public const uint PREDICTION_TEST = 10001;
|
public const uint PREDICTION_TEST = 10001;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
### Interaction Messages
|
||||||
|
|
||||||
|
# Shown when player tries to replace light, but there is no lighs left
|
||||||
|
comp-light-replacer-missing-light = No lights left in {$light-replacer}.
|
||||||
|
|
||||||
|
# Shown when player inserts light bulb inside light replacer
|
||||||
|
comp-light-replacer-insert-light = You insert {$bulb} into {$light-replacer}.
|
||||||
|
|
||||||
|
# Shown when player tries to insert in light replacer brolen light bulb
|
||||||
|
comp-light-replacer-insert-broken-light = You can't insert broken lights!
|
||||||
|
|
||||||
|
# Shown when player refill light from light box
|
||||||
|
comp-light-replacer-refill-from-storage = You refill {$light-replacer}.
|
||||||
@@ -47,6 +47,10 @@
|
|||||||
amount: 3
|
amount: 3
|
||||||
- name: TrashBag
|
- name: TrashBag
|
||||||
amount: 2
|
amount: 2
|
||||||
|
- name: LightReplacer
|
||||||
|
amount: 1
|
||||||
|
- name: BoxLightMixed
|
||||||
|
amount: 1
|
||||||
|
|
||||||
#- type: entity
|
#- type: entity
|
||||||
# id: LockerLegalFilled
|
# id: LockerLegalFilled
|
||||||
|
|||||||
@@ -266,3 +266,24 @@
|
|||||||
quickInsert: true
|
quickInsert: true
|
||||||
areaInsert: true
|
areaInsert: true
|
||||||
storageSoundCollection: trashBagRustle
|
storageSoundCollection: trashBagRustle
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
name: light replacer
|
||||||
|
id: LightReplacer
|
||||||
|
description: An item which uses magnets to easily replace broken lights.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Specific/Janitorial/light_replacer.rsi
|
||||||
|
state: icon
|
||||||
|
- type: Item
|
||||||
|
sprite: Objects/Specific/Janitorial/light_replacer.rsi
|
||||||
|
- type: LightReplacer
|
||||||
|
contents:
|
||||||
|
- name: LightTube
|
||||||
|
amount: 8
|
||||||
|
type: Tube
|
||||||
|
- name: LightBulb
|
||||||
|
amount: 5
|
||||||
|
type: Bulb
|
||||||
|
|
||||||
|
|||||||
@@ -46,3 +46,12 @@
|
|||||||
completetime: 300
|
completetime: 300
|
||||||
materials:
|
materials:
|
||||||
plastic: 100
|
plastic: 100
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: LightReplacer
|
||||||
|
icon: Objects/Specific/Janitorial/light_replacer.rsi
|
||||||
|
result: LightReplacer
|
||||||
|
completetime: 600
|
||||||
|
materials:
|
||||||
|
steel: 100
|
||||||
|
glass: 1000
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 361 B |
Binary file not shown.
|
After Width: | Height: | Size: 328 B |
Binary file not shown.
|
After Width: | Height: | Size: 502 B |
Binary file not shown.
|
After Width: | Height: | Size: 601 B |
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from https://github.com/discordia-space/CEV-Eris/commit/d76180f48949870dd57c7274d494175b3b3515ba",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "emagged"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user