World Storage (#112)
* Added generic crate assets. Added open flag to StorageComponent. * StorageComponent door toggling works. * Door now updates based on if someone is subscribed to the Storage. * Added License to crate.rsi Fixed Icon of crate. * Added basic Locker. * Added Fire Extinguisher. * Added ability to close the storage UI by the server. Notify the server that the UI is closed, so that the player is unsubscribed from the storage. Unsubscribe the player from a storage if the player entity moves out of range. * Add check to make sure entity is on the same map as the storage. * Update Engine module. * Update Engine.
@@ -27,6 +27,16 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
private int StorageCapacityMax;
|
private int StorageCapacityMax;
|
||||||
private StorageWindow Window;
|
private StorageWindow Window;
|
||||||
|
|
||||||
|
public bool Open
|
||||||
|
{
|
||||||
|
get => _open;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_open = value;
|
||||||
|
SetDoorSprite(_open);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnAdd()
|
public override void OnAdd()
|
||||||
{
|
{
|
||||||
base.OnAdd();
|
base.OnAdd();
|
||||||
@@ -41,6 +51,17 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
base.OnRemove();
|
base.OnRemove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void HandleComponentState(ComponentState state)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(state);
|
||||||
|
|
||||||
|
if (!(state is StorageComponentState storageState))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Open = storageState.Open;
|
||||||
|
}
|
||||||
|
|
||||||
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
{
|
{
|
||||||
switch (message)
|
switch (message)
|
||||||
@@ -54,7 +75,7 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
OpenUI();
|
OpenUI();
|
||||||
break;
|
break;
|
||||||
case CloseStorageUIMessage msg:
|
case CloseStorageUIMessage msg:
|
||||||
// todo: close window/grey it out
|
CloseUI();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,6 +101,11 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
Window.Open();
|
Window.Open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CloseUI()
|
||||||
|
{
|
||||||
|
Window.Close();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Function for clicking one of the stored entity buttons in the UI, tells server to remove that entity
|
/// Function for clicking one of the stored entity buttons in the UI, tells server to remove that entity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -89,6 +115,22 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
SendNetworkMessage(new RemoveEntityMessage(entityuid));
|
SendNetworkMessage(new RemoveEntityMessage(entityuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetDoorSprite(bool open)
|
||||||
|
{
|
||||||
|
if(!Owner.TryGetComponent<ISpriteComponent>(out var spriteComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!spriteComp.Running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var baseName = spriteComp.LayerGetState(0).Name;
|
||||||
|
|
||||||
|
var stateId = open ? $"{baseName}_open" : $"{baseName}_door";
|
||||||
|
|
||||||
|
if (spriteComp.BaseRSI.TryGetState(stateId, out _))
|
||||||
|
spriteComp.LayerSetState(1, stateId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GUI class for client storage component
|
/// GUI class for client storage component
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -113,6 +155,12 @@ namespace Content.Client.GameObjects.Components.Storage
|
|||||||
Information = VSplitContainer.GetChild<Label>("Information");
|
Information = VSplitContainer.GetChild<Label>("Information");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Close()
|
||||||
|
{
|
||||||
|
StorageEntity.SendNetworkMessage(new CloseStorageUIMessage());
|
||||||
|
base.Close();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loops through stored entities creating buttons for each, updates information labels
|
/// Loops through stored entities creating buttons for each, updates information labels
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -102,6 +102,7 @@
|
|||||||
<Compile Include="GameObjects\EntitySystems\PowerApcSystem.cs" />
|
<Compile Include="GameObjects\EntitySystems\PowerApcSystem.cs" />
|
||||||
<Compile Include="GameObjects\EntitySystems\PowerSmesSystem.cs" />
|
<Compile Include="GameObjects\EntitySystems\PowerSmesSystem.cs" />
|
||||||
<Compile Include="GameObjects\EntitySystems\PowerSystem.cs" />
|
<Compile Include="GameObjects\EntitySystems\PowerSystem.cs" />
|
||||||
|
<Compile Include="GameObjects\EntitySystems\StorageSystem.cs" />
|
||||||
<Compile Include="GameObjects\EntitySystems\WelderSystem.cs" />
|
<Compile Include="GameObjects\EntitySystems\WelderSystem.cs" />
|
||||||
<Compile Include="GameObjects\EntitySystems\TemperatureSystem.cs" />
|
<Compile Include="GameObjects\EntitySystems\TemperatureSystem.cs" />
|
||||||
<Compile Include="Interfaces\GameObjects\Components\Items\IHandsComponent.cs" />
|
<Compile Include="Interfaces\GameObjects\Components\Items\IHandsComponent.cs" />
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using SS14.Shared.IoC;
|
|||||||
using SS14.Shared.Log;
|
using SS14.Shared.Log;
|
||||||
using SS14.Shared.Serialization;
|
using SS14.Shared.Serialization;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using SS14.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects
|
namespace Content.Server.GameObjects
|
||||||
{
|
{
|
||||||
@@ -28,6 +29,20 @@ namespace Content.Server.GameObjects
|
|||||||
private int StorageCapacityMax = 10000;
|
private int StorageCapacityMax = 10000;
|
||||||
public HashSet<IPlayerSession> SubscribedSessions = new HashSet<IPlayerSession>();
|
public HashSet<IPlayerSession> SubscribedSessions = new HashSet<IPlayerSession>();
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool Open
|
||||||
|
{
|
||||||
|
get => _open;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_open == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_open = value;
|
||||||
|
Dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnAdd()
|
public override void OnAdd()
|
||||||
{
|
{
|
||||||
base.OnAdd();
|
base.OnAdd();
|
||||||
@@ -35,6 +50,12 @@ namespace Content.Server.GameObjects
|
|||||||
storage = ContainerManagerComponent.Create<Container>("storagebase", Owner);
|
storage = ContainerManagerComponent.Create<Container>("storagebase", Owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ComponentState GetComponentState()
|
||||||
|
{
|
||||||
|
return new StorageComponentState(_open);
|
||||||
|
}
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
@@ -155,6 +176,7 @@ namespace Content.Server.GameObjects
|
|||||||
Logger.DebugS("Storage", "Storage (UID {0}) subscribed player session (UID {1}).", Owner.Uid, session.AttachedEntityUid);
|
Logger.DebugS("Storage", "Storage (UID {0}) subscribed player session (UID {1}).", Owner.Uid, session.AttachedEntityUid);
|
||||||
session.PlayerStatusChanged += HandlePlayerSessionChangeEvent;
|
session.PlayerStatusChanged += HandlePlayerSessionChangeEvent;
|
||||||
SubscribedSessions.Add(session);
|
SubscribedSessions.Add(session);
|
||||||
|
UpdateDoorState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,10 +185,19 @@ namespace Content.Server.GameObjects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel"></param>
|
/// <param name="channel"></param>
|
||||||
public void UnsubscribeSession(IPlayerSession session)
|
public void UnsubscribeSession(IPlayerSession session)
|
||||||
|
{
|
||||||
|
if(SubscribedSessions.Contains(session))
|
||||||
{
|
{
|
||||||
Logger.DebugS("Storage", "Storage (UID {0}) unsubscribed player session (UID {1}).", Owner.Uid, session.AttachedEntityUid);
|
Logger.DebugS("Storage", "Storage (UID {0}) unsubscribed player session (UID {1}).", Owner.Uid, session.AttachedEntityUid);
|
||||||
SubscribedSessions.Remove(session);
|
SubscribedSessions.Remove(session);
|
||||||
SendNetworkMessage(new CloseStorageUIMessage(), session.ConnectedClient);
|
SendNetworkMessage(new CloseStorageUIMessage(), session.ConnectedClient);
|
||||||
|
UpdateDoorState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDoorState()
|
||||||
|
{
|
||||||
|
Open = SubscribedSessions.Count != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandlePlayerSessionChangeEvent(object obj, SessionStatusEventArgs SSEA)
|
public void HandlePlayerSessionChangeEvent(object obj, SessionStatusEventArgs SSEA)
|
||||||
@@ -210,7 +241,8 @@ namespace Content.Server.GameObjects
|
|||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
case RemoveEntityMessage msg:
|
case RemoveEntityMessage _:
|
||||||
|
{
|
||||||
var playerMan = IoCManager.Resolve<IPlayerManager>();
|
var playerMan = IoCManager.Resolve<IPlayerManager>();
|
||||||
var session = playerMan.GetSessionByChannel(netChannel);
|
var session = playerMan.GetSessionByChannel(netChannel);
|
||||||
var playerentity = session.AttachedEntity;
|
var playerentity = session.AttachedEntity;
|
||||||
@@ -237,6 +269,16 @@ namespace Content.Server.GameObjects
|
|||||||
entity.GetComponent<ITransformComponent>().WorldPosition = ourtransform.WorldPosition;
|
entity.GetComponent<ITransformComponent>().WorldPosition = ourtransform.WorldPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CloseStorageUIMessage _:
|
||||||
|
{
|
||||||
|
var playerMan = IoCManager.Resolve<IPlayerManager>();
|
||||||
|
var session = playerMan.GetSessionByChannel(netChannel);
|
||||||
|
|
||||||
|
UnsubscribeSession(session);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
Content.Server/GameObjects/EntitySystems/StorageSystem.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using SS14.Server.Interfaces.Player;
|
||||||
|
using SS14.Shared.GameObjects;
|
||||||
|
using SS14.Shared.GameObjects.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
|
{
|
||||||
|
class StorageSystem : EntitySystem
|
||||||
|
{
|
||||||
|
private readonly List<IPlayerSession> _sessionCache = new List<IPlayerSession>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
EntityQuery = new TypeEntityQuery(typeof(ServerStorageComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var entity in RelevantEntities)
|
||||||
|
{
|
||||||
|
var storageComp = entity.GetComponent<ServerStorageComponent>();
|
||||||
|
|
||||||
|
// We have to cache the set of sessions because Unsubscribe modifies the original.
|
||||||
|
_sessionCache.Clear();
|
||||||
|
_sessionCache.AddRange(storageComp.SubscribedSessions);
|
||||||
|
|
||||||
|
if (_sessionCache.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var storagePos = entity.Transform.WorldPosition;
|
||||||
|
var storageMap = entity.Transform.MapID;
|
||||||
|
|
||||||
|
foreach (var session in _sessionCache)
|
||||||
|
{
|
||||||
|
var attachedEntity = session.AttachedEntity;
|
||||||
|
|
||||||
|
// The component manages the set of sessions, so this invalid session should be removed soon.
|
||||||
|
if (attachedEntity == null || !attachedEntity.IsValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(storageMap != attachedEntity.Transform.MapID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var distanceSquared = (storagePos - attachedEntity.Transform.WorldPosition).LengthSquared;
|
||||||
|
if (distanceSquared > InteractionSystem.INTERACTION_RANGE_SQUARED)
|
||||||
|
{
|
||||||
|
storageComp.UnsubscribeSession(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,29 @@ namespace Content.Shared.GameObjects.Components.Storage
|
|||||||
{
|
{
|
||||||
public sealed override string Name => "Storage";
|
public sealed override string Name => "Storage";
|
||||||
public override uint? NetID => ContentNetIDs.INVENTORY;
|
public override uint? NetID => ContentNetIDs.INVENTORY;
|
||||||
|
public override Type StateType => typeof(StorageComponentState);
|
||||||
|
|
||||||
|
protected bool _open;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataField(ref _open, "open", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class StorageComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public bool Open { get; }
|
||||||
|
|
||||||
|
public StorageComponentState(bool open)
|
||||||
|
: base(ContentNetIDs.INVENTORY)
|
||||||
|
{
|
||||||
|
Open = open;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
24
Resources/Prototypes/Entities/Container.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
- type: entity
|
||||||
|
id: crate_generic
|
||||||
|
name: Crate
|
||||||
|
description: A large container for items.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Buildings/crate.rsi
|
||||||
|
layers:
|
||||||
|
- state: crate
|
||||||
|
- state: crate_door
|
||||||
|
|
||||||
|
- type: Icon
|
||||||
|
sprite: Buildings/crate.rsi
|
||||||
|
state: crate
|
||||||
|
|
||||||
|
- type: Clickable
|
||||||
|
- type: BoundingBox
|
||||||
|
- type: Collidable
|
||||||
|
- type: Storage
|
||||||
|
Capacity: 60
|
||||||
|
|
||||||
|
placement:
|
||||||
|
snap:
|
||||||
|
- Wall
|
||||||
@@ -69,6 +69,19 @@
|
|||||||
- type: Item
|
- type: Item
|
||||||
Size: 10
|
Size: 10
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: "Fire Extinguisher"
|
||||||
|
parent: BaseItem
|
||||||
|
id: fire_extinguisher
|
||||||
|
description: Extinguishes fires.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
texture: Objects/fire_extinguisher.png
|
||||||
|
- type: Icon
|
||||||
|
texture: Objects/fire_extinguisher.png
|
||||||
|
- type: Item
|
||||||
|
Size: 10
|
||||||
|
|
||||||
#handheld lights
|
#handheld lights
|
||||||
- type: entity
|
- type: entity
|
||||||
name: "Lantern"
|
name: "Lantern"
|
||||||
|
|||||||
24
Resources/Prototypes/Entities/closet.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
- type: entity
|
||||||
|
id: locker_generic
|
||||||
|
name: Locker
|
||||||
|
description: A standard-issue Nanotrasen storage unit.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Buildings/closet.rsi
|
||||||
|
layers:
|
||||||
|
- state: generic
|
||||||
|
- state: generic_door
|
||||||
|
|
||||||
|
- type: Icon
|
||||||
|
sprite: Buildings/closet.rsi
|
||||||
|
state: generic
|
||||||
|
|
||||||
|
- type: Clickable
|
||||||
|
- type: BoundingBox
|
||||||
|
- type: Collidable
|
||||||
|
- type: Storage
|
||||||
|
Capacity: 80
|
||||||
|
|
||||||
|
placement:
|
||||||
|
snap:
|
||||||
|
- Wall
|
||||||
BIN
Resources/Textures/Buildings/closet.rsi/generic.png
Normal file
|
After Width: | Height: | Size: 178 B |
BIN
Resources/Textures/Buildings/closet.rsi/generic_door.png
Normal file
|
After Width: | Height: | Size: 177 B |
BIN
Resources/Textures/Buildings/closet.rsi/generic_open.png
Normal file
|
After Width: | Height: | Size: 165 B |
29
Resources/Textures/Buildings/closet.rsi/meta.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from https://github.com/discordia-space/CEV-Eris.",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "generic",
|
||||||
|
"select": [],
|
||||||
|
"flags": {},
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "generic_door",
|
||||||
|
"select": [],
|
||||||
|
"flags": {},
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "generic_open",
|
||||||
|
"select": [],
|
||||||
|
"flags": {},
|
||||||
|
"directions": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Buildings/crate.rsi/crate.png
Normal file
|
After Width: | Height: | Size: 278 B |
BIN
Resources/Textures/Buildings/crate.rsi/crate_door.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
Resources/Textures/Buildings/crate.rsi/crate_open.png
Normal file
|
After Width: | Height: | Size: 166 B |
29
Resources/Textures/Buildings/crate.rsi/meta.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from https://github.com/discordia-space/CEV-Eris.",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "crate",
|
||||||
|
"select": [],
|
||||||
|
"flags": {},
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "crate_door",
|
||||||
|
"select": [],
|
||||||
|
"flags": {},
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "crate_open",
|
||||||
|
"select": [],
|
||||||
|
"flags": {},
|
||||||
|
"directions": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Objects/fire_extinguisher.png
Normal file
|
After Width: | Height: | Size: 230 B |