mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 18:31:17 +03:00
Merge remote-tracking branch 'upstream/master' into upstream-sync
# Conflicts: # Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs # Content.Server/Zombies/ZombifyOnDeathSystem.cs # Content.Shared/Humanoid/HumanoidComponent.cs # Content.Shared/Humanoid/SharedHumanoidSystem.cs # Content.Shared/Preferences/HumanoidCharacterProfile.cs # Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
This commit is contained in:
commit
f3bd98ceed
224 changed files with 191845 additions and 39928 deletions
|
|
@ -65,16 +65,22 @@ namespace Content.Client.Buckle
|
|||
|
||||
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
|
||||
return;
|
||||
|
||||
if (!_appearanceSystem.TryGetData<int>(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
|
||||
!_appearanceSystem.TryGetData<bool>(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
|
||||
!buckled ||
|
||||
args.Sprite == null)
|
||||
{
|
||||
_rotationVisualizerSystem.SetHorizontalAngle(uid, RotationVisualsComponent.DefaultRotation, rotVisuals);
|
||||
return;
|
||||
}
|
||||
|
||||
// Animate strapping yourself to something at a given angle
|
||||
_rotationVisualizerSystem.AnimateSpriteRotation(args.Sprite, Angle.FromDegrees(angle), 0.125f);
|
||||
_rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals);
|
||||
// TODO: Dump this when buckle is better
|
||||
_rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
|||
if (!args.Sprite.LayerMapTryGet(component.Layer, out var fillLayer))
|
||||
return;
|
||||
|
||||
// Currently some solution methods such as overflowing will try to update appearance with a
|
||||
// volume greater than the max volume. We'll clamp it so players don't see
|
||||
// a giant error sign and error for debug.
|
||||
if (fraction > 1f)
|
||||
{
|
||||
Logger.Error("Attempted to set solution container visuals volume ratio on " + ToPrettyString(uid) + " to a value greater than 1. Volume should never be greater than max volume!");
|
||||
fraction = 1f;
|
||||
}
|
||||
|
||||
var closestFillSprite = (int) Math.Round(fraction * component.MaxFillLevels);
|
||||
|
||||
if (closestFillSprite > 0)
|
||||
|
|
|
|||
|
|
@ -65,9 +65,8 @@ public sealed class ClientClothingSystem : ClothingSystem
|
|||
if (!TryComp(uid, out SpriteComponent? sprite) || !sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
|
||||
return;
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(HumanoidVisualizerKey.Key, out object? obj)
|
||||
|| obj is not HumanoidVisualizerData data
|
||||
|| data.Sex != Sex.Female
|
||||
if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid)
|
||||
|| humanoid.Sex != Sex.Female
|
||||
|| !_inventorySystem.TryGetSlotEntity(uid, "jumpsuit", out var suit, component)
|
||||
|| !TryComp(suit, out ClothingComponent? clothing))
|
||||
{
|
||||
|
|
@ -219,8 +218,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
|||
|
||||
if (slot == "jumpsuit" && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var suitLayer))
|
||||
{
|
||||
if (_appearance.TryGetData<HumanoidVisualizerData>(equipee, HumanoidVisualizerKey.Key, out var data)
|
||||
&& data.Sex == Sex.Female)
|
||||
if (TryComp(equipee, out HumanoidAppearanceComponent? humanoid) && humanoid.Sex == Sex.Female)
|
||||
{
|
||||
sprite.LayerSetState(suitLayer, clothingComponent.FemaleMask switch
|
||||
{
|
||||
|
|
|
|||
351
Content.Client/Humanoid/HumanoidAppearanceSystem.cs
Normal file
351
Content.Client/Humanoid/HumanoidAppearanceSystem.cs
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not HumanoidAppearanceState state)
|
||||
return;
|
||||
|
||||
ApplyState(uid, component, Comp<SpriteComponent>(uid), state);
|
||||
}
|
||||
|
||||
private void ApplyState(EntityUid uid, HumanoidAppearanceComponent component, SpriteComponent sprite, HumanoidAppearanceState state)
|
||||
{
|
||||
component.Sex = state.Sex;
|
||||
component.Species = state.Species;
|
||||
component.Age = state.Age;
|
||||
component.SkinColor = state.SkinColor;
|
||||
component.EyeColor = state.EyeColor;
|
||||
component.HiddenLayers = new(state.HiddenLayers);
|
||||
component.PermanentlyHidden = new(state.PermanentlyHidden);
|
||||
|
||||
component.CustomBaseLayers = state.CustomBaseLayers.ShallowClone();
|
||||
UpdateLayers(component, sprite);
|
||||
|
||||
ApplyMarkingSet(uid, state.Markings, component, sprite);
|
||||
|
||||
sprite[sprite.LayerMapReserveBlank(HumanoidVisualLayers.Eyes)].Color = state.EyeColor;
|
||||
}
|
||||
|
||||
private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
|
||||
=> humanoid.HiddenLayers.Contains(layer) || humanoid.PermanentlyHidden.Contains(layer);
|
||||
|
||||
private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
|
||||
{
|
||||
var oldLayers = new HashSet<HumanoidVisualLayers>(component.BaseLayers.Keys);
|
||||
component.BaseLayers.Clear();
|
||||
|
||||
// add default species layers
|
||||
var speciesProto = _prototypeManager.Index<SpeciesPrototype>(component.Species);
|
||||
var baseSprites = _prototypeManager.Index<HumanoidSpeciesBaseSpritesPrototype>(speciesProto.SpriteSet);
|
||||
foreach (var (key, id) in baseSprites.Sprites)
|
||||
{
|
||||
oldLayers.Remove(key);
|
||||
if (!component.CustomBaseLayers.ContainsKey(key))
|
||||
SetLayerData(component, sprite, key, id, sexMorph: true);
|
||||
}
|
||||
|
||||
// add custom layers
|
||||
foreach (var (key, info) in component.CustomBaseLayers)
|
||||
{
|
||||
oldLayers.Remove(key);
|
||||
SetLayerData(component, sprite, key, info.ID, sexMorph: false, color: info.Color); ;
|
||||
}
|
||||
|
||||
// hide old layers
|
||||
// TODO maybe just remove them altogether?
|
||||
foreach (var key in oldLayers)
|
||||
{
|
||||
if (sprite.LayerMapTryGet(key, out var index))
|
||||
sprite[index].Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLayerData(
|
||||
HumanoidAppearanceComponent component,
|
||||
SpriteComponent sprite,
|
||||
HumanoidVisualLayers key,
|
||||
string protoId,
|
||||
bool sexMorph = false,
|
||||
Color? color = null)
|
||||
{
|
||||
if (sexMorph)
|
||||
protoId = HumanoidVisualLayersExtension.GetSexMorph(key, component.Sex, protoId);
|
||||
|
||||
var proto = _prototypeManager.Index<HumanoidSpeciesSpriteLayer>(protoId);
|
||||
component.BaseLayers[key] = proto;
|
||||
|
||||
var layerIndex = sprite.LayerMapReserveBlank(key);
|
||||
var layer = sprite[layerIndex];
|
||||
|
||||
if (color != null)
|
||||
layer.Color = color.Value;
|
||||
else if (proto.MatchSkin)
|
||||
layer.Color = proto.MatchSkin ? component.SkinColor.WithAlpha(proto.LayerAlpha) : Color.White;
|
||||
|
||||
if (proto.BaseSprite != null)
|
||||
sprite.LayerSetSprite(layerIndex, proto.BaseSprite);
|
||||
|
||||
layer.Visible = !IsHidden(component, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a profile directly into a humanoid.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid entity's UID</param>
|
||||
/// <param name="profile">The profile to load.</param>
|
||||
/// <param name="humanoid">The humanoid entity's humanoid component.</param>
|
||||
/// <remarks>
|
||||
/// This should not be used if the entity is owned by the server. The server will otherwise
|
||||
/// override this with the appearance data it sends over.
|
||||
/// </remarks>
|
||||
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>();
|
||||
|
||||
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
||||
var markings = new MarkingSet(profile.Appearance.Markings, speciesPrototype.MarkingPoints, _markingManager,
|
||||
_prototypeManager);
|
||||
markings.EnsureDefault(profile.Appearance.SkinColor, _markingManager);
|
||||
|
||||
// legacy: remove in the future?
|
||||
markings.RemoveCategory(MarkingCategories.Hair);
|
||||
markings.RemoveCategory(MarkingCategories.FacialHair);
|
||||
|
||||
var hair = new Marking(profile.Appearance.HairStyleId, new[] { profile.Appearance.HairColor });
|
||||
markings.AddBack(MarkingCategories.Hair, hair);
|
||||
|
||||
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
|
||||
new[] { profile.Appearance.FacialHairColor });
|
||||
markings.AddBack(MarkingCategories.FacialHair, facialHair);
|
||||
|
||||
markings.FilterSpecies(profile.Species, _markingManager, _prototypeManager);
|
||||
|
||||
DebugTools.Assert(uid.IsClientSide());
|
||||
|
||||
var state = new HumanoidAppearanceState(markings,
|
||||
new(),
|
||||
new(),
|
||||
customBaseLayers,
|
||||
profile.Sex,
|
||||
profile.Gender,
|
||||
profile.Age,
|
||||
profile.Species,
|
||||
profile.Appearance.SkinColor,
|
||||
profile.Appearance.EyeColor);
|
||||
|
||||
ApplyState(uid, humanoid, Comp<SpriteComponent>(uid), state);
|
||||
}
|
||||
|
||||
private void ApplyMarkingSet(EntityUid uid,
|
||||
MarkingSet newMarkings,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
// skip this entire thing if both sets are empty
|
||||
if (humanoid.MarkingSet.Markings.Count == 0 && newMarkings.Markings.Count == 0)
|
||||
return;
|
||||
|
||||
// I am lazy and I CBF resolving the previous mess, so I'm just going to nuke the markings.
|
||||
// Really, markings should probably be a separate component altogether.
|
||||
|
||||
ClearAllMarkings(uid, humanoid, sprite);
|
||||
|
||||
humanoid.MarkingSet = new(newMarkings);
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
|
||||
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllMarkings(EntityUid uid, HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
RemoveMarking(uid, marking, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearMarkings(EntityUid uid, List<Marking> markings, HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
RemoveMarking(uid, marking, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMarking(EntityUid uid, Marking marking,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sprite in prototype.Sprites)
|
||||
{
|
||||
if (sprite is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
|
||||
if (!spriteComp.LayerMapTryGet(layerId, out var index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
spriteComp.LayerMapRemove(layerId);
|
||||
spriteComp.RemoveLayer(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarking(EntityUid uid,
|
||||
MarkingPrototype markingPrototype,
|
||||
IReadOnlyList<Color>? colors,
|
||||
bool visible,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
visible &= !IsHidden(humanoid, markingPrototype.BodyPart);
|
||||
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
|
||||
&& setting.AllowsMarkings;
|
||||
|
||||
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
|
||||
{
|
||||
if (markingPrototype.Sprites[j] is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
if (!sprite.LayerMapTryGet(layerId, out _))
|
||||
{
|
||||
var layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
|
||||
sprite.LayerMapSet(layerId, layer);
|
||||
sprite.LayerSetSprite(layerId, rsi);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(layerId, visible);
|
||||
|
||||
if (!visible || setting == null) // this is kinda implied
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (markingPrototype.FollowSkinColor || colors == null || setting.MarkingsMatchSkin)
|
||||
{
|
||||
var skinColor = humanoid.SkinColor;
|
||||
skinColor.A = setting.LayerAlpha;
|
||||
|
||||
sprite.LayerSetColor(layerId, skinColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetColor(layerId, colors[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid) || humanoid.SkinColor == skinColor)
|
||||
return;
|
||||
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
|
||||
if (!TryComp(uid, out SpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
|
||||
{
|
||||
if (!spriteInfo.MatchSkin)
|
||||
continue;
|
||||
|
||||
var index = sprite.LayerMapReserveBlank(layer);
|
||||
sprite[index].Color = skinColor.WithAlpha(spriteInfo.LayerAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetLayerVisibility(
|
||||
EntityUid uid,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent,
|
||||
ref bool dirty)
|
||||
{
|
||||
base.SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
|
||||
var sprite = Comp<SpriteComponent>(uid);
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
if (!visible)
|
||||
return;
|
||||
else
|
||||
index = sprite.LayerMapReserveBlank(layer);
|
||||
}
|
||||
|
||||
var spriteLayer = sprite[index];
|
||||
if (spriteLayer.Visible == visible)
|
||||
return;
|
||||
|
||||
spriteLayer.Visible = visible;
|
||||
|
||||
// I fucking hate this. I'll get around to refactoring sprite layers eventually I swear
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
|
||||
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Robust.Client.GameObjects;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Robust.Client.AutoGenerated;
|
|||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ public sealed partial class HumanoidMarkingModifierWindow : DefaultWindow
|
|||
continue;
|
||||
}
|
||||
|
||||
modifier.SetState(true, layerInfo.ID, layerInfo.Color);
|
||||
modifier.SetState(true, layerInfo.ID, layerInfo.Color ?? Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HumanoidSystem : SharedHumanoidSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Loads a profile directly into a humanoid.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid entity's UID</param>
|
||||
/// <param name="profile">The profile to load.</param>
|
||||
/// <param name="humanoid">The humanoid entity's humanoid component.</param>
|
||||
/// <remarks>
|
||||
/// This should not be used if the entity is owned by the server. The server will otherwise
|
||||
/// override this with the appearance data it sends over.
|
||||
/// </remarks>
|
||||
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.Species = profile.Species;
|
||||
var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>
|
||||
{
|
||||
[HumanoidVisualLayers.Eyes] = new CustomBaseLayerInfo(string.Empty, profile.Appearance.EyeColor)
|
||||
};
|
||||
|
||||
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
||||
var markings = new MarkingSet(profile.Appearance.Markings, speciesPrototype.MarkingPoints, _markingManager,
|
||||
_prototypeManager);
|
||||
markings.EnsureDefault(profile.Appearance.SkinColor, _markingManager);
|
||||
|
||||
// legacy: remove in the future?
|
||||
markings.RemoveCategory(MarkingCategories.Hair);
|
||||
markings.RemoveCategory(MarkingCategories.FacialHair);
|
||||
|
||||
var hair = new Marking(profile.Appearance.HairStyleId, new[] { profile.Appearance.HairColor });
|
||||
markings.AddBack(MarkingCategories.Hair, hair);
|
||||
|
||||
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
|
||||
new[] { profile.Appearance.FacialHairColor });
|
||||
markings.AddBack(MarkingCategories.FacialHair, facialHair);
|
||||
|
||||
markings.FilterSpecies(profile.Species, _markingManager, _prototypeManager);
|
||||
|
||||
SetAppearance(uid,
|
||||
profile.Species,
|
||||
customBaseLayers,
|
||||
profile.Appearance.SkinColor,
|
||||
profile.Sex,
|
||||
new(), // doesn't exist yet
|
||||
markings.GetForwardEnumerator().ToList());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,447 +0,0 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HumanoidVisualizerSystem : VisualizerSystem<HumanoidComponent>
|
||||
{
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private MarkingManager _markingManager = default!;
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, HumanoidComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
base.OnAppearanceChange(uid, component, ref args);
|
||||
|
||||
if (args.Sprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(HumanoidVisualizerKey.Key, out var dataRaw)
|
||||
|| dataRaw is not HumanoidVisualizerData data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(data.Species, out SpeciesPrototype? speciesProto)
|
||||
|| !_prototypeManager.TryIndex(speciesProto.SpriteSet, out HumanoidSpeciesBaseSpritesPrototype? baseSprites))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dirty = data.SkinColor != component.SkinColor || data.Sex != component.Sex;
|
||||
component.Sex = data.Sex;
|
||||
|
||||
if (data.CustomBaseLayerInfo.Count != 0)
|
||||
{
|
||||
dirty |= MergeCustomBaseSprites(uid, baseSprites.Sprites, data.CustomBaseLayerInfo, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
dirty |= MergeCustomBaseSprites(uid, baseSprites.Sprites, null, component);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
ApplyBaseSprites(uid, component, args.Sprite);
|
||||
ApplySkinColor(uid, data.SkinColor, component, args.Sprite);
|
||||
}
|
||||
|
||||
if (data.CustomBaseLayerInfo.Count != 0)
|
||||
{
|
||||
foreach (var (layer, info) in data.CustomBaseLayerInfo)
|
||||
{
|
||||
SetBaseLayerColor(uid, layer, info.Color, args.Sprite);
|
||||
}
|
||||
}
|
||||
|
||||
var layerVis = data.LayerVisibility.ToHashSet();
|
||||
dirty |= ReplaceHiddenLayers(uid, layerVis, component, args.Sprite);
|
||||
|
||||
DiffAndApplyMarkings(uid, data.Markings, dirty, component, args.Sprite);
|
||||
}
|
||||
|
||||
private bool ReplaceHiddenLayers(EntityUid uid, HashSet<HumanoidVisualLayers> hiddenLayers,
|
||||
HumanoidComponent humanoid, SpriteComponent sprite)
|
||||
{
|
||||
if (hiddenLayers.SetEquals(humanoid.HiddenLayers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetSpriteVisibility(uid, hiddenLayers, false, sprite);
|
||||
|
||||
humanoid.HiddenLayers.ExceptWith(hiddenLayers);
|
||||
|
||||
SetSpriteVisibility(uid, humanoid.HiddenLayers, true, sprite);
|
||||
|
||||
humanoid.HiddenLayers.Clear();
|
||||
humanoid.HiddenLayers.UnionWith(hiddenLayers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetSpriteVisibility(EntityUid uid, HashSet<HumanoidVisualLayers> layers, bool visibility, SpriteComponent sprite)
|
||||
{
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sprite[index].Visible = visibility;
|
||||
}
|
||||
}
|
||||
|
||||
private void DiffAndApplyMarkings(EntityUid uid,
|
||||
List<Marking> newMarkings,
|
||||
bool layersDirty,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
// skip this entire thing if both sets are empty
|
||||
if (humanoid.CurrentClientMarkings.Count == 0 && newMarkings.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dirtyMarkings = new List<int>();
|
||||
var dirtyRangeStart = humanoid.CurrentClientMarkings.Count == 0 ? 0 : -1;
|
||||
|
||||
// edge cases:
|
||||
// humanoid.CurrentClientMarkings < newMarkings.Count
|
||||
// - check if count matches this condition before diffing
|
||||
// - if count is unequal, set dirty range to start from humanoid.CurrentClientMarkings.Count
|
||||
// humanoid.CurrentClientMarkings > newMarkings.Count, no dirty markings
|
||||
// - break count upon meeting this condition
|
||||
// - clear markings from newMarkings.Count to humanoid.CurrentClientMarkings.Count - newMarkings.Count
|
||||
|
||||
for (var i = 0; i < humanoid.CurrentClientMarkings.Count; i++)
|
||||
{
|
||||
// if we've reached the end of the new set of markings,
|
||||
// then that means it's time to finish
|
||||
if (newMarkings.Count == i)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// if the marking is different here, set the range start to i and break, we need
|
||||
// to rebuild all markings starting from i
|
||||
if (humanoid.CurrentClientMarkings[i].MarkingId != newMarkings[i].MarkingId)
|
||||
{
|
||||
dirtyRangeStart = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise, we add the current marking to dirtyMarkings if it has different
|
||||
// settings
|
||||
// however: if the hidden layers are set to dirty, then we need to
|
||||
// instead just add every single marking, since we don't know ahead of time
|
||||
// where these markings go
|
||||
if (humanoid.CurrentClientMarkings[i] != newMarkings[i] || layersDirty)
|
||||
{
|
||||
dirtyMarkings.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var i in dirtyMarkings)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(newMarkings[i], out var dirtyMarking))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyMarking(uid, dirtyMarking, newMarkings[i].MarkingColors, newMarkings[i].Visible, humanoid, sprite);
|
||||
}
|
||||
|
||||
if (humanoid.CurrentClientMarkings.Count < newMarkings.Count && dirtyRangeStart < 0)
|
||||
{
|
||||
dirtyRangeStart = humanoid.CurrentClientMarkings.Count;
|
||||
}
|
||||
|
||||
if (dirtyRangeStart >= 0)
|
||||
{
|
||||
var range = newMarkings.GetRange(dirtyRangeStart, newMarkings.Count - dirtyRangeStart);
|
||||
|
||||
if (humanoid.CurrentClientMarkings.Count > 0)
|
||||
{
|
||||
var oldRange = humanoid.CurrentClientMarkings.GetRange(dirtyRangeStart, humanoid.CurrentClientMarkings.Count - dirtyRangeStart);
|
||||
ClearMarkings(uid, oldRange, humanoid, sprite);
|
||||
}
|
||||
|
||||
ApplyMarkings(uid, range, humanoid, sprite);
|
||||
}
|
||||
else if (humanoid.CurrentClientMarkings.Count != newMarkings.Count)
|
||||
{
|
||||
if (newMarkings.Count == 0)
|
||||
{
|
||||
ClearAllMarkings(uid, humanoid, sprite);
|
||||
}
|
||||
else if (humanoid.CurrentClientMarkings.Count > newMarkings.Count)
|
||||
{
|
||||
var rangeStart = newMarkings.Count;
|
||||
var rangeCount = humanoid.CurrentClientMarkings.Count - newMarkings.Count;
|
||||
var range = humanoid.CurrentClientMarkings.GetRange(rangeStart, rangeCount);
|
||||
|
||||
ClearMarkings(uid, range, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirtyMarkings.Count > 0 || dirtyRangeStart >= 0 || humanoid.CurrentClientMarkings.Count != newMarkings.Count)
|
||||
{
|
||||
humanoid.CurrentClientMarkings = newMarkings;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllMarkings(EntityUid uid, HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
ClearMarkings(uid, humanoid.CurrentClientMarkings, humanoid, spriteComp);
|
||||
}
|
||||
|
||||
private void ClearMarkings(EntityUid uid, List<Marking> markings, HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
RemoveMarking(uid, marking, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMarking(EntityUid uid, Marking marking,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sprite in prototype.Sprites)
|
||||
{
|
||||
if (sprite is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
|
||||
if (!spriteComp.LayerMapTryGet(layerId, out var index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
spriteComp.LayerMapRemove(layerId);
|
||||
spriteComp.RemoveLayer(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarkings(EntityUid uid,
|
||||
List<Marking> markings,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var marking in new ReverseMarkingEnumerator(markings))
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var markingPrototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarking(EntityUid uid,
|
||||
MarkingPrototype markingPrototype,
|
||||
IReadOnlyList<Color>? colors,
|
||||
bool visible,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
visible &= !humanoid.HiddenLayers.Contains(markingPrototype.BodyPart);
|
||||
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
|
||||
&& setting.AllowsMarkings;
|
||||
|
||||
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
|
||||
{
|
||||
if (markingPrototype.Sprites[j] is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
if (!sprite.LayerMapTryGet(layerId, out _))
|
||||
{
|
||||
var layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
|
||||
sprite.LayerMapSet(layerId, layer);
|
||||
sprite.LayerSetSprite(layerId, rsi);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(layerId, visible);
|
||||
|
||||
if (!visible || setting == null) // this is kinda implied
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (markingPrototype.FollowSkinColor || colors == null || setting.MarkingsMatchSkin)
|
||||
{
|
||||
var skinColor = humanoid.SkinColor;
|
||||
skinColor.A = setting.LayerAlpha;
|
||||
|
||||
sprite.LayerSetColor(layerId, skinColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetColor(layerId, colors[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySkinColor(EntityUid uid,
|
||||
Color skinColor,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
|
||||
{
|
||||
if (!spriteInfo.MatchSkin)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var color = skinColor;
|
||||
color.A = spriteInfo.LayerAlpha;
|
||||
|
||||
SetBaseLayerColor(uid, layer, color, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBaseLayerColor(EntityUid uid, HumanoidVisualLayers layer, Color color,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sprite[index].Color = color;
|
||||
}
|
||||
|
||||
private bool MergeCustomBaseSprites(EntityUid uid, Dictionary<HumanoidVisualLayers, string> baseSprites,
|
||||
Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>? customBaseSprites,
|
||||
HumanoidComponent humanoid)
|
||||
{
|
||||
var newBaseLayers = new Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer>();
|
||||
|
||||
foreach (var (key, id) in baseSprites)
|
||||
{
|
||||
var sexMorph = humanoid.Sex switch
|
||||
{
|
||||
Sex.Male when HumanoidVisualLayersExtension.HasSexMorph(key) => $"{id}Male",
|
||||
Sex.Female when HumanoidVisualLayersExtension.HasSexMorph(key) => $"{id}Female",
|
||||
_ => id
|
||||
};
|
||||
|
||||
if (!_prototypeManager.TryIndex(sexMorph, out HumanoidSpeciesSpriteLayer? baseLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newBaseLayers.TryAdd(key, baseLayer))
|
||||
{
|
||||
newBaseLayers[key] = baseLayer;
|
||||
}
|
||||
}
|
||||
|
||||
if (customBaseSprites == null)
|
||||
{
|
||||
return IsDirty(newBaseLayers);
|
||||
}
|
||||
|
||||
foreach (var (key, info) in customBaseSprites)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(info.ID, out HumanoidSpeciesSpriteLayer? baseLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newBaseLayers.TryAdd(key, baseLayer))
|
||||
{
|
||||
newBaseLayers[key] = baseLayer;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDirty(Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer> newBaseLayers)
|
||||
{
|
||||
var dirty = false;
|
||||
if (humanoid.BaseLayers.Count != newBaseLayers.Count)
|
||||
{
|
||||
dirty = true;
|
||||
humanoid.BaseLayers = newBaseLayers;
|
||||
return dirty;
|
||||
}
|
||||
|
||||
foreach (var (key, info) in humanoid.BaseLayers)
|
||||
{
|
||||
if (!newBaseLayers.TryGetValue(key, out var newInfo))
|
||||
{
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (info.ID != newInfo.ID)
|
||||
{
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
humanoid.BaseLayers = newBaseLayers;
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
return IsDirty(newBaseLayers);
|
||||
}
|
||||
|
||||
private void ApplyBaseSprites(EntityUid uid,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
|
||||
{
|
||||
if (spriteInfo.BaseSprite != null && spriteComp.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
switch (spriteInfo.BaseSprite)
|
||||
{
|
||||
case SpriteSpecifier.Rsi rsi:
|
||||
spriteComp.LayerSetRSI(index, rsi.RsiPath);
|
||||
spriteComp.LayerSetState(index, rsi.RsiState);
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
spriteComp.LayerSetTexture(index, texture.TexturePath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public sealed partial class MarkingPicker : Control
|
|||
|
||||
private List<MarkingCategories> _markingCategories = Enum.GetValues<MarkingCategories>().ToList();
|
||||
|
||||
private string _currentSpecies = SharedHumanoidSystem.DefaultSpecies;
|
||||
private string _currentSpecies = SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
public Color CurrentSkinColor = Color.White;
|
||||
|
||||
private readonly HashSet<MarkingCategories> _ignoreCategories = new();
|
||||
|
|
@ -373,12 +373,13 @@ public sealed partial class MarkingPicker : Control
|
|||
colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" });
|
||||
colorContainer.AddChild(colorSelector);
|
||||
|
||||
var listing = _currentMarkings[_selectedMarkingCategory];
|
||||
var listing = _currentMarkings.Markings[_selectedMarkingCategory];
|
||||
|
||||
var color = listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i];
|
||||
var currentColor = new Color(
|
||||
listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].RByte,
|
||||
listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].GByte,
|
||||
listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].BByte
|
||||
color.RByte,
|
||||
color.GByte,
|
||||
color.BByte
|
||||
);
|
||||
colorSelector.Color = currentColor;
|
||||
_currentMarkingColors.Add(currentColor);
|
||||
|
|
@ -406,7 +407,7 @@ public sealed partial class MarkingPicker : Control
|
|||
|
||||
_selectedMarking.IconModulate = _currentMarkingColors[colorIndex];
|
||||
|
||||
var marking = new Marking(_currentMarkings[_selectedMarkingCategory][markingIndex]);
|
||||
var marking = new Marking(_currentMarkings.Markings[_selectedMarkingCategory][markingIndex]);
|
||||
marking.SetColor(colorIndex, _currentMarkingColors[colorIndex]);
|
||||
_currentMarkings.Replace(_selectedMarkingCategory, markingIndex, marking);
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ namespace Content.Client.Lobby.UI
|
|||
_viewBox.AddChild(viewWest);
|
||||
_viewBox.AddChild(viewEast);
|
||||
_summaryLabel.Text = selectedCharacter.Summary;
|
||||
EntitySystem.Get<HumanoidSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
|
||||
EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
|
||||
GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,10 +172,10 @@ namespace Content.Client.Preferences.UI
|
|||
}
|
||||
else
|
||||
{
|
||||
_previewDummy = entityManager.SpawnEntity(prototypeManager.Index<SpeciesPrototype>(SharedHumanoidSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
|
||||
_previewDummy = entityManager.SpawnEntity(prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
|
||||
}
|
||||
|
||||
EntitySystem.Get<HumanoidSystem>().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile);
|
||||
EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile);
|
||||
|
||||
if (humanoid != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -563,7 +563,7 @@ namespace Content.Client.Preferences.UI
|
|||
#endregion FlavorText
|
||||
|
||||
#region Dummy
|
||||
var species = Profile?.Species ?? SharedHumanoidSystem.DefaultSpecies;
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
|
||||
|
||||
if (_previewDummy != null)
|
||||
|
|
@ -703,7 +703,7 @@ namespace Content.Client.Preferences.UI
|
|||
|
||||
private void RebuildSpriteView()
|
||||
{
|
||||
var species = Profile?.Species ?? SharedHumanoidSystem.DefaultSpecies;
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
|
||||
|
||||
if (_previewDummy != null)
|
||||
|
|
@ -1051,7 +1051,7 @@ namespace Content.Client.Preferences.UI
|
|||
if (Profile is null)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<HumanoidSystem>().LoadProfile(_previewDummy!.Value, Profile);
|
||||
EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy!.Value, Profile);
|
||||
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,18 @@ namespace Content.Client.Rotation;
|
|||
|
||||
public sealed class RotationVisualizerSystem : VisualizerSystem<RotationVisualsComponent>
|
||||
{
|
||||
public void SetHorizontalAngle(EntityUid uid, Angle angle, RotationVisualsComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.HorizontalRotation.Equals(angle))
|
||||
return;
|
||||
|
||||
component.HorizontalRotation = angle;
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, RotationVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
base.OnAppearanceChange(uid, component, ref args);
|
||||
|
|
@ -20,10 +32,10 @@ public sealed class RotationVisualizerSystem : VisualizerSystem<RotationVisualsC
|
|||
switch (state)
|
||||
{
|
||||
case RotationState.Vertical:
|
||||
AnimateSpriteRotation(args.Sprite, component.VerticalRotation, component.AnimationTime);
|
||||
AnimateSpriteRotation(uid, args.Sprite, component.VerticalRotation, component.AnimationTime);
|
||||
break;
|
||||
case RotationState.Horizontal:
|
||||
AnimateSpriteRotation(args.Sprite, component.HorizontalRotation, component.AnimationTime);
|
||||
AnimateSpriteRotation(uid, args.Sprite, component.HorizontalRotation, component.AnimationTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,14 +43,14 @@ public sealed class RotationVisualizerSystem : VisualizerSystem<RotationVisualsC
|
|||
/// <summary>
|
||||
/// Rotates a sprite between two animated keyframes given a certain time.
|
||||
/// </summary>
|
||||
public void AnimateSpriteRotation(SpriteComponent spriteComp, Angle rotation, float animationTime)
|
||||
public void AnimateSpriteRotation(EntityUid uid, SpriteComponent spriteComp, Angle rotation, float animationTime)
|
||||
{
|
||||
if (spriteComp.Rotation.Equals(rotation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var animationComp = EnsureComp<AnimationPlayerComponent>(spriteComp.Owner);
|
||||
var animationComp = EnsureComp<AnimationPlayerComponent>(uid);
|
||||
const string animationKey = "rotate";
|
||||
// Stop the current rotate animation and then start a new one
|
||||
if (AnimationSystem.HasRunningAnimation(animationComp, animationKey))
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ namespace Content.Client.Rotation;
|
|||
[RegisterComponent]
|
||||
public sealed class RotationVisualsComponent : Component
|
||||
{
|
||||
public static readonly Angle DefaultRotation = Angle.FromDegrees(90);
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle VerticalRotation = 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle HorizontalRotation = Angle.FromDegrees(90);
|
||||
[ViewVariables(VVAccess.ReadWrite)] public Angle HorizontalRotation = DefaultRotation;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float AnimationTime = 0.125f;
|
||||
|
|
|
|||
|
|
@ -41,18 +41,23 @@ public sealed class RandomSpriteSystem : SharedRandomSpriteSystem
|
|||
|
||||
foreach (var layer in component.Selected)
|
||||
{
|
||||
object key;
|
||||
int index;
|
||||
if (_reflection.TryParseEnumReference(layer.Key, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
if (!sprite.LayerMapTryGet(@enum, out index, logError: true))
|
||||
return;
|
||||
}
|
||||
else
|
||||
else if (!sprite.LayerMapTryGet(layer.Key, out index))
|
||||
{
|
||||
key = layer.Key;
|
||||
if (layer.Key is not string strKey || !int.TryParse(strKey, out index))
|
||||
{
|
||||
Logger.Error($"Invalid key `{layer.Key}` for entity with random sprite {ToPrettyString(uid)}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sprite.LayerSetState(key, layer.Value.State);
|
||||
sprite.LayerSetColor(key, layer.Value.Color ?? Color.White);
|
||||
sprite.LayerSetState(index, layer.Value.State);
|
||||
sprite.LayerSetColor(index, layer.Value.Color ?? Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,11 @@ public sealed class CargoTest
|
|||
var ent = entManager.SpawnEntity(proto.Product, new MapCoordinates(Vector2.Zero, mapId));
|
||||
var price = pricing.GetPrice(ent);
|
||||
|
||||
Assert.That(price, Is.LessThan(proto.PointCost), $"Found arbitrage on {proto.ID} cargo product! Cost is {proto.PointCost} but sell is {price}!");
|
||||
Assert.That(price, Is.AtMost(proto.PointCost), $"Found arbitrage on {proto.ID} cargo product! Cost is {proto.PointCost} but sell is {price}!");
|
||||
entManager.DeleteEntity(ent);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
mapManager.DeleteMap(mapId);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -46,14 +46,14 @@ namespace Content.IntegrationTests.Tests
|
|||
}
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
await server.WaitRunTicks(15);
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var entityMetas = entityMan.EntityQuery<MetaDataComponent>(true).ToList();
|
||||
foreach (var meta in entityMetas)
|
||||
{
|
||||
if(!entityMan.Deleted(meta.Owner))
|
||||
if(!meta.EntityDeleted)
|
||||
entityMan.DeleteEntity(meta.Owner);
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ namespace Content.IntegrationTests.Tests
|
|||
{
|
||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, Destructive = true});
|
||||
var server = pairTracker.Pair.Server;
|
||||
|
||||
var map = await PoolManager.CreateTestMap(pairTracker);
|
||||
IEntityManager entityMan = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
|
|
@ -81,21 +81,18 @@ namespace Content.IntegrationTests.Tests
|
|||
.Where(p=>!p.Abstract)
|
||||
.Select(p => p.ID)
|
||||
.ToList();
|
||||
var mapId = mapManager.CreateMap();
|
||||
var grid = mapManager.CreateGrid(mapId);
|
||||
var coord = new EntityCoordinates(grid.Owner, 0, 0);
|
||||
foreach (var protoId in protoIds)
|
||||
{
|
||||
entityMan.SpawnEntity(protoId, coord);
|
||||
entityMan.SpawnEntity(protoId, map.GridCoords);
|
||||
}
|
||||
});
|
||||
await server.WaitRunTicks(5);
|
||||
await server.WaitRunTicks(15);
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var entityMetas = entityMan.EntityQuery<MetaDataComponent>(true).ToList();
|
||||
foreach (var meta in entityMetas)
|
||||
{
|
||||
if(!entityMan.Deleted(meta.Owner))
|
||||
if(!meta.EntityDeleted)
|
||||
entityMan.DeleteEntity(meta.Owner);
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +100,54 @@ namespace Content.IntegrationTests.Tests
|
|||
});
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="SpawnAndDeleteAllEntitiesInTheSameSpot"/> that also launches a client and dirties
|
||||
/// all components on every entity.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SpawnAndDirtyAllEntities()
|
||||
{
|
||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = false, Destructive = true });
|
||||
var server = pairTracker.Pair.Server;
|
||||
var map = await PoolManager.CreateTestMap(pairTracker);
|
||||
IEntityManager entityMan = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
entityMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
var protoIds = prototypeMan
|
||||
.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(p => !p.Abstract)
|
||||
.Select(p => p.ID)
|
||||
.ToList();
|
||||
foreach (var protoId in protoIds)
|
||||
{
|
||||
var ent = entityMan.SpawnEntity(protoId, map.GridCoords);
|
||||
foreach (var (netId, component) in entityMan.GetNetComponents(ent))
|
||||
{
|
||||
entityMan.Dirty(component);
|
||||
}
|
||||
}
|
||||
});
|
||||
await server.WaitRunTicks(15);
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var entityMetas = entityMan.EntityQuery<MetaDataComponent>(true).ToList();
|
||||
foreach (var meta in entityMetas)
|
||||
{
|
||||
if (!meta.EntityDeleted)
|
||||
entityMan.DeleteEntity(meta.Owner);
|
||||
}
|
||||
|
||||
Assert.That(entityMan.EntityCount, Is.Zero);
|
||||
});
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task AllComponentsOneToOneDeleteTest()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,41 +36,6 @@ public sealed class PrototypeSaveTest
|
|||
{
|
||||
"Singularity", // physics collision uses "AllMask" (-1). The flag serializer currently fails to save this because this features un-named bits.
|
||||
"constructionghost",
|
||||
|
||||
// TODO fix more prototypes
|
||||
// The rest of these prototypes (probably) shouldn't be getting ignored.
|
||||
// There should be an issue up tracking all of these prototypes, indicating that still need to get fixed.
|
||||
"HeadSkeleton",
|
||||
// The followjng are all fixture-less phsyics entities that set can-collide to false on init.
|
||||
"CarpRift",
|
||||
"GasMinerOxygen",
|
||||
"GasMinerNitrogen",
|
||||
"GasMinerCarbonDioxide",
|
||||
"GasMinerPlasma",
|
||||
"GasMinerTritium",
|
||||
"GasMinerWaterVapor",
|
||||
"GasMinerMiasma",
|
||||
"GasMinerNitrousOxide",
|
||||
"SignalSwitch",
|
||||
"SignalButton",
|
||||
"ApcNetSwitch",
|
||||
"SignalButtonExt1",
|
||||
"SignalButtonExt2",
|
||||
"SignalButtonExt3",
|
||||
"SignalButtonBridge",
|
||||
"SignalButtonWindows",
|
||||
"GrilleBroken",
|
||||
"BaseGeneratorWallmountFrame",
|
||||
"GeneratorWallmountBasic",
|
||||
"GeneratorWallmountAPU",
|
||||
"Lightning",
|
||||
"LightningRevenant",
|
||||
"ChargedLightning",
|
||||
"SuperchargedLightning",
|
||||
"HyperchargedLightning",
|
||||
"BaseSubstationWall",
|
||||
"SubstationWallBasic",
|
||||
"BaseSubstationWallFrame"
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public sealed class BodySystem : SharedBodySystem
|
|||
{
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ public sealed class BodySystem : SharedBodySystem
|
|||
return false;
|
||||
|
||||
if (part.Body is { } body &&
|
||||
TryComp<HumanoidComponent>(body, out var humanoid))
|
||||
TryComp<HumanoidAppearanceComponent>(body, out var humanoid))
|
||||
{
|
||||
var layer = part.ToHumanoidLayers();
|
||||
if (layer != null)
|
||||
|
|
@ -103,7 +103,7 @@ public sealed class BodySystem : SharedBodySystem
|
|||
if (!base.DropPart(partId, part))
|
||||
return false;
|
||||
|
||||
if (oldBody == null || !TryComp<HumanoidComponent>(oldBody, out var humanoid))
|
||||
if (oldBody == null || !TryComp<HumanoidAppearanceComponent>(oldBody, out var humanoid))
|
||||
return true;
|
||||
|
||||
var layer = part.ToHumanoidLayers();
|
||||
|
|
|
|||
173
Content.Server/Chat/Systems/ChatSystem.Emote.cs
Normal file
173
Content.Server/Chat/Systems/ChatSystem.Emote.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Chat.Systems;
|
||||
|
||||
// emotes using emote prototype
|
||||
public partial class ChatSystem
|
||||
{
|
||||
private readonly Dictionary<string, EmotePrototype> _wordEmoteDict = new();
|
||||
|
||||
private void InitializeEmotes()
|
||||
{
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypeReloadEmotes;
|
||||
CacheEmotes();
|
||||
}
|
||||
|
||||
private void ShutdownEmotes()
|
||||
{
|
||||
_prototypeManager.PrototypesReloaded -= OnPrototypeReloadEmotes;
|
||||
}
|
||||
|
||||
private void OnPrototypeReloadEmotes(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
CacheEmotes();
|
||||
}
|
||||
|
||||
private void CacheEmotes()
|
||||
{
|
||||
_wordEmoteDict.Clear();
|
||||
var emotes = _prototypeManager.EnumeratePrototypes<EmotePrototype>();
|
||||
foreach (var emote in emotes)
|
||||
{
|
||||
foreach (var word in emote.ChatTriggers)
|
||||
{
|
||||
var lowerWord = word.ToLower();
|
||||
if (_wordEmoteDict.ContainsKey(lowerWord))
|
||||
{
|
||||
var existingId = _wordEmoteDict[lowerWord].ID;
|
||||
var errMsg = $"Duplicate of emote word {lowerWord} in emotes {emote.ID} and {existingId}";
|
||||
Logger.Error(errMsg);
|
||||
continue;
|
||||
}
|
||||
|
||||
_wordEmoteDict.Add(lowerWord, emote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
|
||||
/// </summary>
|
||||
/// <param name="source">The entity that is speaking</param>
|
||||
/// <param name="emoteId">The id of emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
|
||||
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
|
||||
/// <param name="hideGlobalGhostChat">Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions)</param>
|
||||
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
|
||||
public void TryEmoteWithChat(EntityUid source, string emoteId, bool hideChat = false,
|
||||
bool hideGlobalGhostChat = false, string? nameOverride = null)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||
return;
|
||||
TryEmoteWithChat(source, proto, hideChat, hideGlobalGhostChat, nameOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
|
||||
/// </summary>
|
||||
/// <param name="source">The entity that is speaking</param>
|
||||
/// <param name="emote">The emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
|
||||
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
|
||||
/// <param name="hideGlobalGhostChat">Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions)</param>
|
||||
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
|
||||
public void TryEmoteWithChat(EntityUid source, EmotePrototype emote, bool hideChat = false,
|
||||
bool hideGlobalGhostChat = false, string? nameOverride = null)
|
||||
{
|
||||
// check if proto has valid message for chat
|
||||
if (emote.ChatMessages.Count != 0)
|
||||
{
|
||||
var action = _random.Pick(emote.ChatMessages);
|
||||
SendEntityEmote(source, action, hideChat, hideGlobalGhostChat, nameOverride, false);
|
||||
}
|
||||
|
||||
// do the rest of emote event logic here
|
||||
TryEmoteWithoutChat(source, emote);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||
/// </summary>
|
||||
public void TryEmoteWithoutChat(EntityUid uid, string emoteId)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||
return;
|
||||
TryEmoteWithoutChat(uid, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||
/// </summary>
|
||||
public void TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto)
|
||||
{
|
||||
if (!_actionBlocker.CanEmote(uid))
|
||||
return;
|
||||
|
||||
InvokeEmoteEvent(uid, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find and play relevant emote sound in emote sounds collection.
|
||||
/// </summary>
|
||||
/// <returns>True if emote sound was played.</returns>
|
||||
public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, EmotePrototype emote)
|
||||
{
|
||||
return TryPlayEmoteSound(uid, proto, emote.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find and play relevant emote sound in emote sounds collection.
|
||||
/// </summary>
|
||||
/// <returns>True if emote sound was played.</returns>
|
||||
public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, string emoteId)
|
||||
{
|
||||
if (proto == null)
|
||||
return false;
|
||||
|
||||
// try to get specific sound for this emote
|
||||
if (!proto.Sounds.TryGetValue(emoteId, out var sound))
|
||||
{
|
||||
// no specific sound - check fallback
|
||||
sound = proto.FallbackSound;
|
||||
if (sound == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
// if general params for all sounds set - use them
|
||||
var param = proto.GeneralParams ?? sound.Params;
|
||||
_audio.PlayPvs(sound, uid, param);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryEmoteChatInput(EntityUid uid, string textInput)
|
||||
{
|
||||
var actionLower = textInput.ToLower();
|
||||
if (!_wordEmoteDict.TryGetValue(actionLower, out var emote))
|
||||
return;
|
||||
|
||||
InvokeEmoteEvent(uid, emote);
|
||||
}
|
||||
|
||||
private void InvokeEmoteEvent(EntityUid uid, EmotePrototype proto)
|
||||
{
|
||||
var ev = new EmoteEvent(proto);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by chat system when entity made some emote.
|
||||
/// Use it to play sound, change sprite or something else.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct EmoteEvent
|
||||
{
|
||||
public bool Handled;
|
||||
public readonly EmotePrototype Emote;
|
||||
|
||||
public EmoteEvent(EmotePrototype emote)
|
||||
{
|
||||
Emote = emote;
|
||||
Handled = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -52,6 +52,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public const int VoiceRange = 10; // how far voice goes in world units
|
||||
public const int WhisperRange = 2; // how far whisper goes in world units
|
||||
|
|
@ -63,7 +64,9 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeRadio();
|
||||
InitializeEmotes();
|
||||
_configurationManager.OnValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged, true);
|
||||
_configurationManager.OnValueChanged(CCVars.DeadLoocEnabled, OnDeadLoocEnabledChanged, true);
|
||||
|
||||
|
|
@ -72,7 +75,9 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
ShutdownRadio();
|
||||
ShutdownEmotes();
|
||||
_configurationManager.UnsubValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged);
|
||||
}
|
||||
|
||||
|
|
@ -366,7 +371,8 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Whisper from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}.");
|
||||
}
|
||||
|
||||
private void SendEntityEmote(EntityUid source, string action, bool hideChat, bool hideGlobalGhostChat, string? nameOverride)
|
||||
private void SendEntityEmote(EntityUid source, string action, bool hideChat,
|
||||
bool hideGlobalGhostChat, string? nameOverride, bool checkEmote = true)
|
||||
{
|
||||
if (!_actionBlocker.CanEmote(source)) return;
|
||||
|
||||
|
|
@ -378,6 +384,8 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
("entityName", name),
|
||||
("message", FormattedMessage.EscapeText(action)));
|
||||
|
||||
if (checkEmote)
|
||||
TryEmoteChatInput(source, action);
|
||||
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, hideChat, hideGlobalGhostChat);
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user}: {action}");
|
||||
}
|
||||
|
|
|
|||
33
Content.Server/Chemistry/ReagentEffects/Emote.cs
Normal file
33
Content.Server/Chemistry/ReagentEffects/Emote.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to force someone to emote (scream, laugh, etc).
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class Emote : ReagentEffect
|
||||
{
|
||||
[DataField("emote", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
public string? EmoteId;
|
||||
|
||||
[DataField("showInChat")]
|
||||
public bool ShowInChat;
|
||||
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
if (EmoteId == null)
|
||||
return;
|
||||
|
||||
var chatSys = args.EntityManager.System<ChatSystem>();
|
||||
if (ShowInChat)
|
||||
chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, hideGlobalGhostChat: true);
|
||||
else
|
||||
chatSys.TryEmoteWithoutChat(args.SolutionEntity, EmoteId);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
using Content.Server.Speech;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects;
|
||||
|
||||
/// <summary>
|
||||
/// Forces someone to scream their lungs out.
|
||||
/// </summary>
|
||||
public sealed class Scream : ReagentEffect
|
||||
{
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
EntitySystem.Get<VocalSystem>().TryScream(args.SolutionEntity);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Server.Cloning.Components;
|
||||
using Content.Server.MachineLinking.Components;
|
||||
|
|
@ -13,6 +15,7 @@ using Robust.Server.GameObjects;
|
|||
using Robust.Server.Player;
|
||||
using Content.Shared.Cloning.CloningConsole;
|
||||
using Content.Shared.Cloning;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.MachineLinking.Events;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mobs.Components;
|
||||
|
|
@ -24,6 +27,7 @@ namespace Content.Server.Cloning
|
|||
public sealed class CloningConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly CloningSystem _cloningSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
|
@ -168,7 +172,8 @@ namespace Content.Server.Cloning
|
|||
if (mind == null || mind.UserId.HasValue == false || mind.Session == null)
|
||||
return;
|
||||
|
||||
_cloningSystem.TryCloning(cloningPodUid, body.Value, mind, cloningPod, scannerComp.CloningFailChanceMultiplier);
|
||||
if (_cloningSystem.TryCloning(cloningPodUid, body.Value, mind, cloningPod, scannerComp.CloningFailChanceMultiplier))
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(uid)} successfully cloned {ToPrettyString(body.Value)}.");
|
||||
}
|
||||
|
||||
public void RecheckConnections(EntityUid console, EntityUid? cloningPod, EntityUid? scanner, CloningConsoleComponent? consoleComp = null)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using Content.Server.Stack;
|
|||
using Content.Server.Jobs;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Zombies;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Containers;
|
||||
|
|
@ -30,6 +31,7 @@ using Robust.Shared.Random;
|
|||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
|
||||
namespace Content.Server.Cloning
|
||||
{
|
||||
|
|
@ -40,7 +42,7 @@ namespace Content.Server.Cloning
|
|||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = null!;
|
||||
[Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!;
|
||||
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly ContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
|
||||
|
|
@ -169,7 +171,7 @@ namespace Content.Server.Cloning
|
|||
if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client))
|
||||
return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad.
|
||||
|
||||
if (!TryComp<HumanoidComponent>(bodyToClone, out var humanoid))
|
||||
if (!TryComp<HumanoidAppearanceComponent>(bodyToClone, out var humanoid))
|
||||
return false; // whatever body was to be cloned, was not a humanoid
|
||||
|
||||
if (!_prototype.TryIndex<SpeciesPrototype>(humanoid.Species, out var speciesPrototype))
|
||||
|
|
@ -220,7 +222,11 @@ namespace Content.Server.Cloning
|
|||
var mob = Spawn(speciesPrototype.Prototype, Transform(clonePod.Owner).MapPosition);
|
||||
_humanoidSystem.CloneAppearance(bodyToClone, mob);
|
||||
|
||||
MetaData(mob).EntityName = MetaData(bodyToClone).EntityName;
|
||||
var ev = new CloningEvent(bodyToClone, mob);
|
||||
RaiseLocalEvent(bodyToClone, ref ev);
|
||||
|
||||
if (!ev.NameHandled)
|
||||
MetaData(mob).EntityName = MetaData(bodyToClone).EntityName;
|
||||
|
||||
var cloneMindReturn = EntityManager.AddComponent<BeingClonedComponent>(mob);
|
||||
cloneMindReturn.Mind = mind;
|
||||
|
|
@ -323,4 +329,21 @@ namespace Content.Server.Cloning
|
|||
ClonesWaitingForMind.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised after a new mob got spawned when cloning a humanoid
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct CloningEvent
|
||||
{
|
||||
public bool NameHandled = false;
|
||||
|
||||
public readonly EntityUid Source;
|
||||
public readonly EntityUid Target;
|
||||
|
||||
public CloningEvent(EntityUid source, EntityUid target) {
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,7 @@
|
|||
using Content.Server.Humanoid;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Tag;
|
||||
|
||||
namespace Content.Server.Clothing;
|
||||
|
||||
public sealed class ServerClothingSystem : ClothingSystem
|
||||
{
|
||||
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
|
||||
protected override void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args)
|
||||
{
|
||||
base.OnGotEquipped(uid, component, args);
|
||||
// why the fuck is humanoid visuals server-only???
|
||||
|
||||
if (args.Slot == "head"
|
||||
&& _tagSystem.HasTag(args.Equipment, "HidesHair"))
|
||||
{
|
||||
_humanoidSystem.ToggleHiddenLayer(args.Equipee, HumanoidVisualLayers.Hair);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args)
|
||||
{
|
||||
base.OnGotUnequipped(uid, component, args);
|
||||
|
||||
// why the fuck is humanoid visuals server-only???
|
||||
|
||||
if (args.Slot == "head"
|
||||
&& _tagSystem.HasTag(args.Equipment, "HidesHair"))
|
||||
{
|
||||
_humanoidSystem.ToggleHiddenLayer(args.Equipee, HumanoidVisualLayers.Hair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ namespace Content.Server.Decals
|
|||
return;
|
||||
|
||||
// Should this be a full component state or a delta-state?
|
||||
if (args.FromTick <= component.CreationTick && args.FromTick <= component.ForceTick)
|
||||
if (args.FromTick <= component.CreationTick || args.FromTick <= component.ForceTick)
|
||||
{
|
||||
args.State = new DecalGridState(component.ChunkCollection.ChunkCollection);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.DeviceNetwork.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class StationLimitedNetworkComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The station id the device is limited to.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? StationId;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the entity is allowed to receive packets from entities that are not tied to any station
|
||||
/// </summary>
|
||||
[DataField("allowNonStationPackets")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AllowNonStationPackets = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ public sealed class DeviceNet
|
|||
/// </summary>
|
||||
public bool Remove(DeviceNetworkComponent device)
|
||||
{
|
||||
if (device.Address == null || Devices.Remove(device.Address))
|
||||
if (device.Address == null || !Devices.Remove(device.Address))
|
||||
return false;
|
||||
|
||||
if (device.ReceiveFrequency is not uint freq)
|
||||
|
|
|
|||
|
|
@ -48,18 +48,22 @@ namespace Content.Server.DeviceNetwork.Systems
|
|||
/// <param name="address">The address of the entity that the packet gets sent to. If null, the message is broadcast to all devices on that frequency (except the sender)</param>
|
||||
/// <param name="frequency">The frequency to send on</param>
|
||||
/// <param name="data">The data to be sent</param>
|
||||
public void QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, DeviceNetworkComponent? device = null)
|
||||
/// <returns>Returns true when the packet was successfully enqueued.</returns>
|
||||
public bool QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, DeviceNetworkComponent? device = null)
|
||||
{
|
||||
if (!Resolve(uid, ref device, false))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (device.Address == string.Empty)
|
||||
return;
|
||||
return false;
|
||||
|
||||
frequency ??= device.TransmitFrequency;
|
||||
|
||||
if (frequency != null)
|
||||
_packets.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data));
|
||||
if (frequency == null)
|
||||
return false;
|
||||
|
||||
_packets.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, DeviceNetworkComponent device, ExaminedEvent args)
|
||||
|
|
@ -137,6 +141,32 @@ namespace Content.Server.DeviceNetwork.Systems
|
|||
return GetNetwork(device.DeviceNetId).Remove(device);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a device is already connected to its network
|
||||
/// </summary>
|
||||
/// <returns>True if the device was found in the network with its corresponding network id</returns>
|
||||
public bool IsDeviceConnected(EntityUid uid, DeviceNetworkComponent? device)
|
||||
{
|
||||
if (!Resolve(uid, ref device, false))
|
||||
return false;
|
||||
|
||||
if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet))
|
||||
return false;
|
||||
|
||||
return deviceNet.Devices.ContainsValue(device);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an address exists in the network with the given netId
|
||||
/// </summary>
|
||||
public bool IsAddressPresent(int netId, string? address)
|
||||
{
|
||||
if (address == null || !_networks.TryGetValue(netId, out var network))
|
||||
return false;
|
||||
|
||||
return network.Devices.ContainsKey(address);
|
||||
}
|
||||
|
||||
public void SetReceiveFrequency(EntityUid uid, uint? frequency, DeviceNetworkComponent? device = null)
|
||||
{
|
||||
if (!Resolve(uid, ref device, false))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.DeviceNetwork.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// This system requires the StationLimitedNetworkComponent to be on the the sending entity as well as the receiving entity
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class StationLimitedNetworkSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<StationLimitedNetworkComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<StationLimitedNetworkComponent, BeforePacketSentEvent>(OnBeforePacketSent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the station id the device is limited to.
|
||||
/// </summary>
|
||||
public void SetStation(EntityUid uid, EntityUid? stationId, StationLimitedNetworkComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.StationId = stationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the station id to the one the entity is on when the station limited component is added
|
||||
/// </summary>
|
||||
private void OnMapInit(EntityUid uid, StationLimitedNetworkComponent networkComponent, MapInitEvent args)
|
||||
{
|
||||
networkComponent.StationId = _stationSystem.GetOwningStation(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if both devices are limited to the same station
|
||||
/// </summary>
|
||||
private void OnBeforePacketSent(EntityUid uid, StationLimitedNetworkComponent component, BeforePacketSentEvent args)
|
||||
{
|
||||
if (!CheckStationId(args.Sender, component.AllowNonStationPackets, component.StationId))
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the station IDs of the sending and receiving network components.
|
||||
/// Returns false if either of them doesn't have a station ID or if their station ID isn't equal.
|
||||
/// Returns true even when the sending entity isn't tied to a station if `allowNonStationPackets` is set to true.
|
||||
/// </summary>
|
||||
private bool CheckStationId(EntityUid senderUid, bool allowNonStationPackets, EntityUid? receiverStationId, StationLimitedNetworkComponent? sender = null)
|
||||
{
|
||||
if (!receiverStationId.HasValue)
|
||||
return false;
|
||||
|
||||
if (!Resolve(senderUid, ref sender, false))
|
||||
return allowNonStationPackets;
|
||||
|
||||
return sender.StationId == receiverStationId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -303,7 +303,7 @@ namespace Content.Server.Dragon
|
|||
var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
|
||||
|
||||
//Humanoid devours allow dragon to get eggs, corpses included
|
||||
if (!EntityManager.HasComponent<HumanoidComponent>(args.Target))
|
||||
if (!EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
ichorInjection.ScaleSolution(0.5f);
|
||||
}
|
||||
|
|
|
|||
24
Content.Server/Emoting/Components/BodyEmotesComponent.cs
Normal file
24
Content.Server/Emoting/Components/BodyEmotesComponent.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Server.Emoting.Systems;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Emoting.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component required for entities to be able to do body emotions (clap, flip, etc).
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(BodyEmotesSystem))]
|
||||
public sealed class BodyEmotesComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Emote sounds prototype id for body emotes.
|
||||
/// </summary>
|
||||
[DataField("soundsId", customTypeSerializer: typeof(PrototypeIdSerializer<EmoteSoundsPrototype>))]
|
||||
public string? SoundsId;
|
||||
|
||||
/// <summary>
|
||||
/// Loaded emote sounds prototype used for body emotes.
|
||||
/// </summary>
|
||||
public EmoteSoundsPrototype? Sounds;
|
||||
}
|
||||
48
Content.Server/Emoting/Systems/BodyEmotesSystem.cs
Normal file
48
Content.Server/Emoting/Systems/BodyEmotesSystem.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Emoting.Components;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Emoting.Systems;
|
||||
|
||||
public sealed class BodyEmotesSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BodyEmotesComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<BodyEmotesComponent, EmoteEvent>(OnEmote);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, BodyEmotesComponent component, ComponentStartup args)
|
||||
{
|
||||
if (component.SoundsId == null)
|
||||
return;
|
||||
_proto.TryIndex(component.SoundsId, out component.Sounds);
|
||||
}
|
||||
|
||||
private void OnEmote(EntityUid uid, BodyEmotesComponent component, ref EmoteEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var cat = args.Emote.Category;
|
||||
if (cat.HasFlag(EmoteCategory.Hands))
|
||||
{
|
||||
args.Handled = TryEmoteHands(uid, args.Emote, component);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryEmoteHands(EntityUid uid, EmotePrototype emote, BodyEmotesComponent component)
|
||||
{
|
||||
// check that user actually has hands to do emote sound
|
||||
if (!TryComp(uid, out HandsComponent? hands) || hands.Count <= 0)
|
||||
return false;
|
||||
|
||||
return _chat.TryPlayEmoteSound(uid, component.Sounds, emote);
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +362,8 @@ namespace Content.Server.GameTicking
|
|||
}
|
||||
|
||||
// AAAAAAAAAAAAA
|
||||
_sawmill.Error("Found no observer spawn points!");
|
||||
// This should be an error, if it didn't cause tests to start erroring when they delete a player.
|
||||
_sawmill.Warning("Found no observer spawn points!");
|
||||
return EntityCoordinates.Invalid;
|
||||
}
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||
private void CheckRoundEnd(EntityUid target)
|
||||
{
|
||||
//we only care about players, not monkeys and such.
|
||||
if (!HasComp<HumanoidComponent>(target))
|
||||
if (!HasComp<HumanoidAppearanceComponent>(target))
|
||||
return;
|
||||
|
||||
var percent = GetInfectedPercentage(out var num);
|
||||
|
|
@ -196,7 +196,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||
|
||||
private float GetInfectedPercentage(out List<EntityUid> livingHumans)
|
||||
{
|
||||
var allPlayers = EntityQuery<HumanoidComponent, MobStateComponent>(true);
|
||||
var allPlayers = EntityQuery<HumanoidAppearanceComponent, MobStateComponent>(true);
|
||||
var allZombers = GetEntityQuery<ZombieComponent>();
|
||||
|
||||
var totalPlayers = new List<EntityUid>();
|
||||
|
|
|
|||
|
|
@ -1,50 +1,32 @@
|
|||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Humanoid;
|
||||
|
||||
public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
||||
public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
{
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<HumanoidComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<HumanoidComponent, HumanoidMarkingModifierMarkingSetMessage>(OnMarkingsSet);
|
||||
SubscribeLocalEvent<HumanoidComponent, HumanoidMarkingModifierBaseLayersSetMessage>(OnBaseLayersSet);
|
||||
SubscribeLocalEvent<HumanoidComponent, GetVerbsEvent<Verb>>(OnVerbsRequest);
|
||||
SubscribeLocalEvent<HumanoidComponent, ExaminedEvent>(OnExamined);
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, HumanoidMarkingModifierMarkingSetMessage>(OnMarkingsSet);
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, HumanoidMarkingModifierBaseLayersSetMessage>(OnBaseLayersSet);
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, GetVerbsEvent<Verb>>(OnVerbsRequest);
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void Synchronize(EntityUid uid, HumanoidComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetAppearance(uid,
|
||||
component.Species,
|
||||
component.CustomBaseLayers,
|
||||
component.SkinColor,
|
||||
component.Sex,
|
||||
component.AllHiddenLayers.ToList(),
|
||||
component.CurrentMarkings.GetForwardEnumerator().ToList());
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, HumanoidComponent humanoid, ComponentInit args)
|
||||
private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, ComponentInit args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(humanoid.Species))
|
||||
{
|
||||
|
|
@ -67,7 +49,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
LoadProfile(uid, startingSet.Profile, humanoid);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, HumanoidComponent component, ExaminedEvent args)
|
||||
private void OnExamined(EntityUid uid, HumanoidAppearanceComponent component, ExaminedEvent args)
|
||||
{
|
||||
var identity = Identity.Entity(component.Owner, EntityManager);
|
||||
var species = GetSpeciesRepresentation(component.Species).ToLower();
|
||||
|
|
@ -82,7 +64,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="uid">The mob's entity UID.</param>
|
||||
/// <param name="profile">The character profile to load.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidComponent? humanoid = null)
|
||||
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
|
|
@ -90,12 +72,12 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
}
|
||||
|
||||
SetSpecies(uid, profile.Species, false, humanoid);
|
||||
humanoid.Sex = profile.Sex;
|
||||
SetSex(uid, profile.Sex, false, humanoid);
|
||||
humanoid.EyeColor = profile.Appearance.EyeColor;
|
||||
|
||||
SetSkinColor(uid, profile.Appearance.SkinColor, false);
|
||||
SetBaseLayerColor(uid, HumanoidVisualLayers.Eyes, profile.Appearance.EyeColor, false);
|
||||
|
||||
humanoid.CurrentMarkings.Clear();
|
||||
humanoid.MarkingSet.Clear();
|
||||
|
||||
// Hair/facial hair - this may eventually be deprecated.
|
||||
|
||||
|
|
@ -118,7 +100,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
|
||||
humanoid.Age = profile.Age;
|
||||
|
||||
Synchronize(uid);
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
// this was done enough times that it only made sense to do it here
|
||||
|
|
@ -130,8 +112,8 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="target">Target entity to apply the source entity's appearance to.</param>
|
||||
/// <param name="sourceHumanoid">Source entity's humanoid component.</param>
|
||||
/// <param name="targetHumanoid">Target entity's humanoid component.</param>
|
||||
public void CloneAppearance(EntityUid source, EntityUid target, HumanoidComponent? sourceHumanoid = null,
|
||||
HumanoidComponent? targetHumanoid = null)
|
||||
public void CloneAppearance(EntityUid source, EntityUid target, HumanoidAppearanceComponent? sourceHumanoid = null,
|
||||
HumanoidAppearanceComponent? targetHumanoid = null)
|
||||
{
|
||||
if (!Resolve(source, ref sourceHumanoid) || !Resolve(target, ref targetHumanoid))
|
||||
{
|
||||
|
|
@ -140,9 +122,9 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
|
||||
targetHumanoid.Species = sourceHumanoid.Species;
|
||||
targetHumanoid.SkinColor = sourceHumanoid.SkinColor;
|
||||
targetHumanoid.Sex = sourceHumanoid.Sex;
|
||||
SetSex(target, sourceHumanoid.Sex, false, targetHumanoid);
|
||||
targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers);
|
||||
targetHumanoid.CurrentMarkings = new(sourceHumanoid.CurrentMarkings);
|
||||
targetHumanoid.MarkingSet = new(sourceHumanoid.MarkingSet);
|
||||
SetTTSVoice(target, sourceHumanoid.Voice, targetHumanoid); // Corvax-TTS
|
||||
|
||||
targetHumanoid.Gender = sourceHumanoid.Gender;
|
||||
|
|
@ -151,180 +133,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
grammar.Gender = sourceHumanoid.Gender;
|
||||
}
|
||||
|
||||
Synchronize(target, targetHumanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a humanoid mob's species. This will change their base sprites, as well as their current
|
||||
/// set of markings to fit against the mob's new species.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="species">The species to set the mob to. Will return if the species prototype was invalid.</param>
|
||||
/// <param name="sync">Whether to immediately synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetSpecies(EntityUid uid, string species, bool sync = true, HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid) || !_prototypeManager.TryIndex<SpeciesPrototype>(species, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.Species = species;
|
||||
humanoid.CurrentMarkings.FilterSpecies(species, _markingManager);
|
||||
var oldMarkings = humanoid.CurrentMarkings.GetForwardEnumerator().ToList();
|
||||
humanoid.CurrentMarkings = new(oldMarkings, prototype.MarkingPoints, _markingManager, _prototypeManager);
|
||||
|
||||
if (sync)
|
||||
{
|
||||
Synchronize(uid, humanoid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the skin color of this humanoid mob. Will only affect base layers that are not custom,
|
||||
/// custom base layers should use <see cref="SetBaseLayerColor"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="skinColor">Skin color to set on the humanoid mob.</param>
|
||||
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
if (sync)
|
||||
Synchronize(uid, humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the base layer ID of this humanoid mob. A humanoid mob's 'base layer' is
|
||||
/// the skin sprite that is applied to the mob's sprite upon appearance refresh.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="layer">The layer to target on this humanoid mob.</param>
|
||||
/// <param name="id">The ID of the sprite to use. See <see cref="HumanoidSpeciesSpriteLayer"/>.</param>
|
||||
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetBaseLayerId(EntityUid uid, HumanoidVisualLayers layer, string id, bool sync = true,
|
||||
HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid)
|
||||
|| !_prototypeManager.HasIndex<HumanoidSpeciesSpriteLayer>(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (humanoid.CustomBaseLayers.TryGetValue(layer, out var info))
|
||||
{
|
||||
humanoid.CustomBaseLayers[layer] = new(id, info.Color);
|
||||
}
|
||||
else
|
||||
{
|
||||
var layerInfo = new CustomBaseLayerInfo(id, humanoid.SkinColor);
|
||||
humanoid.CustomBaseLayers.Add(layer, layerInfo);
|
||||
}
|
||||
|
||||
if (sync)
|
||||
Synchronize(uid, humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of this humanoid mob's base layer. See <see cref="SetBaseLayerId"/> for a
|
||||
/// description of how base layers work.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="layer">The layer to target on this humanoid mob.</param>
|
||||
/// <param name="color">The color to set this base layer to.</param>
|
||||
public void SetBaseLayerColor(EntityUid uid, HumanoidVisualLayers layer, Color color, bool sync = true, HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (humanoid.CustomBaseLayers.TryGetValue(layer, out var info))
|
||||
{
|
||||
humanoid.CustomBaseLayers[layer] = new(info.ID, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
var layerInfo = new CustomBaseLayerInfo(string.Empty, color);
|
||||
humanoid.CustomBaseLayers.Add(layer, layerInfo);
|
||||
}
|
||||
|
||||
if (sync)
|
||||
Synchronize(uid, humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles a humanoid's sprite layer visibility.
|
||||
/// </summary>
|
||||
/// <param name="uid">Humanoid mob's UID</param>
|
||||
/// <param name="layer">Layer to toggle visibility for</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void ToggleHiddenLayer(EntityUid uid, HumanoidVisualLayers layer, HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (humanoid.HiddenLayers.Contains(layer))
|
||||
{
|
||||
humanoid.HiddenLayers.Remove(layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
humanoid.HiddenLayers.Add(layer);
|
||||
}
|
||||
|
||||
Synchronize(uid, humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the visibility for multiple layers at once on a humanoid's sprite.
|
||||
/// </summary>
|
||||
/// <param name="uid">Humanoid mob's UID</param>
|
||||
/// <param name="layers">An enumerable of all sprite layers that are going to have their visibility set</param>
|
||||
/// <param name="visible">The visibility state of the layers given</param>
|
||||
/// <param name="permanent">If this is a permanent change, or temporary. Permanent layers are stored in their own hash set.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetLayersVisibility(EntityUid uid, IEnumerable<HumanoidVisualLayers> layers, bool visible, bool permanent = false,
|
||||
HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
if (permanent && humanoid.PermanentlyHidden.Contains(layer))
|
||||
{
|
||||
humanoid.PermanentlyHidden.Remove(layer);
|
||||
}
|
||||
|
||||
humanoid.HiddenLayers.Remove(layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (permanent)
|
||||
{
|
||||
humanoid.PermanentlyHidden.Add(layer);
|
||||
}
|
||||
|
||||
humanoid.HiddenLayers.Add(layer);
|
||||
}
|
||||
}
|
||||
|
||||
Synchronize(uid, humanoid);
|
||||
Dirty(targetHumanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -336,7 +145,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="sync">Whether to immediately sync this marking or not</param>
|
||||
/// <param name="forced">If this marking was forced (ignores marking points)</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidComponent? humanoid = null)
|
||||
public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid)
|
||||
|| !_markingManager.Markings.TryGetValue(marking, out var prototype))
|
||||
|
|
@ -354,10 +163,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
}
|
||||
}
|
||||
|
||||
humanoid.CurrentMarkings.AddBack(prototype.MarkingCategory, markingObject);
|
||||
humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject);
|
||||
|
||||
if (sync)
|
||||
Synchronize(uid, humanoid);
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -369,7 +178,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="sync">Whether to immediately sync this marking or not</param>
|
||||
/// <param name="forced">If this marking was forced (ignores marking points)</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void AddMarking(EntityUid uid, string marking, IReadOnlyList<Color> colors, bool sync = true, bool forced = false, HumanoidComponent? humanoid = null)
|
||||
public void AddMarking(EntityUid uid, string marking, IReadOnlyList<Color> colors, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid)
|
||||
|| !_markingManager.Markings.TryGetValue(marking, out var prototype))
|
||||
|
|
@ -379,10 +188,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
|
||||
var markingObject = new Marking(marking, colors);
|
||||
markingObject.Forced = forced;
|
||||
humanoid.CurrentMarkings.AddBack(prototype.MarkingCategory, markingObject);
|
||||
humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject);
|
||||
|
||||
if (sync)
|
||||
Synchronize(uid, humanoid);
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -392,7 +201,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="marking">The marking to try and remove.</param>
|
||||
/// <param name="sync">Whether to immediately sync this to the humanoid</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void RemoveMarking(EntityUid uid, string marking, bool sync = true, HumanoidComponent? humanoid = null)
|
||||
public void RemoveMarking(EntityUid uid, string marking, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid)
|
||||
|| !_markingManager.Markings.TryGetValue(marking, out var prototype))
|
||||
|
|
@ -400,10 +209,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
return;
|
||||
}
|
||||
|
||||
humanoid.CurrentMarkings.Remove(prototype.MarkingCategory, marking);
|
||||
humanoid.MarkingSet.Remove(prototype.MarkingCategory, marking);
|
||||
|
||||
if (sync)
|
||||
Synchronize(uid, humanoid);
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -413,19 +222,18 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="category">Category of the marking</param>
|
||||
/// <param name="index">Index of the marking</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void RemoveMarking(EntityUid uid, MarkingCategories category, int index, HumanoidComponent? humanoid = null)
|
||||
public void RemoveMarking(EntityUid uid, MarkingCategories category, int index, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (index < 0
|
||||
|| !Resolve(uid, ref humanoid)
|
||||
|| !humanoid.CurrentMarkings.TryGetCategory(category, out var markings)
|
||||
|| !humanoid.MarkingSet.TryGetCategory(category, out var markings)
|
||||
|| index >= markings.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.CurrentMarkings.Remove(category, index);
|
||||
|
||||
Synchronize(uid, humanoid);
|
||||
humanoid.MarkingSet.Remove(category, index);
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -436,12 +244,12 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="index">Index of the marking</param>
|
||||
/// <param name="markingId">The marking ID to use</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetMarkingId(EntityUid uid, MarkingCategories category, int index, string markingId, HumanoidComponent? humanoid = null)
|
||||
public void SetMarkingId(EntityUid uid, MarkingCategories category, int index, string markingId, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (index < 0
|
||||
|| !_markingManager.MarkingsByCategory(category).TryGetValue(markingId, out var markingPrototype)
|
||||
|| !Resolve(uid, ref humanoid)
|
||||
|| !humanoid.CurrentMarkings.TryGetCategory(category, out var markings)
|
||||
|| !humanoid.MarkingSet.TryGetCategory(category, out var markings)
|
||||
|| index >= markings.Count)
|
||||
{
|
||||
return;
|
||||
|
|
@ -453,9 +261,8 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
marking.SetColor(i, markings[index].MarkingColors[i]);
|
||||
}
|
||||
|
||||
humanoid.CurrentMarkings.Replace(category, index, marking);
|
||||
|
||||
Synchronize(uid, humanoid);
|
||||
humanoid.MarkingSet.Replace(category, index, marking);
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -467,11 +274,11 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
/// <param name="colors">The marking colors to use</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetMarkingColor(EntityUid uid, MarkingCategories category, int index, List<Color> colors,
|
||||
HumanoidComponent? humanoid = null)
|
||||
HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (index < 0
|
||||
|| !Resolve(uid, ref humanoid)
|
||||
|| !humanoid.CurrentMarkings.TryGetCategory(category, out var markings)
|
||||
|| !humanoid.MarkingSet.TryGetCategory(category, out var markings)
|
||||
|| index >= markings.Count)
|
||||
{
|
||||
return;
|
||||
|
|
@ -482,7 +289,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
markings[index].SetColor(i, colors[i]);
|
||||
}
|
||||
|
||||
Synchronize(uid, humanoid);
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -523,13 +330,13 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
|
|||
return Loc.GetString("identity-age-old");
|
||||
}
|
||||
|
||||
private void EnsureDefaultMarkings(EntityUid uid, HumanoidComponent? humanoid)
|
||||
private void EnsureDefaultMarkings(EntityUid uid, HumanoidAppearanceComponent? humanoid)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.CurrentMarkings.EnsureDefault(humanoid.SkinColor, _markingManager);
|
||||
humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, _markingManager);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@ using Robust.Server.Player;
|
|||
|
||||
namespace Content.Server.Humanoid;
|
||||
|
||||
public sealed partial class HumanoidSystem
|
||||
public sealed partial class HumanoidAppearanceSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
private void OnVerbsRequest(EntityUid uid, HumanoidComponent component, GetVerbsEvent<Verb> args)
|
||||
private void OnVerbsRequest(EntityUid uid, HumanoidAppearanceComponent component, GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(args.User, out var actor))
|
||||
{
|
||||
|
|
@ -35,12 +35,12 @@ public sealed partial class HumanoidSystem
|
|||
_uiSystem.TrySetUiState(
|
||||
uid,
|
||||
HumanoidMarkingModifierKey.Key,
|
||||
new HumanoidMarkingModifierState(component.CurrentMarkings, component.Species, component.SkinColor, component.CustomBaseLayers));
|
||||
new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnBaseLayersSet(EntityUid uid, HumanoidComponent component,
|
||||
private void OnBaseLayersSet(EntityUid uid, HumanoidAppearanceComponent component,
|
||||
HumanoidMarkingModifierBaseLayersSetMessage message)
|
||||
{
|
||||
if (message.Session is not IPlayerSession player
|
||||
|
|
@ -55,24 +55,21 @@ public sealed partial class HumanoidSystem
|
|||
}
|
||||
else
|
||||
{
|
||||
if (!component.CustomBaseLayers.TryAdd(message.Layer, message.Info))
|
||||
{
|
||||
component.CustomBaseLayers[message.Layer] = message.Info;
|
||||
}
|
||||
component.CustomBaseLayers[message.Layer] = message.Info.Value;
|
||||
}
|
||||
|
||||
Synchronize(uid, component);
|
||||
Dirty(component);
|
||||
|
||||
if (message.ResendState)
|
||||
{
|
||||
_uiSystem.TrySetUiState(
|
||||
uid,
|
||||
HumanoidMarkingModifierKey.Key,
|
||||
new HumanoidMarkingModifierState(component.CurrentMarkings, component.Species, component.SkinColor, component.CustomBaseLayers));
|
||||
new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMarkingsSet(EntityUid uid, HumanoidComponent component,
|
||||
private void OnMarkingsSet(EntityUid uid, HumanoidAppearanceComponent component,
|
||||
HumanoidMarkingModifierMarkingSetMessage message)
|
||||
{
|
||||
if (message.Session is not IPlayerSession player
|
||||
|
|
@ -81,15 +78,15 @@ public sealed partial class HumanoidSystem
|
|||
return;
|
||||
}
|
||||
|
||||
component.CurrentMarkings = message.MarkingSet;
|
||||
Synchronize(uid, component);
|
||||
component.MarkingSet = message.MarkingSet;
|
||||
Dirty(component);
|
||||
|
||||
if (message.ResendState)
|
||||
{
|
||||
_uiSystem.TrySetUiState(
|
||||
uid,
|
||||
HumanoidMarkingModifierKey.Key,
|
||||
new HumanoidMarkingModifierState(component.CurrentMarkings, component.Species, component.SkinColor, component.CustomBaseLayers));
|
||||
new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Content.Server.CharacterAppearance.Components;
|
||||
using Content.Server.CharacterAppearance.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Preferences;
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ namespace Content.Server.Humanoid.Systems;
|
|||
|
||||
public sealed class RandomHumanoidAppearanceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly HumanoidSystem _humanoid = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -18,7 +18,7 @@ public sealed class RandomHumanoidAppearanceSystem : EntitySystem
|
|||
private void OnMapInit(EntityUid uid, RandomHumanoidAppearanceComponent component, MapInitEvent args)
|
||||
{
|
||||
// If we have an initial profile/base layer set, do not randomize this humanoid.
|
||||
if (!TryComp(uid, out HumanoidComponent? humanoid) || !string.IsNullOrEmpty(humanoid.Initial))
|
||||
if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid) || !string.IsNullOrEmpty(humanoid.Initial))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public sealed class RandomHumanoidSystem : EntitySystem
|
|||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
|
||||
[Dependency] private readonly HumanoidSystem _humanoid = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ public class IdentitySystem : SharedIdentitySystem
|
|||
/// </summary>
|
||||
private IdentityRepresentation GetIdentityRepresentation(EntityUid target,
|
||||
InventoryComponent? inventory=null,
|
||||
HumanoidComponent? appearance=null)
|
||||
HumanoidAppearanceComponent? appearance=null)
|
||||
{
|
||||
int age = 18;
|
||||
Gender gender = Gender.Epicene;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Content.Server.MagicMirror;
|
|||
public sealed class MagicMirrorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MarkingManager _markings = default!;
|
||||
[Dependency] private readonly HumanoidSystem _humanoid = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
|
||||
|
|
@ -30,14 +30,14 @@ public sealed class MagicMirrorSystem : EntitySystem
|
|||
|
||||
private void OnOpenUIAttempt(EntityUid uid, MagicMirrorComponent mirror, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidComponent>(args.User))
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.User))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component,
|
||||
MagicMirrorSelectMessage message)
|
||||
{
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ public sealed class MagicMirrorSystem : EntitySystem
|
|||
private void OnMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent component,
|
||||
MagicMirrorChangeColorMessage message)
|
||||
{
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ public sealed class MagicMirrorSystem : EntitySystem
|
|||
private void OnMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent component,
|
||||
MagicMirrorRemoveSlotMessage message)
|
||||
{
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ public sealed class MagicMirrorSystem : EntitySystem
|
|||
private void OnMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent component,
|
||||
MagicMirrorAddSlotMessage message)
|
||||
{
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -145,34 +145,34 @@ public sealed class MagicMirrorSystem : EntitySystem
|
|||
UpdateInterface(uid, message.Session.AttachedEntity.Value, message.Session);
|
||||
}
|
||||
|
||||
private void UpdateInterface(EntityUid uid, EntityUid playerUid, ICommonSession session, HumanoidComponent? humanoid = null)
|
||||
private void UpdateInterface(EntityUid uid, EntityUid playerUid, ICommonSession session, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(playerUid, ref humanoid) || session is not IPlayerSession player)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var hair = humanoid.CurrentMarkings.TryGetCategory(MarkingCategories.Hair, out var hairMarkings)
|
||||
var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings)
|
||||
? new List<Marking>(hairMarkings)
|
||||
: new();
|
||||
|
||||
var facialHair = humanoid.CurrentMarkings.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings)
|
||||
var facialHair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings)
|
||||
? new List<Marking>(facialHairMarkings)
|
||||
: new();
|
||||
|
||||
var msg = new MagicMirrorUiData(
|
||||
humanoid.Species,
|
||||
hair,
|
||||
humanoid.CurrentMarkings.PointsLeft(MarkingCategories.Hair) + hair.Count,
|
||||
humanoid.MarkingSet.PointsLeft(MarkingCategories.Hair) + hair.Count,
|
||||
facialHair,
|
||||
humanoid.CurrentMarkings.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count);
|
||||
humanoid.MarkingSet.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count);
|
||||
|
||||
_uiSystem.TrySendUiMessage(uid, MagicMirrorUiKey.Key, msg, player);
|
||||
}
|
||||
|
||||
private void AfterUIOpen(EntityUid uid, MagicMirrorComponent component, AfterActivatableUIOpenEvent args)
|
||||
{
|
||||
var looks = Comp<HumanoidComponent>(args.User);
|
||||
var looks = Comp<HumanoidAppearanceComponent>(args.User);
|
||||
var actor = Comp<ActorComponent>(args.User);
|
||||
|
||||
UpdateInterface(uid, args.User, args.Session);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ using Robust.Shared.Random;
|
|||
using Robust.Shared.Configuration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
|
||||
namespace Content.Server.Medical.BiomassReclaimer
|
||||
{
|
||||
|
|
@ -256,7 +257,7 @@ namespace Content.Server.Medical.BiomassReclaimer
|
|||
|
||||
// Reject souled bodies in easy mode.
|
||||
if (_configManager.GetCVar(CCVars.BiomassEasyMode) &&
|
||||
HasComp<HumanoidComponent>(dragged) &&
|
||||
HasComp<HumanoidAppearanceComponent>(dragged) &&
|
||||
TryComp<MindComponent>(dragged, out var mindComp))
|
||||
{
|
||||
if (mindComp.Mind?.UserId != null && _playerManager.TryGetSessionById(mindComp.Mind.UserId.Value, out _))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using System.Linq;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Medical.SuitSensors;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Medical.CrewMonitoring;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Medical.CrewMonitoring
|
||||
|
|
@ -13,33 +15,14 @@ namespace Content.Server.Medical.CrewMonitoring
|
|||
[Dependency] private readonly SuitSensorSystem _sensors = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private const float UpdateRate = 3f;
|
||||
private float _updateDif;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CrewMonitoringConsoleComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<CrewMonitoringConsoleComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// check update rate
|
||||
_updateDif += frameTime;
|
||||
if (_updateDif < UpdateRate)
|
||||
return;
|
||||
_updateDif = 0f;
|
||||
|
||||
var consoles = EntityManager.EntityQuery<CrewMonitoringConsoleComponent>();
|
||||
foreach (var console in consoles)
|
||||
{
|
||||
UpdateTimeouts(console.Owner, console);
|
||||
UpdateUserInterface(console.Owner, console);
|
||||
}
|
||||
SubscribeLocalEvent<CrewMonitoringConsoleComponent, BoundUIOpenedEvent>(OnUIOpened);
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, CrewMonitoringConsoleComponent component, ComponentRemove args)
|
||||
|
|
@ -49,12 +32,22 @@ namespace Content.Server.Medical.CrewMonitoring
|
|||
|
||||
private void OnPacketReceived(EntityUid uid, CrewMonitoringConsoleComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
var suitSensor = _sensors.PacketToSuitSensor(args.Data);
|
||||
if (suitSensor == null)
|
||||
var payload = args.Data;
|
||||
// check command
|
||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||
return;
|
||||
if (command != DeviceNetworkConstants.CmdUpdatedState)
|
||||
return;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_STATUS_COLLECTION, out Dictionary<string, SuitSensorStatus>? sensorStatus))
|
||||
return;
|
||||
|
||||
suitSensor.Timestamp = _gameTiming.CurTime;
|
||||
component.ConnectedSensors[args.SenderAddress] = suitSensor;
|
||||
component.ConnectedSensors = sensorStatus;
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnUIOpened(EntityUid uid, CrewMonitoringConsoleComponent component, BoundUIOpenedEvent args)
|
||||
{
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
|
||||
|
|
@ -72,19 +65,5 @@ namespace Content.Server.Medical.CrewMonitoring
|
|||
var uiState = new CrewMonitoringState(allSensors, xform.WorldPosition, component.Snap, component.Precision);
|
||||
ui.SetState(uiState);
|
||||
}
|
||||
|
||||
private void UpdateTimeouts(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
foreach (var (address, sensor) in component.ConnectedSensors)
|
||||
{
|
||||
// if too many time passed - sensor just dropped connection
|
||||
var dif = _gameTiming.CurTime - sensor.Timestamp;
|
||||
if (dif.Seconds > component.SensorTimeout)
|
||||
component.ConnectedSensors.Remove(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Medical.CrewMonitoring;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(CrewMonitoringServerSystem))]
|
||||
public sealed class CrewMonitoringServerComponent : Component
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// List of all currently connected sensors to this server.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, SuitSensorStatus> SensorStatus = new();
|
||||
|
||||
/// <summary>
|
||||
/// After what time sensor consider to be lost.
|
||||
/// </summary>
|
||||
[DataField("sensorTimeout"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SensorTimeout = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the server can become the currently active server. The server being unavailable usually means that it isn't powered
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Available = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether the server is the currently active server for the station it's on
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Active = true;
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Medical.SuitSensors;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Medical.CrewMonitoring;
|
||||
|
||||
public sealed class CrewMonitoringServerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SuitSensorSystem _sensors = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
|
||||
private const float UpdateRate = 3f;
|
||||
private float _updateDiff;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CrewMonitoringServerComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<CrewMonitoringServerComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
SubscribeLocalEvent<CrewMonitoringServerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// check update rate
|
||||
_updateDiff += frameTime;
|
||||
if (_updateDiff < UpdateRate)
|
||||
return;
|
||||
_updateDiff -= UpdateRate;
|
||||
|
||||
var servers = EntityManager.EntityQuery<CrewMonitoringServerComponent>();
|
||||
List<EntityUid> activeServers = new();
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
//Make sure the server is disconnected when it becomes unavailable
|
||||
if (!server.Available)
|
||||
{
|
||||
if (server.Active)
|
||||
DisconnectServer(server.Owner, server);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!server.Active)
|
||||
continue;
|
||||
|
||||
activeServers.Add(server.Owner);
|
||||
}
|
||||
|
||||
foreach (var activeServer in activeServers)
|
||||
{
|
||||
UpdateTimeout(activeServer);
|
||||
BroadcastSensorStatus(activeServer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the address of the currently active server for the given station id if there is one
|
||||
/// </summary>
|
||||
public bool TryGetActiveServerAddress(EntityUid stationId, out string? address)
|
||||
{
|
||||
var servers = EntityManager.EntityQuery<CrewMonitoringServerComponent, DeviceNetworkComponent>();
|
||||
(CrewMonitoringServerComponent, DeviceNetworkComponent)? last = default;
|
||||
|
||||
foreach (var (server, device) in servers)
|
||||
{
|
||||
if (!_stationSystem.GetOwningStation(server.Owner)?.Equals(stationId) ?? false)
|
||||
continue;
|
||||
|
||||
if (!server.Available)
|
||||
{
|
||||
DisconnectServer(server.Owner,server, device);
|
||||
continue;
|
||||
}
|
||||
|
||||
last = (server, device);
|
||||
|
||||
if (server.Active)
|
||||
{
|
||||
address = device.Address;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//If there was no active server for the station make the last available inactive one active
|
||||
if (last.HasValue)
|
||||
{
|
||||
ConnectServer(last.Value.Item1.Owner, last.Value.Item1, last.Value.Item2);
|
||||
address = last.Value.Item2.Address;
|
||||
return true;
|
||||
}
|
||||
|
||||
address = null;
|
||||
return address != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates a sensor status entry if the received package is a sensor status update
|
||||
/// </summary>
|
||||
private void OnPacketReceived(EntityUid uid, CrewMonitoringServerComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
var sensorStatus = _sensors.PacketToSuitSensor(args.Data);
|
||||
if (sensorStatus == null)
|
||||
return;
|
||||
|
||||
sensorStatus.Timestamp = _gameTiming.CurTime;
|
||||
component.SensorStatus[args.SenderAddress] = sensorStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the servers sensor status list
|
||||
/// </summary>
|
||||
private void OnRemove(EntityUid uid, CrewMonitoringServerComponent component, ComponentRemove args)
|
||||
{
|
||||
component.SensorStatus.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the server losing power
|
||||
/// </summary>
|
||||
private void OnPowerChanged(EntityUid uid, CrewMonitoringServerComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
component.Available = args.Powered;
|
||||
|
||||
if (!args.Powered)
|
||||
DisconnectServer(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop the sensor status if it hasn't been updated for to long
|
||||
/// </summary>
|
||||
private void UpdateTimeout(EntityUid uid, CrewMonitoringServerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
foreach (var (address, sensor) in component.SensorStatus)
|
||||
{
|
||||
var dif = _gameTiming.CurTime - sensor.Timestamp;
|
||||
if (dif.Seconds > component.SensorTimeout)
|
||||
component.SensorStatus.Remove(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the status of all connected sensors
|
||||
/// </summary>
|
||||
private void BroadcastSensorStatus(EntityUid uid, CrewMonitoringServerComponent? serverComponent = null, DeviceNetworkComponent? device = null)
|
||||
{
|
||||
if (!Resolve(uid, ref serverComponent, ref device))
|
||||
return;
|
||||
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
|
||||
[SuitSensorConstants.NET_STATUS_COLLECTION] = serverComponent.SensorStatus
|
||||
};
|
||||
|
||||
_deviceNetworkSystem.QueuePacket(uid, null, payload, device: device);
|
||||
}
|
||||
|
||||
private void ConnectServer(EntityUid uid, CrewMonitoringServerComponent? server = null, DeviceNetworkComponent? device = null)
|
||||
{
|
||||
if (!Resolve(uid, ref server, ref device))
|
||||
return;
|
||||
|
||||
server.Active = true;
|
||||
|
||||
if (_deviceNetworkSystem.IsDeviceConnected(uid, device))
|
||||
return;
|
||||
|
||||
_deviceNetworkSystem.ConnectDevice(uid, device);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects a server from the device network and clears the currently active server
|
||||
/// </summary>
|
||||
private void DisconnectServer(EntityUid uid, CrewMonitoringServerComponent? server = null, DeviceNetworkComponent? device = null)
|
||||
{
|
||||
if (!Resolve(uid, ref server, ref device))
|
||||
return;
|
||||
|
||||
server.SensorStatus.Clear();
|
||||
server.Active = false;
|
||||
|
||||
_deviceNetworkSystem.DisconnectDevice(uid, device, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -56,5 +56,19 @@ namespace Content.Server.Medical.SuitSensors
|
|||
/// Last time when sensor updated owners status
|
||||
/// </summary>
|
||||
public TimeSpan LastUpdate = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work.
|
||||
/// </summary>
|
||||
[DataField("station")]
|
||||
public EntityUid? StationId = null;
|
||||
|
||||
/// <summary>
|
||||
/// The server the suit sensor sends it state to.
|
||||
/// The suit sensor will try connecting to a new server when no server is connected.
|
||||
/// It does this by calling the servers entity system for performance reasons.
|
||||
/// </summary>
|
||||
[DataField("server")]
|
||||
public string? ConnectedServer = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ using Content.Server.Access.Systems;
|
|||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Medical.CrewMonitoring;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Inventory.Events;
|
||||
|
|
@ -25,6 +27,8 @@ namespace Content.Server.Medical.SuitSensors
|
|||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly CrewMonitoringServerSystem _monitoringServerSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
private const float UpdateRate = 1f;
|
||||
|
|
@ -57,27 +61,48 @@ namespace Content.Server.Medical.SuitSensors
|
|||
var sensors = EntityManager.EntityQuery<SuitSensorComponent, DeviceNetworkComponent>();
|
||||
foreach (var (sensor, device) in sensors)
|
||||
{
|
||||
if (device.TransmitFrequency is not uint frequency)
|
||||
if (!device.TransmitFrequency.HasValue || !sensor.StationId.HasValue)
|
||||
continue;
|
||||
|
||||
// check if sensor is ready to update
|
||||
if (curTime - sensor.LastUpdate < sensor.UpdateRate)
|
||||
continue;
|
||||
sensor.LastUpdate = curTime;
|
||||
|
||||
// Add a random offset to the next update time that isn't longer than the sensors update rate
|
||||
sensor.LastUpdate = curTime.Add(TimeSpan.FromSeconds(_random.Next(0, sensor.UpdateRate.Seconds)));
|
||||
|
||||
// get sensor status
|
||||
var status = GetSensorState(sensor.Owner, sensor);
|
||||
if (status == null)
|
||||
continue;
|
||||
|
||||
// broadcast it to device network
|
||||
//Retrieve active server address if the sensor isn't connected to a server
|
||||
if (sensor.ConnectedServer == null)
|
||||
{
|
||||
if (!_monitoringServerSystem.TryGetActiveServerAddress(sensor.StationId.Value, out var address))
|
||||
continue;
|
||||
|
||||
sensor.ConnectedServer = address;
|
||||
}
|
||||
|
||||
// Send it to the connected server
|
||||
var payload = SuitSensorToPacket(status);
|
||||
_deviceNetworkSystem.QueuePacket(sensor.Owner, null, payload, device: device);
|
||||
|
||||
// Clear the connected server if its address isn't on the network
|
||||
if (!_deviceNetworkSystem.IsAddressPresent(device.DeviceNetId, sensor.ConnectedServer))
|
||||
{
|
||||
sensor.ConnectedServer = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
_deviceNetworkSystem.QueuePacket(sensor.Owner, sensor.ConnectedServer, payload, device: device);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args)
|
||||
{
|
||||
component.StationId = _stationSystem.GetOwningStation(uid);
|
||||
|
||||
// generate random mode
|
||||
if (component.RandomMode)
|
||||
{
|
||||
|
|
@ -277,7 +302,7 @@ namespace Content.Server.Medical.SuitSensors
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize suit sensor status into device network package.
|
||||
/// Serialize create a device network package from the suit sensors status.
|
||||
/// </summary>
|
||||
public NetworkPayload SuitSensorToPacket(SuitSensorStatus status)
|
||||
{
|
||||
|
|
@ -299,7 +324,7 @@ namespace Content.Server.Medical.SuitSensors
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to deserialize device network message into suit sensor status
|
||||
/// Try to create the suit sensors status from the device network message
|
||||
/// </summary>
|
||||
public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -102,9 +102,11 @@ public sealed class MindSystem : EntitySystem
|
|||
}
|
||||
|
||||
// TODO refactor observer spawning.
|
||||
// please.
|
||||
if (!spawnPosition.IsValid(EntityManager))
|
||||
{
|
||||
Logger.ErrorS("mind", $"Entity \"{ToPrettyString(uid)}\" for {mind.Mind?.CharacterName} was deleted, and no applicable spawn location is available.");
|
||||
// This should be an error, if it didn't cause tests to start erroring when they delete a player.
|
||||
Logger.WarningS("mind", $"Entity \"{ToPrettyString(uid)}\" for {mind.Mind?.CharacterName} was deleted, and no applicable spawn location is available.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace Content.Server.Polymorph.Systems
|
|||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly HumanoidSystem _humanoid = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ namespace Content.Server.Preferences.Managers
|
|||
case HumanoidCharacterProfile hp:
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var selectedSpecies = HumanoidSystem.DefaultSpecies;
|
||||
var selectedSpecies = HumanoidAppearanceSystem.DefaultSpecies;
|
||||
|
||||
if (prototypeManager.TryIndex<SpeciesPrototype>(hp.Species, out var species) && species.RoundStart)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ using Content.Shared.Mobs.Systems;
|
|||
using Content.Shared.Revenant.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Humanoid;
|
||||
|
||||
namespace Content.Server.Revenant.EntitySystems;
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ public sealed partial class RevenantSystem
|
|||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<MobStateComponent>(target) || !HasComp<HumanoidComponent>(target) || HasComp<RevenantComponent>(target))
|
||||
if (!HasComp<MobStateComponent>(target) || !HasComp<HumanoidAppearanceComponent>(target) || HasComp<RevenantComponent>(target))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
|
|
|||
|
|
@ -1,41 +1,53 @@
|
|||
using Content.Server.Humanoid;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Speech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component required for entities to be able to scream.
|
||||
/// Component required for entities to be able to do vocal emotions.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(VocalSystem))]
|
||||
public sealed class VocalComponent : Component
|
||||
{
|
||||
[DataField("maleScream")]
|
||||
public SoundSpecifier MaleScream = new SoundCollectionSpecifier("MaleScreams");
|
||||
/// <summary>
|
||||
/// Emote sounds prototype id for each sex (not gender).
|
||||
/// Entities without <see cref="HumanoidComponent"/> considered to be <see cref="Sex.Unsexed"/>.
|
||||
/// </summary>
|
||||
[DataField("sounds", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer<Sex, EmoteSoundsPrototype>))]
|
||||
public Dictionary<Sex, string>? Sounds;
|
||||
|
||||
[DataField("femaleScream")]
|
||||
public SoundSpecifier FemaleScream = new SoundCollectionSpecifier("FemaleScreams");
|
||||
|
||||
[DataField("unsexedScream")]
|
||||
public SoundSpecifier UnsexedScream = new SoundCollectionSpecifier("MaleScreams");
|
||||
[DataField("screamId", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
public string ScreamId = "Scream";
|
||||
|
||||
[DataField("wilhelm")]
|
||||
public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg");
|
||||
|
||||
[DataField("audioParams")]
|
||||
public AudioParams AudioParams = AudioParams.Default.WithVolume(4f);
|
||||
|
||||
[DataField("wilhelmProbability")]
|
||||
public float WilhelmProbability = 0.01f;
|
||||
|
||||
public const float Variation = 0.125f;
|
||||
[DataField("screamActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string ScreamActionId = "Scream";
|
||||
|
||||
[DataField("actionId", customTypeSerializer:typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string ActionId = "Scream";
|
||||
[DataField("screamAction")]
|
||||
public InstantAction? ScreamAction;
|
||||
|
||||
[DataField("action")] // must be a data-field to properly save cooldown when saving game state.
|
||||
public InstantAction? ScreamAction = null;
|
||||
/// <summary>
|
||||
/// Currently loaded emote sounds prototype, based on entity sex.
|
||||
/// Null if no valid prototype for entity sex was found.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EmoteSoundsPrototype? EmoteSounds = null;
|
||||
}
|
||||
|
||||
public sealed class ScreamActionEvent : InstantActionEvent { };
|
||||
public sealed class ScreamActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
|||
105
Content.Server/Speech/EntitySystems/VocalSystem.cs
Normal file
105
Content.Server/Speech/EntitySystems/VocalSystem.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using Content.Server.Actions;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Speech.EntitySystems;
|
||||
|
||||
public sealed class VocalSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<VocalComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<VocalComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<VocalComponent, SexChangedEvent>(OnSexChanged);
|
||||
SubscribeLocalEvent<VocalComponent, EmoteEvent>(OnEmote);
|
||||
SubscribeLocalEvent<VocalComponent, ScreamActionEvent>(OnScreamAction);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, VocalComponent component, MapInitEvent args)
|
||||
{
|
||||
// try to add scream action when vocal comp added
|
||||
if (_proto.TryIndex(component.ScreamActionId, out InstantActionPrototype? proto))
|
||||
{
|
||||
component.ScreamAction = new InstantAction(proto);
|
||||
_actions.AddAction(uid, component.ScreamAction, null);
|
||||
}
|
||||
|
||||
LoadSounds(uid, component);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args)
|
||||
{
|
||||
// remove scream action when component removed
|
||||
if (component.ScreamAction != null)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.ScreamAction);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSexChanged(EntityUid uid, VocalComponent component, SexChangedEvent args)
|
||||
{
|
||||
LoadSounds(uid, component);
|
||||
}
|
||||
|
||||
private void OnEmote(EntityUid uid, VocalComponent component, ref EmoteEvent args)
|
||||
{
|
||||
if (args.Handled || !args.Emote.Category.HasFlag(EmoteCategory.Vocal))
|
||||
return;
|
||||
|
||||
// snowflake case for wilhelm scream easter egg
|
||||
if (args.Emote.ID == component.ScreamId)
|
||||
{
|
||||
args.Handled = TryPlayScreamSound(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
// just play regular sound based on emote proto
|
||||
args.Handled = _chat.TryPlayEmoteSound(uid, component.EmoteSounds, args.Emote);
|
||||
}
|
||||
|
||||
private void OnScreamAction(EntityUid uid, VocalComponent component, ScreamActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_chat.TryEmoteWithChat(uid, component.ScreamActionId);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private bool TryPlayScreamSound(EntityUid uid, VocalComponent component)
|
||||
{
|
||||
if (_random.Prob(component.WilhelmProbability))
|
||||
{
|
||||
_audio.PlayPvs(component.Wilhelm, uid, component.Wilhelm.Params);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _chat.TryPlayEmoteSound(uid, component.EmoteSounds, component.ScreamId);
|
||||
}
|
||||
|
||||
private void LoadSounds(EntityUid uid, VocalComponent component, Sex? sex = null)
|
||||
{
|
||||
if (component.Sounds == null)
|
||||
return;
|
||||
|
||||
sex ??= CompOrNull<HumanoidAppearanceComponent>(uid)?.Sex ?? Sex.Unsexed;
|
||||
|
||||
if (!component.Sounds.TryGetValue(sex.Value, out var protoId))
|
||||
return;
|
||||
_proto.TryIndex(protoId, out component.EmoteSounds);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
using Content.Server.Humanoid;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Speech;
|
||||
|
||||
/// <summary>
|
||||
/// Fer Screamin
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Or I guess other vocalizations, like laughing. If fun is ever legalized on the station.
|
||||
/// </remarks>
|
||||
public sealed class VocalSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<VocalComponent, ScreamActionEvent>(OnActionPerform);
|
||||
SubscribeLocalEvent<VocalComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<VocalComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, VocalComponent component, ComponentStartup args)
|
||||
{
|
||||
if (component.ScreamAction == null
|
||||
&& _proto.TryIndex(component.ActionId, out InstantActionPrototype? act))
|
||||
{
|
||||
component.ScreamAction = new(act);
|
||||
}
|
||||
|
||||
if (component.ScreamAction != null)
|
||||
_actions.AddAction(uid, component.ScreamAction, null);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.ScreamAction != null)
|
||||
_actions.RemoveAction(uid, component.ScreamAction);
|
||||
}
|
||||
|
||||
private void OnActionPerform(EntityUid uid, VocalComponent component, ScreamActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = TryScream(uid, component);
|
||||
}
|
||||
|
||||
public bool TryScream(EntityUid uid, VocalComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return false;
|
||||
|
||||
if (!_blocker.CanSpeak(uid))
|
||||
return false;
|
||||
|
||||
var sex = CompOrNull<HumanoidComponent>(uid)?.Sex ?? Sex.Unsexed;
|
||||
|
||||
if (_random.Prob(component.WilhelmProbability))
|
||||
{
|
||||
SoundSystem.Play(component.Wilhelm.GetSound(), Filter.Pvs(uid), uid, component.AudioParams);
|
||||
return true;
|
||||
}
|
||||
|
||||
var scale = (float) _random.NextGaussian(1, VocalComponent.Variation);
|
||||
var pitchedParams = component.AudioParams.WithPitchScale(scale);
|
||||
|
||||
switch (sex)
|
||||
{
|
||||
case Sex.Male:
|
||||
SoundSystem.Play(component.MaleScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams);
|
||||
break;
|
||||
case Sex.Female:
|
||||
SoundSystem.Play(component.FemaleScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams);
|
||||
break;
|
||||
default:
|
||||
SoundSystem.Play(component.UnsexedScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams);
|
||||
break;
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("scream-action-popup"), uid, PopupType.Medium);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
77
Content.Server/Spider/SpiderSystem.cs
Normal file
77
Content.Server/Spider/SpiderSystem.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Spider;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Spider;
|
||||
|
||||
public sealed class SpiderSystem : SharedSpiderSystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SpiderComponent, SpiderWebActionEvent>(OnSpawnNet);
|
||||
}
|
||||
|
||||
private void OnSpawnNet(EntityUid uid, SpiderComponent component, SpiderWebActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var transform = Transform(uid);
|
||||
|
||||
if (transform.GridUid == null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("spider-web-action-nogrid"), args.Performer, args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
var coords = transform.Coordinates;
|
||||
|
||||
// TODO generic way to get certain coordinates
|
||||
|
||||
var result = false;
|
||||
// Spawn web in center
|
||||
if (!IsTileBlockedByWeb(coords))
|
||||
{
|
||||
Spawn(component.WebPrototype, coords);
|
||||
result = true;
|
||||
}
|
||||
|
||||
// Spawn web in other directions
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var direction = (DirectionFlag) (1 << i);
|
||||
coords = transform.Coordinates.Offset(direction.AsDir().ToVec());
|
||||
|
||||
if (!IsTileBlockedByWeb(coords))
|
||||
{
|
||||
Spawn(component.WebPrototype, coords);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (result)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("spider-web-action-success"), args.Performer, args.Performer);
|
||||
args.Handled = true;
|
||||
}
|
||||
else
|
||||
_popup.PopupEntity(Loc.GetString("spider-web-action-fail"), args.Performer, args.Performer);
|
||||
}
|
||||
|
||||
private bool IsTileBlockedByWeb(EntityCoordinates coords)
|
||||
{
|
||||
foreach (var entity in coords.GetEntitiesInTile())
|
||||
{
|
||||
if (HasComp<SpiderWebObjectComponent>(entity))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ public sealed class StationSpawningSystem : EntitySystem
|
|||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly HandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly PDASystem _pdaSystem = default!;
|
||||
|
|
@ -107,7 +107,7 @@ public sealed class StationSpawningSystem : EntitySystem
|
|||
}
|
||||
|
||||
var entity = EntityManager.SpawnEntity(
|
||||
_prototypeManager.Index<SpeciesPrototype>(profile?.Species ?? HumanoidSystem.DefaultSpecies).Prototype,
|
||||
_prototypeManager.Index<SpeciesPrototype>(profile?.Species ?? HumanoidAppearanceSystem.DefaultSpecies).Prototype,
|
||||
coordinates);
|
||||
|
||||
if (job?.StartingGear != null)
|
||||
|
|
|
|||
32
Content.Server/StationEvents/Events/SpiderSpawn.cs
Normal file
32
Content.Server/StationEvents/Events/SpiderSpawn.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class SpiderSpawn : StationEventSystem
|
||||
{
|
||||
public override string Prototype => "SpiderSpawn";
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var mod = Math.Sqrt(GetSeverityModifier());
|
||||
|
||||
var spawnAmount = (int) (RobustRandom.Next(4, 8) * mod);
|
||||
Sawmill.Info($"Spawning {spawnAmount} of spiders");
|
||||
foreach (var location in spawnLocations)
|
||||
{
|
||||
if (spawnAmount-- == 0)
|
||||
break;
|
||||
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(location.Owner);
|
||||
|
||||
EntityManager.SpawnEntity("MobGiantSpiderAngry", coords.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
|
|
@ -7,7 +8,7 @@ namespace Content.Server.StationEvents.Events;
|
|||
public sealed class VentCritters : StationEventSystem
|
||||
{
|
||||
public static List<string> SpawnedPrototypeChoices = new List<string>()
|
||||
{"MobGiantSpiderAngry", "MobMouse", "MobMouse1", "MobMouse2"};
|
||||
{"MobMouse", "MobMouse1", "MobMouse2"};
|
||||
|
||||
public override string Prototype => "VentCritters";
|
||||
|
||||
|
|
@ -18,9 +19,7 @@ public sealed class VentCritters : StationEventSystem
|
|||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var mod = Math.Sqrt(GetSeverityModifier());
|
||||
|
||||
var spawnAmount = (int) (RobustRandom.Next(4, 12) * mod); // A small colony of critters.
|
||||
var spawnAmount = (int) (RobustRandom.Next(4, 12)); // A small colony of critters.
|
||||
Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}");
|
||||
foreach (var location in spawnLocations)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Content.Shared.Humanoid;
|
|||
using Content.Shared.Store;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Content.Shared.Humanoid;
|
||||
|
||||
namespace Content.Server.Store.Conditions;
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ public sealed class BuyerSpeciesCondition : ListingCondition
|
|||
{
|
||||
var ent = args.EntityManager;
|
||||
|
||||
if (!ent.TryGetComponent<HumanoidComponent>(args.Buyer, out var appearance))
|
||||
if (!ent.TryGetComponent<HumanoidAppearanceComponent>(args.Buyer, out var appearance))
|
||||
return true; // inanimate or non-humanoid entities should be handled elsewhere, main example being surplus crates
|
||||
|
||||
if (Blacklist != null)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ public sealed class ActiveZombieComponent : Component
|
|||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RandomGroanAttempt = 5;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string GroanEmoteId = "Scream";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LastDamageGroanCooldown = 0f;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Cloning;
|
||||
using Content.Server.Disease;
|
||||
using Content.Server.Disease.Components;
|
||||
using Content.Server.Drone.Components;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Speech;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Emoting.Systems;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Disease.Events;
|
||||
|
|
@ -28,17 +33,22 @@ namespace Content.Server.Zombies
|
|||
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
|
||||
[Dependency] private readonly ServerInventorySystem _inv = default!;
|
||||
[Dependency] private readonly VocalSystem _vocal = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ZombieComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ZombieComponent, EmoteEvent>(OnEmote, before:
|
||||
new []{typeof(VocalSystem), typeof(BodyEmotesSystem)});
|
||||
|
||||
SubscribeLocalEvent<ZombieComponent, MeleeHitEvent>(OnMeleeHit);
|
||||
SubscribeLocalEvent<ZombieComponent, MobStateChangedEvent>(OnMobState);
|
||||
SubscribeLocalEvent<ZombieComponent, CloningEvent>(OnZombieCloning);
|
||||
SubscribeLocalEvent<ActiveZombieComponent, DamageChangedEvent>(OnDamage);
|
||||
SubscribeLocalEvent<ActiveZombieComponent, AttemptSneezeCoughEvent>(OnSneeze);
|
||||
SubscribeLocalEvent<ActiveZombieComponent, TryingToSleepEvent>(OnSleepAttempt);
|
||||
|
|
@ -50,6 +60,21 @@ namespace Content.Server.Zombies
|
|||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args)
|
||||
{
|
||||
if (component.EmoteSoundsId == null)
|
||||
return;
|
||||
_protoManager.TryIndex(component.EmoteSoundsId, out component.EmoteSounds);
|
||||
}
|
||||
|
||||
private void OnEmote(EntityUid uid, ZombieComponent component, ref EmoteEvent args)
|
||||
{
|
||||
// always play zombie emote sounds and ignore others
|
||||
if (args.Handled)
|
||||
return;
|
||||
args.Handled = _chat.TryPlayEmoteSound(uid, component.EmoteSounds, args.Emote);
|
||||
}
|
||||
|
||||
private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Alive)
|
||||
|
|
@ -151,7 +176,7 @@ namespace Content.Server.Zombies
|
|||
// [automated maintainer groan]
|
||||
_chat.TrySendInGameICMessage(uid, "[automated zombie groan]", InGameICChatType.Speak, false);
|
||||
else
|
||||
_vocal.TryScream(uid);
|
||||
_chat.TryEmoteWithoutChat(uid, component.GroanEmoteId);
|
||||
|
||||
component.LastDamageGroanCooldown = component.GroanCooldown;
|
||||
}
|
||||
|
|
@ -176,5 +201,36 @@ namespace Content.Server.Zombies
|
|||
DoGroan(zombiecomp.Owner, zombiecomp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the function to call if you want to unzombify an entity.
|
||||
/// </summary>
|
||||
/// <param name="source">the entity having the ZombieComponent</param>
|
||||
/// <param name="target">the entity you want to unzombify (different from source in case of cloning, for example)</param>
|
||||
/// <remarks>
|
||||
/// this currently only restore the name and skin/eye color from before zombified
|
||||
/// TODO: reverse everything else done in ZombifyEntity
|
||||
/// </remarks>
|
||||
public bool UnZombify(EntityUid source, EntityUid target, ZombieComponent? zombiecomp)
|
||||
{
|
||||
if (!Resolve(source, ref zombiecomp))
|
||||
return false;
|
||||
|
||||
foreach (var (layer, info) in zombiecomp.BeforeZombifiedCustomBaseLayers)
|
||||
{
|
||||
_humanoidSystem.SetBaseLayerColor(target, layer, info.Color);
|
||||
_humanoidSystem.SetBaseLayerId(target, layer, info.ID);
|
||||
}
|
||||
_humanoidSystem.SetSkinColor(target, zombiecomp.BeforeZombifiedSkinColor);
|
||||
|
||||
MetaData(target).EntityName = zombiecomp.BeforeZombifiedEntityName;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnZombieCloning(EntityUid uid, ZombieComponent zombiecomp, ref CloningEvent args)
|
||||
{
|
||||
if (UnZombify(args.Source, args.Target, zombiecomp))
|
||||
args.NameHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ namespace Content.Server.Zombies
|
|||
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private readonly ServerInventorySystem _serverInventory = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly HumanoidSystem _sharedHuApp = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _sharedHuApp = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private readonly IChatManager _chatMan = default!;
|
||||
|
|
@ -118,11 +118,6 @@ namespace Content.Server.Zombies
|
|||
RemComp<PacifiedComponent>(target);
|
||||
// Corvax-DionaPacifist-End
|
||||
|
||||
var vocal = EnsureComp<VocalComponent>(target);
|
||||
var scream = new SoundCollectionSpecifier ("ZombieScreams");
|
||||
vocal.FemaleScream = scream;
|
||||
vocal.MaleScream = scream;
|
||||
|
||||
//This is the actual damage of the zombie. We assign the visual appearance
|
||||
//and range here because of stuff we'll find out later
|
||||
var melee = EnsureComp<MeleeWeaponComponent>(target);
|
||||
|
|
@ -132,8 +127,12 @@ namespace Content.Server.Zombies
|
|||
Dirty(melee);
|
||||
|
||||
//We have specific stuff for humanoid zombies because they matter more
|
||||
if (TryComp<HumanoidComponent>(target, out var huApComp)) //huapcomp
|
||||
if (TryComp<HumanoidAppearanceComponent>(target, out var huApComp)) //huapcomp
|
||||
{
|
||||
//store some values before changing them in case the humanoid get cloned later
|
||||
zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor;
|
||||
zombiecomp.BeforeZombifiedCustomBaseLayers = new(huApComp.CustomBaseLayers);
|
||||
|
||||
_sharedHuApp.SetSkinColor(target, zombiecomp.SkinColor, humanoid: huApComp);
|
||||
_sharedHuApp.SetBaseLayerColor(target, HumanoidVisualLayers.Eyes, zombiecomp.EyeColor, humanoid: huApComp);
|
||||
|
||||
|
|
@ -179,6 +178,7 @@ namespace Content.Server.Zombies
|
|||
|
||||
//gives it the funny "Zombie ___" name.
|
||||
var meta = MetaData(target);
|
||||
zombiecomp.BeforeZombifiedEntityName = meta.EntityName;
|
||||
meta.EntityName = Loc.GetString("zombie-name-prefix", ("target", meta.EntityName));
|
||||
|
||||
_identity.QueueIdentityUpdate(target);
|
||||
|
|
|
|||
51
Content.Shared/Chat/Prototypes/EmotePrototype.cs
Normal file
51
Content.Shared/Chat/Prototypes/EmotePrototype.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chat.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// IC emotes (scream, smile, clapping, etc).
|
||||
/// Entities can activate emotes by chat input or code.
|
||||
/// </summary>
|
||||
[Prototype("emote")]
|
||||
public sealed class EmotePrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Different emote categories may be handled by different systems.
|
||||
/// Also may be used for filtering.
|
||||
/// </summary>
|
||||
[DataField("category")]
|
||||
public EmoteCategory Category = EmoteCategory.General;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of words that will be sent to chat if emote activates.
|
||||
/// Will be picked randomly from list.
|
||||
/// </summary>
|
||||
[DataField("chatMessages")]
|
||||
public List<string> ChatMessages = new();
|
||||
|
||||
/// <summary>
|
||||
/// Trigger words for emote. Case independent.
|
||||
/// When typed into players chat they will activate emote event.
|
||||
/// All words should be unique across all emote prototypes.
|
||||
/// </summary>
|
||||
[DataField("chatTriggers")]
|
||||
public HashSet<string> ChatTriggers = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IC emote category. Usually physical source of emote,
|
||||
/// like hands, voice, face, etc.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum EmoteCategory : byte
|
||||
{
|
||||
Invalid = 0,
|
||||
Vocal = 1 << 0,
|
||||
Hands = 1 << 1,
|
||||
General = byte.MaxValue
|
||||
}
|
||||
36
Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs
Normal file
36
Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Shared.Chat.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Sounds collection for each <see cref="EmotePrototype"/>.
|
||||
/// Different entities may use different sounds collections.
|
||||
/// </summary>
|
||||
[Prototype("emoteSounds")]
|
||||
public sealed class EmoteSoundsPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Optional fallback sound that will play if collection
|
||||
/// doesn't have specific sound for this emote id.
|
||||
/// </summary>
|
||||
[DataField("sound")]
|
||||
public SoundSpecifier? FallbackSound;
|
||||
|
||||
/// <summary>
|
||||
/// Optional audio params that will be applied to ALL sounds.
|
||||
/// This will overwrite any params that may be set in sound specifiers.
|
||||
/// </summary>
|
||||
[DataField("params")]
|
||||
public AudioParams? GeneralParams;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of emote prototypes and their sounds.
|
||||
/// </summary>
|
||||
[DataField("sounds", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<SoundSpecifier, EmotePrototype>))]
|
||||
public Dictionary<string, SoundSpecifier> Sounds = new();
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
namespace Content.Shared.Chat;
|
||||
|
||||
public abstract class SharedChatSystem : EntitySystem {}
|
||||
public abstract class SharedChatSystem : EntitySystem
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
|
@ -9,6 +11,8 @@ namespace Content.Shared.Clothing.EntitySystems;
|
|||
public abstract class ClothingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSys = default!;
|
||||
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -23,11 +27,15 @@ public abstract class ClothingSystem : EntitySystem
|
|||
protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args)
|
||||
{
|
||||
component.InSlot = args.Slot;
|
||||
if (args.Slot == "head" && _tagSystem.HasTag(args.Equipment, "HidesHair"))
|
||||
_humanoidSystem.SetLayerVisibility(args.Equipee, HumanoidVisualLayers.Hair, false);
|
||||
}
|
||||
|
||||
protected virtual void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args)
|
||||
{
|
||||
component.InSlot = null;
|
||||
if (args.Slot == "head" && _tagSystem.HasTag(args.Equipment, "HidesHair"))
|
||||
_humanoidSystem.SetLayerVisibility(args.Equipee, HumanoidVisualLayers.Hair, true);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, ClothingComponent component, ref ComponentGetState args)
|
||||
|
|
|
|||
136
Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
Normal file
136
Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
public sealed class HumanoidAppearanceComponent : Component
|
||||
{
|
||||
[DataField("markingSet")]
|
||||
public MarkingSet MarkingSet = new();
|
||||
|
||||
[DataField("baseLayers")]
|
||||
public Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer> BaseLayers = new();
|
||||
|
||||
[DataField("permanentlyHidden")]
|
||||
public HashSet<HumanoidVisualLayers> PermanentlyHidden = new();
|
||||
|
||||
// Couldn't these be somewhere else?
|
||||
|
||||
[DataField("gender")]
|
||||
[ViewVariables] public Gender Gender = default!;
|
||||
|
||||
[DataField("age")]
|
||||
[ViewVariables] public int Age = 18;
|
||||
|
||||
/// <summary>
|
||||
/// Any custom base layers this humanoid might have. See:
|
||||
/// limb transplants (potentially), robotic arms, etc.
|
||||
/// Stored on the server, this is merged in the client into
|
||||
/// all layer settings.
|
||||
/// </summary>
|
||||
[DataField("customBaseLayers")]
|
||||
public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Current species. Dictates things like base body sprites,
|
||||
/// base humanoid to spawn, etc.
|
||||
/// </summary>
|
||||
[DataField("species", customTypeSerializer: typeof(PrototypeIdSerializer<SpeciesPrototype>))]
|
||||
public string Species { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The initial profile and base layers to apply to this humanoid.
|
||||
/// </summary>
|
||||
[DataField("initial", customTypeSerializer: typeof(PrototypeIdSerializer<HumanoidProfilePrototype>))]
|
||||
public string? Initial { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Skin color of this humanoid.
|
||||
/// </summary>
|
||||
[DataField("skinColor")]
|
||||
public Color SkinColor { get; set; } = Color.FromHex("#C0967F");
|
||||
|
||||
/// <summary>
|
||||
/// Visual layers currently hidden. This will affect the base sprite
|
||||
/// on this humanoid layer, and any markings that sit above it.
|
||||
/// </summary>
|
||||
[DataField("hiddenLayers")]
|
||||
public HashSet<HumanoidVisualLayers> HiddenLayers = new();
|
||||
|
||||
[DataField("sex")]
|
||||
public Sex Sex = Sex.Male;
|
||||
|
||||
[DataField("eyeColor")]
|
||||
public Color EyeColor = Color.Brown;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HumanoidAppearanceState : ComponentState
|
||||
{
|
||||
public readonly MarkingSet Markings;
|
||||
public readonly HashSet<HumanoidVisualLayers> PermanentlyHidden;
|
||||
public readonly HashSet<HumanoidVisualLayers> HiddenLayers;
|
||||
public readonly Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayers;
|
||||
public readonly Sex Sex;
|
||||
public readonly Gender Gender;
|
||||
public readonly int Age = 18;
|
||||
public readonly string Species;
|
||||
public readonly Color SkinColor;
|
||||
public readonly Color EyeColor;
|
||||
|
||||
public HumanoidAppearanceState(
|
||||
MarkingSet currentMarkings,
|
||||
HashSet<HumanoidVisualLayers> permanentlyHidden,
|
||||
HashSet<HumanoidVisualLayers> hiddenLayers,
|
||||
Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayers,
|
||||
Sex sex,
|
||||
Gender gender,
|
||||
int age,
|
||||
string species,
|
||||
Color skinColor,
|
||||
Color eyeColor)
|
||||
{
|
||||
Markings = currentMarkings;
|
||||
PermanentlyHidden = permanentlyHidden;
|
||||
HiddenLayers = hiddenLayers;
|
||||
CustomBaseLayers = customBaseLayers;
|
||||
Sex = sex;
|
||||
Gender = gender;
|
||||
Age = age;
|
||||
Species = species;
|
||||
SkinColor = skinColor;
|
||||
EyeColor = eyeColor;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct CustomBaseLayerInfo
|
||||
{
|
||||
public CustomBaseLayerInfo(string id, Color? color = null)
|
||||
{
|
||||
DebugTools.Assert(IoCManager.Resolve<IPrototypeManager>().HasIndex<HumanoidSpeciesSpriteLayer>(id));
|
||||
ID = id;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ID of this custom base layer. Must be a <see cref="HumanoidSpeciesSpriteLayer"/>.
|
||||
/// </summary>
|
||||
[DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer<HumanoidSpeciesSpriteLayer>), required: true)]
|
||||
public string ID { init; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Color of this custom base layer. Null implies skin colour.
|
||||
/// </summary>
|
||||
[DataField("color")]
|
||||
public Color? Color { init; get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
using Content.Shared.Corvax.TTS;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class HumanoidComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Current species. Dictates things like base body sprites,
|
||||
/// base humanoid to spawn, etc.
|
||||
/// </summary>
|
||||
[DataField("species", customTypeSerializer: typeof(PrototypeIdSerializer<SpeciesPrototype>))]
|
||||
public string Species { get; set; } = string.Empty;
|
||||
|
||||
// Corvax-TTS-Start
|
||||
/// <summary>
|
||||
/// Current voice. Used for correct cloning.
|
||||
/// </summary>
|
||||
[DataField("voice", customTypeSerializer: typeof(PrototypeIdSerializer<TTSVoicePrototype>))]
|
||||
public string Voice { get; set; } = SharedHumanoidSystem.DefaultVoice;
|
||||
// Corvax-TTS-End
|
||||
|
||||
/// <summary>
|
||||
/// The initial profile and base layers to apply to this humanoid.
|
||||
/// </summary>
|
||||
[DataField("initial", customTypeSerializer: typeof(PrototypeIdSerializer<HumanoidProfilePrototype>))]
|
||||
public string? Initial { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Skin color of this humanoid.
|
||||
/// </summary>
|
||||
[DataField("skinColor")]
|
||||
public Color SkinColor { get; set; } = Color.FromHex("#C0967F");
|
||||
|
||||
/// <summary>
|
||||
/// Visual layers currently hidden. This will affect the base sprite
|
||||
/// on this humanoid layer, and any markings that sit above it.
|
||||
/// </summary>
|
||||
[ViewVariables] public readonly HashSet<HumanoidVisualLayers> HiddenLayers = new();
|
||||
|
||||
[DataField("sex")] public Sex Sex = Sex.Male;
|
||||
|
||||
public MarkingSet CurrentMarkings = new();
|
||||
|
||||
/// <summary>
|
||||
/// Any custom base layers this humanoid might have. See:
|
||||
/// limb transplants (potentially), robotic arms, etc.
|
||||
/// Stored on the server, this is merged in the client into
|
||||
/// all layer settings.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayers = new();
|
||||
|
||||
public HashSet<HumanoidVisualLayers> PermanentlyHidden = new();
|
||||
|
||||
public HashSet<HumanoidVisualLayers> AllHiddenLayers
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new HashSet<HumanoidVisualLayers>(HiddenLayers);
|
||||
result.UnionWith(PermanentlyHidden);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't these be somewhere else?
|
||||
[ViewVariables] public Gender Gender = default!;
|
||||
[ViewVariables] public int Age = 18;
|
||||
|
||||
[ViewVariables] public List<Marking> CurrentClientMarkings = new();
|
||||
|
||||
public Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer> BaseLayers = new();
|
||||
|
||||
public string LastSpecies = default!;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CustomBaseLayerInfo
|
||||
{
|
||||
public CustomBaseLayerInfo(string id, Color color)
|
||||
{
|
||||
ID = id;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ID of this custom base layer. Must be a <see cref="HumanoidSpeciesSpriteLayer"/>.
|
||||
/// </summary>
|
||||
[DataField("id")]
|
||||
public string ID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Color of this custom base layer.
|
||||
/// </summary>
|
||||
[DataField("color")]
|
||||
public Color Color { get; }
|
||||
}
|
||||
|
|
@ -15,6 +15,14 @@ namespace Content.Shared.Humanoid
|
|||
};
|
||||
}
|
||||
|
||||
public static string GetSexMorph(HumanoidVisualLayers layer, Sex sex, string id)
|
||||
{
|
||||
if (!HasSexMorph(layer) || sex == Sex.Unsexed)
|
||||
return id;
|
||||
|
||||
return $"{id}{sex}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sublayers. Any other layers that may visually depend on this layer existing.
|
||||
/// For example, the head has layers such as eyes, hair, etc. depending on it.
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
using Content.Shared.Humanoid.Markings;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum HumanoidVisualizerKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HumanoidVisualizerData : ICloneable
|
||||
{
|
||||
public HumanoidVisualizerData(string species, Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayerInfo, Color skinColor, Sex sex, List<HumanoidVisualLayers> layerVisibility, List<Marking> markings)
|
||||
{
|
||||
Species = species;
|
||||
CustomBaseLayerInfo = customBaseLayerInfo;
|
||||
SkinColor = skinColor;
|
||||
Sex = sex;
|
||||
LayerVisibility = layerVisibility;
|
||||
Markings = markings;
|
||||
}
|
||||
|
||||
public string Species { get; }
|
||||
public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayerInfo { get; }
|
||||
public Color SkinColor { get; }
|
||||
public Sex Sex { get; }
|
||||
public List<HumanoidVisualLayers> LayerVisibility { get; }
|
||||
public List<Marking> Markings { get; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new HumanoidVisualizerData(Species, new(CustomBaseLayerInfo), SkinColor, Sex, new(LayerVisibility), new(Markings));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using Robust.Shared.Serialization;
|
|||
|
||||
namespace Content.Shared.Humanoid.Markings
|
||||
{
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class Marking : IEquatable<Marking>, IComparable<Marking>, IComparable<string>
|
||||
{
|
||||
|
|
@ -54,7 +55,7 @@ namespace Content.Shared.Humanoid.Markings
|
|||
/// <summary>
|
||||
/// If this marking is currently visible.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("visible")]
|
||||
public bool Visible = true;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Content.Shared.Humanoid.Markings;
|
|||
/// This is serializable for the admin panel that sets markings on demand for a player.
|
||||
/// Most APIs that accept a set of markings usually use a List of type Marking instead.
|
||||
/// </remarks>
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MarkingSet
|
||||
{
|
||||
|
|
@ -40,16 +41,14 @@ public sealed class MarkingSet
|
|||
/// feature of markings, which is the limit of markings you can put on a
|
||||
/// humanoid.
|
||||
/// </remarks>
|
||||
private Dictionary<MarkingCategories, List<Marking>> _markings = new();
|
||||
|
||||
// why i didn't encapsulate this in the first place, i won't know
|
||||
[DataField("markings")]
|
||||
public Dictionary<MarkingCategories, List<Marking>> Markings = new();
|
||||
|
||||
/// <summary>
|
||||
/// Marking points for each category.
|
||||
/// </summary>
|
||||
private Dictionary<MarkingCategories, MarkingPoints> _points = new();
|
||||
|
||||
public IReadOnlyList<Marking> this[MarkingCategories category] => _markings[category];
|
||||
[DataField("points")]
|
||||
public Dictionary<MarkingCategories, MarkingPoints> Points = new();
|
||||
|
||||
public MarkingSet()
|
||||
{}
|
||||
|
|
@ -71,7 +70,7 @@ public sealed class MarkingSet
|
|||
return;
|
||||
}
|
||||
|
||||
_points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
|
||||
Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
|
||||
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
|
|
@ -111,7 +110,7 @@ public sealed class MarkingSet
|
|||
/// <param name="other">The other marking set.</param>
|
||||
public MarkingSet(MarkingSet other)
|
||||
{
|
||||
foreach (var (key, list) in other._markings)
|
||||
foreach (var (key, list) in other.Markings)
|
||||
{
|
||||
foreach (var marking in list)
|
||||
{
|
||||
|
|
@ -119,7 +118,7 @@ public sealed class MarkingSet
|
|||
}
|
||||
}
|
||||
|
||||
_points = MarkingPoints.CloneMarkingPointDictionary(other._points);
|
||||
Points = MarkingPoints.CloneMarkingPointDictionary(other.Points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -137,7 +136,7 @@ public sealed class MarkingSet
|
|||
var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
|
||||
var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
|
||||
|
||||
foreach (var (category, list) in _markings)
|
||||
foreach (var (category, list) in Markings)
|
||||
{
|
||||
foreach (var marking in list)
|
||||
{
|
||||
|
|
@ -212,7 +211,7 @@ public sealed class MarkingSet
|
|||
IoCManager.Resolve(ref markingManager);
|
||||
|
||||
var toRemove = new List<int>();
|
||||
foreach (var (category, list) in _markings)
|
||||
foreach (var (category, list) in Markings)
|
||||
{
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
|
|
@ -244,7 +243,7 @@ public sealed class MarkingSet
|
|||
{
|
||||
IoCManager.Resolve(ref markingManager);
|
||||
|
||||
foreach (var (category, points) in _points)
|
||||
foreach (var (category, points) in Points)
|
||||
{
|
||||
if (points.Points <= 0 || points.DefaultMarkings.Count <= 0)
|
||||
{
|
||||
|
|
@ -288,7 +287,7 @@ public sealed class MarkingSet
|
|||
/// <returns>A number equal or greater than zero if the category exists, -1 otherwise.</returns>
|
||||
public int PointsLeft(MarkingCategories category)
|
||||
{
|
||||
if (!_points.TryGetValue(category, out var points))
|
||||
if (!Points.TryGetValue(category, out var points))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -303,7 +302,7 @@ public sealed class MarkingSet
|
|||
/// <param name="marking">The marking instance in question.</param>
|
||||
public void AddFront(MarkingCategories category, Marking marking)
|
||||
{
|
||||
if (!marking.Forced && _points.TryGetValue(category, out var points))
|
||||
if (!marking.Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
if (points.Points <= 0)
|
||||
{
|
||||
|
|
@ -313,10 +312,10 @@ public sealed class MarkingSet
|
|||
points.Points--;
|
||||
}
|
||||
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
markings = new();
|
||||
_markings[category] = markings;
|
||||
Markings[category] = markings;
|
||||
}
|
||||
|
||||
markings.Insert(0, marking);
|
||||
|
|
@ -329,7 +328,7 @@ public sealed class MarkingSet
|
|||
/// <param name="marking"></param>
|
||||
public void AddBack(MarkingCategories category, Marking marking)
|
||||
{
|
||||
if (!marking.Forced && _points.TryGetValue(category, out var points))
|
||||
if (!marking.Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
if (points.Points <= 0)
|
||||
{
|
||||
|
|
@ -339,10 +338,10 @@ public sealed class MarkingSet
|
|||
points.Points--;
|
||||
}
|
||||
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
markings = new();
|
||||
_markings[category] = markings;
|
||||
Markings[category] = markings;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -357,7 +356,7 @@ public sealed class MarkingSet
|
|||
public List<Marking> AddCategory(MarkingCategories category)
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
_markings.Add(category, markings);
|
||||
Markings.Add(category, markings);
|
||||
return markings;
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +368,7 @@ public sealed class MarkingSet
|
|||
/// <param name="marking">The marking to insert.</param>
|
||||
public void Replace(MarkingCategories category, int index, Marking marking)
|
||||
{
|
||||
if (index < 0 || !_markings.TryGetValue(category, out var markings)
|
||||
if (index < 0 || !Markings.TryGetValue(category, out var markings)
|
||||
|| index >= markings.Count)
|
||||
{
|
||||
return;
|
||||
|
|
@ -386,7 +385,7 @@ public sealed class MarkingSet
|
|||
/// <returns>True if removed, false otherwise.</returns>
|
||||
public bool Remove(MarkingCategories category, string id)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -398,7 +397,7 @@ public sealed class MarkingSet
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!markings[i].Forced && _points.TryGetValue(category, out var points))
|
||||
if (!markings[i].Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
points.Points++;
|
||||
}
|
||||
|
|
@ -418,7 +417,7 @@ public sealed class MarkingSet
|
|||
/// <returns>True if removed, false otherwise.</returns>
|
||||
public void Remove(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -428,7 +427,7 @@ public sealed class MarkingSet
|
|||
return;
|
||||
}
|
||||
|
||||
if (!markings[idx].Forced && _points.TryGetValue(category, out var points))
|
||||
if (!markings[idx].Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
points.Points++;
|
||||
}
|
||||
|
|
@ -443,12 +442,12 @@ public sealed class MarkingSet
|
|||
/// <returns>True if removed, false otherwise.</returns>
|
||||
public bool RemoveCategory(MarkingCategories category)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_points.TryGetValue(category, out var points))
|
||||
if (Points.TryGetValue(category, out var points))
|
||||
{
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
|
|
@ -461,7 +460,7 @@ public sealed class MarkingSet
|
|||
}
|
||||
}
|
||||
|
||||
_markings.Remove(category);
|
||||
Markings.Remove(category);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -484,7 +483,7 @@ public sealed class MarkingSet
|
|||
/// <returns>The index of the marking, otherwise a negative number.</returns>
|
||||
public int FindIndexOf(MarkingCategories category, string id)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -502,7 +501,7 @@ public sealed class MarkingSet
|
|||
{
|
||||
markings = null;
|
||||
|
||||
if (_markings.TryGetValue(category, out var list))
|
||||
if (Markings.TryGetValue(category, out var list))
|
||||
{
|
||||
markings = list;
|
||||
return true;
|
||||
|
|
@ -522,7 +521,7 @@ public sealed class MarkingSet
|
|||
{
|
||||
marking = null;
|
||||
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -546,7 +545,7 @@ public sealed class MarkingSet
|
|||
/// <param name="idx">Index of the marking.</param>
|
||||
public void ShiftRankUp(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -566,7 +565,7 @@ public sealed class MarkingSet
|
|||
/// <param name="idx">Index of the marking from the end</param>
|
||||
public void ShiftRankUpFromEnd(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -581,7 +580,7 @@ public sealed class MarkingSet
|
|||
/// <param name="idx">Index of the marking.</param>
|
||||
public void ShiftRankDown(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -601,7 +600,7 @@ public sealed class MarkingSet
|
|||
/// <param name="idx">Index of the marking from the end</param>
|
||||
public void ShiftRankDownFromEnd(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -616,7 +615,7 @@ public sealed class MarkingSet
|
|||
public ForwardMarkingEnumerator GetForwardEnumerator()
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
foreach (var (_, list) in _markings)
|
||||
foreach (var (_, list) in Markings)
|
||||
{
|
||||
markings.AddRange(list);
|
||||
}
|
||||
|
|
@ -632,7 +631,7 @@ public sealed class MarkingSet
|
|||
public ForwardMarkingEnumerator GetForwardEnumerator(MarkingCategories category)
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
if (_markings.TryGetValue(category, out var listing))
|
||||
if (Markings.TryGetValue(category, out var listing))
|
||||
{
|
||||
markings = new(listing);
|
||||
}
|
||||
|
|
@ -647,7 +646,7 @@ public sealed class MarkingSet
|
|||
public ReverseMarkingEnumerator GetReverseEnumerator()
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
foreach (var (_, list) in _markings)
|
||||
foreach (var (_, list) in Markings)
|
||||
{
|
||||
markings.AddRange(list);
|
||||
}
|
||||
|
|
@ -663,7 +662,7 @@ public sealed class MarkingSet
|
|||
public ReverseMarkingEnumerator GetReverseEnumerator(MarkingCategories category)
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
if (_markings.TryGetValue(category, out var listing))
|
||||
if (Markings.TryGetValue(category, out var listing))
|
||||
{
|
||||
markings = new(listing);
|
||||
}
|
||||
|
|
@ -673,8 +672,8 @@ public sealed class MarkingSet
|
|||
|
||||
public bool CategoryEquals(MarkingCategories category, MarkingSet other)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings)
|
||||
|| !other._markings.TryGetValue(category, out var markingsOther))
|
||||
if (!Markings.TryGetValue(category, out var markings)
|
||||
|| !other.Markings.TryGetValue(category, out var markingsOther))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -684,7 +683,7 @@ public sealed class MarkingSet
|
|||
|
||||
public bool Equals(MarkingSet other)
|
||||
{
|
||||
foreach (var (category, _) in _markings)
|
||||
foreach (var (category, _) in Markings)
|
||||
{
|
||||
if (!CategoryEquals(category, other))
|
||||
{
|
||||
|
|
@ -702,7 +701,7 @@ public sealed class MarkingSet
|
|||
/// <returns>Enumerator of marking categories that were different between the two.</returns>
|
||||
public IEnumerable<MarkingCategories> CategoryDifference(MarkingSet other)
|
||||
{
|
||||
foreach (var (category, _) in _markings)
|
||||
foreach (var (category, _) in Markings)
|
||||
{
|
||||
if (!CategoryEquals(category, other))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Shared.Preferences;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Shared.Humanoid.Prototypes;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,4 +12,10 @@ namespace Content.Shared.Humanoid
|
|||
Female,
|
||||
Unsexed,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when entity has changed their sex.
|
||||
/// This doesn't handle gender changes.
|
||||
/// </summary>
|
||||
public record struct SexChangedEvent(Sex OldSex, Sex NewSex);
|
||||
}
|
||||
|
|
|
|||
222
Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
Normal file
222
Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
/// <summary>
|
||||
/// HumanoidSystem. Primarily deals with the appearance and visual data
|
||||
/// of a humanoid entity. HumanoidVisualizer is what deals with actually
|
||||
/// organizing the sprites and setting up the sprite component's layers.
|
||||
///
|
||||
/// This is a shared system, because while it is server authoritative,
|
||||
/// you still need a local copy so that players can set up their
|
||||
/// characters.
|
||||
/// </summary>
|
||||
public abstract class SharedHumanoidAppearanceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
|
||||
public const string DefaultSpecies = "Human";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, ComponentGetState>(OnGetState);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new HumanoidAppearanceState(component.MarkingSet,
|
||||
component.PermanentlyHidden,
|
||||
component.HiddenLayers,
|
||||
component.CustomBaseLayers,
|
||||
component.Sex,
|
||||
component.Gender,
|
||||
component.Age,
|
||||
component.Species,
|
||||
component.SkinColor,
|
||||
component.EyeColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles a humanoid's sprite layer visibility.
|
||||
/// </summary>
|
||||
/// <param name="uid">Humanoid mob's UID</param>
|
||||
/// <param name="layer">Layer to toggle visibility for</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetLayerVisibility(EntityUid uid,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent = false,
|
||||
HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
var dirty = false;
|
||||
SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
if (dirty)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the visibility for multiple layers at once on a humanoid's sprite.
|
||||
/// </summary>
|
||||
/// <param name="uid">Humanoid mob's UID</param>
|
||||
/// <param name="layers">An enumerable of all sprite layers that are going to have their visibility set</param>
|
||||
/// <param name="visible">The visibility state of the layers given</param>
|
||||
/// <param name="permanent">If this is a permanent change, or temporary. Permanent layers are stored in their own hash set.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetLayersVisibility(EntityUid uid, IEnumerable<HumanoidVisualLayers> layers, bool visible, bool permanent = false,
|
||||
HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
var dirty = false;
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
protected virtual void SetLayerVisibility(
|
||||
EntityUid uid,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent,
|
||||
ref bool dirty)
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
if (permanent)
|
||||
dirty |= humanoid.PermanentlyHidden.Remove(layer);
|
||||
|
||||
dirty |= humanoid.HiddenLayers.Remove(layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (permanent)
|
||||
dirty |= humanoid.PermanentlyHidden.Add(layer);
|
||||
|
||||
dirty |= humanoid.HiddenLayers.Add(layer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a humanoid mob's species. This will change their base sprites, as well as their current
|
||||
/// set of markings to fit against the mob's new species.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="species">The species to set the mob to. Will return if the species prototype was invalid.</param>
|
||||
/// <param name="sync">Whether to immediately synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetSpecies(EntityUid uid, string species, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid) || !_prototypeManager.TryIndex<SpeciesPrototype>(species, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.Species = species;
|
||||
humanoid.MarkingSet.FilterSpecies(species, _markingManager);
|
||||
var oldMarkings = humanoid.MarkingSet.GetForwardEnumerator().ToList();
|
||||
humanoid.MarkingSet = new(oldMarkings, prototype.MarkingPoints, _markingManager, _prototypeManager);
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the skin color of this humanoid mob. Will only affect base layers that are not custom,
|
||||
/// custom base layers should use <see cref="SetBaseLayerColor"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="skinColor">Skin color to set on the humanoid mob.</param>
|
||||
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public virtual void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the base layer ID of this humanoid mob. A humanoid mob's 'base layer' is
|
||||
/// the skin sprite that is applied to the mob's sprite upon appearance refresh.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="layer">The layer to target on this humanoid mob.</param>
|
||||
/// <param name="id">The ID of the sprite to use. See <see cref="HumanoidSpeciesSpriteLayer"/>.</param>
|
||||
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetBaseLayerId(EntityUid uid, HumanoidVisualLayers layer, string id, bool sync = true,
|
||||
HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
if (humanoid.CustomBaseLayers.TryGetValue(layer, out var info))
|
||||
humanoid.CustomBaseLayers[layer] = info with { ID = id };
|
||||
else
|
||||
humanoid.CustomBaseLayers[layer] = new(id);
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of this humanoid mob's base layer. See <see cref="SetBaseLayerId"/> for a
|
||||
/// description of how base layers work.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="layer">The layer to target on this humanoid mob.</param>
|
||||
/// <param name="color">The color to set this base layer to.</param>
|
||||
public void SetBaseLayerColor(EntityUid uid, HumanoidVisualLayers layer, Color? color, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
humanoid.CustomBaseLayers[layer] = humanoid.CustomBaseLayers[layer] with { Color = color };
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a humanoid mob's sex. This will not change their gender.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="sex">The sex to set the mob to.</param>
|
||||
/// <param name="sync">Whether to immediately synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetSex(EntityUid uid, Sex sex, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid) || humanoid.Sex == sex)
|
||||
return;
|
||||
|
||||
var oldSex = humanoid.Sex;
|
||||
humanoid.Sex = sex;
|
||||
RaiseLocalEvent(uid, new SexChangedEvent(oldSex, sex));
|
||||
|
||||
if (sync)
|
||||
{
|
||||
Dirty(humanoid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Shared.Humanoid.Markings;
|
||||
using Robust.Shared.Serialization;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ public sealed class HumanoidMarkingModifierBaseLayersSetMessage : BoundUserInter
|
|||
[Serializable, NetSerializable]
|
||||
public sealed class HumanoidMarkingModifierState : BoundUserInterfaceState
|
||||
{
|
||||
// TODO just use the component state, remove the BUI state altogether.
|
||||
public HumanoidMarkingModifierState(MarkingSet markingSet, string species, Color skinColor, Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayers)
|
||||
{
|
||||
MarkingSet = markingSet;
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
/// <summary>
|
||||
/// HumanoidSystem. Primarily deals with the appearance and visual data
|
||||
/// of a humanoid entity. HumanoidVisualizer is what deals with actually
|
||||
/// organizing the sprites and setting up the sprite component's layers.
|
||||
///
|
||||
/// This is a shared system, because while it is server authoritative,
|
||||
/// you still need a local copy so that players can set up their
|
||||
/// characters.
|
||||
/// </summary>
|
||||
public abstract class SharedHumanoidSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public const string DefaultSpecies = "Human";
|
||||
// Corvax-TTS-Start
|
||||
public const string DefaultVoice = "Garithos";
|
||||
public static readonly Dictionary<Sex, string> DefaultSexVoice = new()
|
||||
{
|
||||
{Sex.Male, "Garithos"},
|
||||
{Sex.Female, "Maiev"},
|
||||
{Sex.Unsexed, "Myron"},
|
||||
};
|
||||
// Corvax-TTS-End
|
||||
|
||||
public void SetAppearance(EntityUid uid,
|
||||
string species,
|
||||
Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayer,
|
||||
Color skinColor,
|
||||
Sex sex,
|
||||
List<HumanoidVisualLayers> visLayers,
|
||||
List<Marking> markings)
|
||||
{
|
||||
var data = new HumanoidVisualizerData(species, customBaseLayer, skinColor, sex, visLayers, markings);
|
||||
|
||||
// This should raise a HumanoidAppearanceUpdateEvent, but that requires this component to be made networked and
|
||||
// I cbf doing that atm.
|
||||
_appearance.SetData(uid, HumanoidVisualizerKey.Key, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,5 +51,8 @@ namespace Content.Shared.Medical.SuitSensor
|
|||
public const string NET_IS_ALIVE = "alive";
|
||||
public const string NET_TOTAL_DAMAGE = "vitals";
|
||||
public const string NET_CORDINATES = "cords";
|
||||
|
||||
///Used by the CrewMonitoringServerSystem to send the status of all connected suit sensors to each crew monitor
|
||||
public const string NET_STATUS_COLLECTION = "suit-status-collection";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ namespace Content.Shared.Preferences
|
|||
|
||||
/// <summary>
|
||||
/// Get the default humanoid character profile, using internal constant values.
|
||||
/// Defaults to <see cref="SharedHumanoidSystem.DefaultSpecies"/> for the species.
|
||||
/// Defaults to <see cref="SharedHumanoidAppearanceSystem.DefaultSpecies"/> for the species.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static HumanoidCharacterProfile Default()
|
||||
|
|
@ -111,8 +111,8 @@ namespace Content.Shared.Preferences
|
|||
return new(
|
||||
"John Doe",
|
||||
"",
|
||||
SharedHumanoidSystem.DefaultSpecies,
|
||||
SharedHumanoidSystem.DefaultVoice, // Corvax-TTS
|
||||
SharedHumanoidAppearanceSystem.DefaultSpecies,
|
||||
SharedHumanoidAppearanceSystem.DefaultVoice, // Corvax-TTS
|
||||
18,
|
||||
Sex.Male,
|
||||
Gender.Male,
|
||||
|
|
@ -131,15 +131,15 @@ namespace Content.Shared.Preferences
|
|||
/// <summary>
|
||||
/// Return a default character profile, based on species.
|
||||
/// </summary>
|
||||
/// <param name="species">The species to use in this default profile. The default species is <see cref="SharedHumanoidSystem.DefaultSpecies"/>.</param>
|
||||
/// <param name="species">The species to use in this default profile. The default species is <see cref="SharedHumanoidAppearanceSystem.DefaultSpecies"/>.</param>
|
||||
/// <returns>Humanoid character profile with default settings.</returns>
|
||||
public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies)
|
||||
public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies)
|
||||
{
|
||||
return new(
|
||||
"John Doe",
|
||||
"",
|
||||
species,
|
||||
SharedHumanoidSystem.DefaultVoice, // Corvax-TTS
|
||||
SharedHumanoidAppearanceSystem.DefaultVoice, // Corvax-TTS
|
||||
18,
|
||||
Sex.Male,
|
||||
Gender.Male,
|
||||
|
|
@ -170,7 +170,7 @@ namespace Content.Shared.Preferences
|
|||
return RandomWithSpecies(species);
|
||||
}
|
||||
|
||||
public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies)
|
||||
public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
|
|
|
|||
7
Content.Shared/Spider/IgnoreSpiderWebComponent.cs
Normal file
7
Content.Shared/Spider/IgnoreSpiderWebComponent.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace Content.Shared.Spider;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class IgnoreSpiderWebComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
35
Content.Shared/Spider/SharedSpiderSystem.cs
Normal file
35
Content.Shared/Spider/SharedSpiderSystem.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Spider;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared.Spider;
|
||||
|
||||
public abstract class SharedSpiderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _action = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SpiderWebObjectComponent, ComponentStartup>(OnWebStartup);
|
||||
SubscribeLocalEvent<SpiderComponent, ComponentStartup>(OnSpiderStartup);
|
||||
}
|
||||
|
||||
private void OnSpiderStartup(EntityUid uid, SpiderComponent component, ComponentStartup args)
|
||||
{
|
||||
var netAction = new InstantAction(_proto.Index<InstantActionPrototype>(component.WebActionName));
|
||||
_action.AddAction(uid, netAction, null);
|
||||
}
|
||||
|
||||
private void OnWebStartup(EntityUid uid, SpiderWebObjectComponent component, ComponentStartup args)
|
||||
{
|
||||
_appearance.SetData(uid, SpiderWebVisuals.Variant, _robustRandom.Next(1, 3));
|
||||
}
|
||||
}
|
||||
22
Content.Shared/Spider/SpiderComponent.cs
Normal file
22
Content.Shared/Spider/SpiderComponent.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Spider;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedSpiderSystem))]
|
||||
public sealed class SpiderComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("webPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string WebPrototype = "SpiderWeb";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("webActionName")]
|
||||
public string WebActionName = "SpiderWebAction";
|
||||
}
|
||||
|
||||
public sealed class SpiderWebActionEvent : InstantActionEvent { }
|
||||
9
Content.Shared/Spider/SpiderWebObjectComponent.cs
Normal file
9
Content.Shared/Spider/SpiderWebObjectComponent.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Spider;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedSpiderSystem))]
|
||||
public sealed class SpiderWebObjectComponent : Component
|
||||
{
|
||||
}
|
||||
9
Content.Shared/Spider/SpiderWebVisualsComponent.cs
Normal file
9
Content.Shared/Spider/SpiderWebVisualsComponent.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Spider;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum SpiderWebVisuals
|
||||
{
|
||||
Variant
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Shared.Zombies
|
||||
{
|
||||
|
|
@ -60,5 +63,28 @@ namespace Content.Shared.Zombies
|
|||
/// </summary>
|
||||
[DataField("zombieRoleId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
public readonly string ZombieRoleId = "Zombie";
|
||||
|
||||
/// <summary>
|
||||
/// The EntityName of the humanoid to restore in case of cloning
|
||||
/// </summary>
|
||||
[DataField("beforeZombifiedEntityName"), ViewVariables(VVAccess.ReadOnly)]
|
||||
public string BeforeZombifiedEntityName = String.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The CustomBaseLayers of the humanoid to restore in case of cloning
|
||||
/// </summary>
|
||||
[DataField("beforeZombifiedCustomBaseLayers")]
|
||||
public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> BeforeZombifiedCustomBaseLayers = new ();
|
||||
|
||||
/// <summary>
|
||||
/// The skin color of the humanoid to restore in case of cloning
|
||||
/// </summary>
|
||||
[DataField("beforeZombifiedSkinColor")]
|
||||
public Color BeforeZombifiedSkinColor;
|
||||
|
||||
[DataField("emoteId", customTypeSerializer: typeof(PrototypeIdSerializer<EmoteSoundsPrototype>))]
|
||||
public string? EmoteSoundsId = "Zombie";
|
||||
|
||||
public EmoteSoundsPrototype? EmoteSounds;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
Resources/Audio/Effects/Emotes/attributions.yml
Normal file
15
Resources/Audio/Effects/Emotes/attributions.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
- files:
|
||||
- clap1.ogg
|
||||
- clap2.ogg
|
||||
- clap3.ogg
|
||||
- clap4.ogg
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432"
|
||||
source: "https://github.com/tgstation/tgstation"
|
||||
- files:
|
||||
- snap1.ogg
|
||||
- snap2.ogg
|
||||
- snap3.ogg
|
||||
license: "CC-BY-4.0"
|
||||
copyright: "Finger Snaps Pack by Snapper4298. Converted from WAV to OGG and downmixed to 1 channel."
|
||||
source: "https://freesound.org/people/Snapper4298/packs/11176/"
|
||||
BIN
Resources/Audio/Effects/Emotes/clap1.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/clap2.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/clap3.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap3.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/clap4.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap4.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/snap1.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/snap1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/snap2.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/snap2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/snap3.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/snap3.ogg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue