Merge branch 'master' into 2020-04-28-tool-component

# Conflicts:
#	SpaceStation14.sln.DotSettings
This commit is contained in:
zumorica
2020-05-22 11:31:18 +02:00
56 changed files with 1128 additions and 43 deletions

View File

@@ -101,6 +101,7 @@ namespace Content.Client.GameObjects
ActiveIndex = cast.ActiveIndex;
_gui?.UpdateHandIcons();
RefreshInHands();
}
private void _setHand(string hand, IEntity entity)
@@ -116,7 +117,19 @@ namespace Content.Client.GameObjects
return;
}
var item = entity.GetComponent<ItemComponent>();
SetInHands(hand, entity);
}
private void SetInHands(string hand, IEntity entity)
{
if (entity == null)
{
_sprite.LayerSetVisible($"hand-{hand}", false);
return;
}
if (!entity.TryGetComponent(out ItemComponent item)) return;
var maybeInhands = item.GetInHandStateInfo(hand);
if (!maybeInhands.HasValue)
{
@@ -130,6 +143,16 @@ namespace Content.Client.GameObjects
}
}
public void RefreshInHands()
{
if (!Initialized) return;
foreach (var (hand, entity) in _hands)
{
SetInHands(hand, entity);
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);

View File

@@ -3,8 +3,10 @@ using Content.Shared.GameObjects.Components.Items;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Renderable;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -26,7 +28,13 @@ namespace Content.Client.GameObjects
public string EquippedPrefix
{
get => _equippedPrefix;
set => _equippedPrefix = value;
set
{
_equippedPrefix = value;
if (!ContainerHelpers.TryGetContainer(Owner, out IContainer container)) return;
if(container.Owner.TryGetComponent(out HandsComponent hands))
hands.RefreshInHands();
}
}
public (RSI rsi, RSI.StateId stateId)? GetInHandStateInfo(string hand)

View File

@@ -1,6 +1,11 @@
using Content.Shared.GameObjects.Components.Mobs;
using System;
using Content.Shared.GameObjects.Components.Mobs;
using Microsoft.CodeAnalysis.Completion;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.Components.Animations;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components.Mobs
@@ -11,19 +16,51 @@ namespace Content.Client.GameObjects.Components.Mobs
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData<SharedSpeciesComponent.MobState>(SharedSpeciesComponent.MobVisuals.RotationState, out var state))
{
switch (state)
{
case SharedSpeciesComponent.MobState.Stand:
sprite.Rotation = 0;
case SharedSpeciesComponent.MobState.Standing:
SetRotation(component, 0);
break;
case SharedSpeciesComponent.MobState.Down:
sprite.Rotation = Angle.FromDegrees(90);
SetRotation(component, Angle.FromDegrees(90));
break;
}
}
}
private void SetRotation(AppearanceComponent component, Angle rotation)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!sprite.Owner.TryGetComponent(out AnimationPlayerComponent animation))
{
sprite.Rotation = rotation;
return;
}
if (animation.HasRunningAnimation("rotate"))
animation.Stop("rotate");
animation.Play(new Animation
{
Length = TimeSpan.FromSeconds(0.125),
AnimationTracks =
{
new AnimationTrackComponentProperty
{
ComponentType = typeof(ISpriteComponent),
Property = nameof(ISpriteComponent.Rotation),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(sprite.Rotation, 0),
new AnimationTrackProperty.KeyFrame(rotation, 0.125f)
}
}
}
}, "rotate");
}
}
}

View File

@@ -14,5 +14,6 @@ namespace Content.Client.Interfaces.GameObjects
void AttackByInHand(string index);
void UseActiveHand();
void ActivateItemInHand(string handIndex);
void RefreshInHands();
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects;
using Robust.Server.GameObjects.Components.Container;
@@ -129,6 +130,9 @@ namespace Content.Server.GameObjects
{
var pass = false;
if (!ActionBlockerSystem.CanEquip(Owner))
return false;
if (item is ClothingComponent clothing)
{
if (clothing.SlotFlags != SlotFlags.PREVENTEQUIP && (clothing.SlotFlags & SlotMasks[slot]) != 0)
@@ -185,6 +189,9 @@ namespace Content.Server.GameObjects
/// </returns>
public bool CanUnequip(Slots slot)
{
if (!ActionBlockerSystem.CanUnequip(Owner))
return false;
var InventorySlot = SlotContainers[slot];
return InventorySlot.ContainedEntity != null && InventorySlot.CanRemove(InventorySlot.ContainedEntity);
}

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.Interfaces.Random;
@@ -48,8 +49,8 @@ namespace Content.Server.GameObjects
}
set
{
Dirty();
_equippedPrefix = value;
Dirty();
}
}

View File

@@ -274,7 +274,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
(_currentCookTimerTime == (uint)recipeToCook.CookTime) ? true : false;
SetAppearance(MicrowaveVisualState.Cooking);
_audioSystem.Play(_startCookingSound, AudioParams.Default);
_audioSystem.Play(_startCookingSound,Owner, AudioParams.Default);
Timer.Spawn((int)(_currentCookTimerTime * _cookTimeMultiplier), () =>
{
@@ -290,7 +290,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
var entityToSpawn = goodMeal ? recipeToCook.Result : _badRecipeName;
_entityManager.SpawnEntity(entityToSpawn, Owner.Transform.GridPosition);
_audioSystem.Play(_cookingCompleteSound, AudioParams.Default);
_audioSystem.Play(_cookingCompleteSound,Owner, AudioParams.Default);
SetAppearance(MicrowaveVisualState.Idle);
_busy = false;
UpdateUserInterface();
@@ -395,7 +395,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
private void ClickSound()
{
_audioSystem.Play("/Audio/machines/machine_switch.ogg", AudioParams.Default.WithVolume(-2f));
_audioSystem.Play("/Audio/machines/machine_switch.ogg",Owner, AudioParams.Default.WithVolume(-2f));
}

View File

@@ -1,8 +1,14 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Mobs;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects
{
@@ -77,6 +83,21 @@ namespace Content.Server.GameObjects
{
return true;
}
bool IActionBlocker.CanEquip()
{
return true;
}
bool IActionBlocker.CanUnequip()
{
return true;
}
bool IActionBlocker.CanChangeDirection()
{
return true;
}
}
/// <summary>
@@ -86,10 +107,15 @@ namespace Content.Server.GameObjects
{
public void EnterState(IEntity entity)
{
if(entity.TryGetComponent(out StunnableComponent stun))
stun.CancelAll();
StandingStateHelper.Down(entity);
}
public void ExitState(IEntity entity)
{
StandingStateHelper.Standing(entity);
}
public bool IsConscious => false;
@@ -138,6 +164,21 @@ namespace Content.Server.GameObjects
{
return false;
}
bool IActionBlocker.CanEquip()
{
return false;
}
bool IActionBlocker.CanUnequip()
{
return false;
}
bool IActionBlocker.CanChangeDirection()
{
return true;
}
}
/// <summary>
@@ -147,11 +188,10 @@ namespace Content.Server.GameObjects
{
public void EnterState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
var newState = SharedSpeciesComponent.MobState.Down;
appearance.SetData(SharedSpeciesComponent.MobVisuals.RotationState, newState);
}
if(entity.TryGetComponent(out StunnableComponent stun))
stun.CancelAll();
StandingStateHelper.Down(entity, playSound:false);
if (entity.TryGetComponent(out CollidableComponent collidable))
{
@@ -161,11 +201,7 @@ namespace Content.Server.GameObjects
public void ExitState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
var newState = SharedSpeciesComponent.MobState.Stand;
appearance.SetData(SharedSpeciesComponent.MobVisuals.RotationState, newState);
}
StandingStateHelper.Standing(entity);
if (entity.TryGetComponent(out CollidableComponent collidable))
{
@@ -219,5 +255,20 @@ namespace Content.Server.GameObjects
{
return false;
}
bool IActionBlocker.CanEquip()
{
return false;
}
bool IActionBlocker.CanUnequip()
{
return false;
}
bool IActionBlocker.CanChangeDirection()
{
return false;
}
}
}

View File

@@ -0,0 +1,332 @@
using System;
using System.Threading;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Mobs;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timers;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Mobs
{
[RegisterComponent]
public class StunnableComponent : Component, IActionBlocker, IAttackHand, IMoveSpeedModifier
{
public override string Name => "Stunnable";
#pragma warning disable 649
[Dependency] private IEntitySystemManager _entitySystemManager;
[Dependency] private IGameTiming _gameTiming;
#pragma warning restore 649
private TimeSpan? _lastStun;
[ViewVariables]
public TimeSpan? StunStart => _lastStun;
[ViewVariables]
public TimeSpan? StunEnd => _lastStun == null
? (TimeSpan?) null
: _gameTiming.CurTime + TimeSpan.FromSeconds(_stunnedTimer + _knockdownTimer + _slowdownTimer);
private const int StunLevels = 8;
private bool _canHelp = true;
private float _stunCap = 20f;
private float _knockdownCap = 20f;
private float _slowdownCap = 20f;
private float _helpKnockdownRemove = 1f;
private float _helpInterval = 1f;
private float _stunnedTimer = 0f;
private float _knockdownTimer = 0f;
private float _slowdownTimer = 0f;
private float _walkModifierOverride = 0f;
private float _runModifierOverride = 0f;
private readonly string[] _texturesStunOverlay = new string[StunLevels];
[ViewVariables] public bool Stunned => _stunnedTimer > 0f;
[ViewVariables] public bool KnockedDown => _knockdownTimer > 0f;
[ViewVariables] public bool SlowedDown => _slowdownTimer > 0f;
[ViewVariables] public float StunCap => _stunCap;
[ViewVariables] public float KnockdownCap => _knockdownCap;
[ViewVariables] public float SlowdownCap => _slowdownCap;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _stunCap, "stunCap", 20f);
serializer.DataField(ref _knockdownCap, "knockdownCap", 20f);
serializer.DataField(ref _slowdownCap, "slowdownCap", 20f);
serializer.DataField(ref _helpInterval, "helpInterval", 1f);
serializer.DataField(ref _helpKnockdownRemove, "helpKnockdownRemove", 1f);
}
public override void Initialize()
{
base.Initialize();
for (var i = 0; i < StunLevels; i++)
{
_texturesStunOverlay[i] = $"/Textures/UserInterface/Inventory/cooldown-{i}.png";
}
}
/// <summary>
/// Stuns the entity, disallowing it from doing many interactions temporarily.
/// </summary>
/// <param name="seconds">How many seconds the mob will stay stunned</param>
public void Stun(float seconds)
{
seconds = MathF.Min(_stunnedTimer + (seconds * StunTimeModifier), _stunCap);
if (seconds <= 0f)
return;
StandingStateHelper.DropAllItemsInHands(Owner);
_stunnedTimer = seconds;
_lastStun = _gameTiming.CurTime;
}
/// <summary>
/// Knocks down the mob, making it fall to the ground.
/// </summary>
/// <param name="seconds">How many seconds the mob will stay on the ground</param>
public void Knockdown(float seconds)
{
seconds = MathF.Min(_knockdownTimer + (seconds * KnockdownTimeModifier), _knockdownCap);
if (seconds <= 0f)
return;
StandingStateHelper.Down(Owner);
_knockdownTimer = seconds;
_lastStun = _gameTiming.CurTime;
}
/// <summary>
/// Applies knockdown and stun to the mob temporarily
/// </summary>
/// <param name="seconds">How many seconds the mob will be paralyzed</param>
public void Paralyze(float seconds)
{
Stun(seconds);
Knockdown(seconds);
}
/// <summary>
/// Slows down the mob's walking/running speed temporarily
/// </summary>
/// <param name="seconds">How many seconds the mob will be slowed down</param>
/// <param name="walkModifierOverride">Walk speed modifier. Set to 0 or negative for default value. (0.5f)</param>
/// <param name="runModifierOverride">Run speed modifier. Set to 0 or negative for default value. (0.5f)</param>
public void Slowdown(float seconds, float walkModifierOverride = 0f, float runModifierOverride = 0f)
{
seconds = MathF.Min(_slowdownTimer + (seconds * SlowdownTimeModifier), _slowdownCap);
if (seconds <= 0f)
return;
_walkModifierOverride = walkModifierOverride;
_runModifierOverride = runModifierOverride;
_slowdownTimer = seconds;
_lastStun = _gameTiming.CurTime;
if(Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
movement.RefreshMovementSpeedModifiers();
}
/// <summary>
/// Used when
/// </summary>
public void CancelAll()
{
_knockdownTimer = 0f;
_stunnedTimer = 0f;
}
public bool AttackHand(AttackHandEventArgs eventArgs)
{
if (!_canHelp || !KnockedDown)
return false;
_canHelp = false;
Timer.Spawn(((int)_helpInterval*1000), () => _canHelp = true);
_entitySystemManager.GetEntitySystem<AudioSystem>()
.Play("/Audio/effects/thudswoosh.ogg", Owner, AudioHelpers.WithVariation(0.25f));
_knockdownTimer -= _helpKnockdownRemove;
return true;
}
public void Update(float delta)
{
if (Stunned)
{
_stunnedTimer -= delta;
if (_stunnedTimer <= 0)
{
_stunnedTimer = 0f;
}
}
if (KnockedDown)
{
_knockdownTimer -= delta;
if (_knockdownTimer <= 0f)
{
StandingStateHelper.Standing(Owner);
_knockdownTimer = 0f;
}
}
if (SlowedDown)
{
_slowdownTimer -= delta;
if (_slowdownTimer <= 0f)
{
_slowdownTimer = 0f;
if(Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
movement.RefreshMovementSpeedModifiers();
}
}
if (!_lastStun.HasValue || !StunEnd.HasValue || !Owner.TryGetComponent(out ServerStatusEffectsComponent status))
return;
var start = _lastStun.Value;
var end = StunEnd.Value;
var length = (end - start).TotalSeconds;
var progress = (_gameTiming.CurTime - start).TotalSeconds;
var ratio = (float)(progress / length);
var textureIndex = CalculateStunLevel(ratio);
if (textureIndex == StunLevels)
{
_lastStun = null;
status.RemoveStatus(StatusEffect.Stun);
}
else
{
status.ChangeStatus(StatusEffect.Stun, _texturesStunOverlay[textureIndex]);
}
}
private static int CalculateStunLevel(float stunValue)
{
var val = stunValue.Clamp(0, 1);
val *= StunLevels;
return (int)Math.Floor(val);
}
#region ActionBlockers
public bool CanMove() => (!Stunned);
public bool CanInteract() => (!Stunned);
public bool CanUse() => (!Stunned);
public bool CanThrow() => (!Stunned);
public bool CanSpeak() => true;
public bool CanDrop() => (!Stunned);
public bool CanPickup() => (!Stunned);
public bool CanEmote() => true;
public bool CanAttack() => (!Stunned);
public bool CanEquip() => (!Stunned);
public bool CanUnequip() => (!Stunned);
public bool CanChangeDirection() => true;
#endregion
public float StunTimeModifier
{
get
{
var modifier = 1.0f;
var components = Owner.GetAllComponents<IStunModifier>();
foreach (var component in components)
{
modifier *= component.StunTimeModifier;
}
return modifier;
}
}
public float KnockdownTimeModifier
{
get
{
var modifier = 1.0f;
var components = Owner.GetAllComponents<IStunModifier>();
foreach (var component in components)
{
modifier *= component.KnockdownTimeModifier;
}
return modifier;
}
}
public float SlowdownTimeModifier
{
get
{
var modifier = 1.0f;
var components = Owner.GetAllComponents<IStunModifier>();
foreach (var component in components)
{
modifier *= component.SlowdownTimeModifier;
}
return modifier;
}
}
public float WalkSpeedModifier => (SlowedDown ? (_walkModifierOverride <= 0f ? 0.5f : _walkModifierOverride) : 1f);
public float SprintSpeedModifier => (SlowedDown ? (_runModifierOverride <= 0f ? 0.5f : _runModifierOverride) : 1f);
}
/// <summary>
/// This interface allows components to multiply the time in seconds of various stuns by a number.
/// </summary>
public interface IStunModifier
{
float StunTimeModifier => 1.0f;
float KnockdownTimeModifier => 1.0f;
float SlowdownTimeModifier => 1.0f;
}
}

View File

@@ -65,7 +65,7 @@ namespace Content.Server.GameObjects.Components.Power
{
_supply = value;
var node = Owner.GetComponent<PowerNodeComponent>();
node.Parent.UpdateGenerator(this);
node?.Parent?.UpdateGenerator(this);
}
/// <summary>

View File

@@ -0,0 +1,91 @@
using System;
using Content.Server.GameObjects.Components.Damage;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// This is a solar panel.
/// It generates power from the sun based on coverage.
/// </summary>
[RegisterComponent]
public class SolarPanelComponent : Component, IBreakAct
{
public override string Name => "SolarPanel";
private PowerGeneratorComponent _powerGenerator;
/// <summary>
/// Maximum supply output by this panel (coverage = 1)
/// </summary>
private float _maxSupply = 1500;
[ViewVariables(VVAccess.ReadWrite)]
public float MaxSupply
{
get => _maxSupply;
set {
_maxSupply = value;
UpdateSupply();
}
}
/// <summary>
/// Current coverage of this panel (from 0 to 1).
/// This is updated by <see cref='PowerSolarSystem'/>.
/// </summary>
private float _coverage = 0;
[ViewVariables]
public float Coverage
{
get => _coverage;
set {
// This gets updated once-per-tick, so avoid updating it if truly unnecessary
if (_coverage != value) {
_coverage = value;
UpdateSupply();
}
}
}
private void UpdateSupply()
{
if (_powerGenerator != null)
_powerGenerator.Supply = _maxSupply * _coverage;
}
public override void Initialize()
{
base.Initialize();
_powerGenerator = Owner.GetComponent<PowerGeneratorComponent>();
UpdateSupply();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxSupply, "maxsupply", 1500);
}
public void OnBreak(BreakageEventArgs args)
{
var sprite = Owner.GetComponent<SpriteComponent>();
sprite.LayerSetState(0, "broken");
MaxSupply = 0;
}
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
@@ -33,7 +34,7 @@ namespace Content.Server.GameObjects.Components.Research
private const string _soundCollectionName = "keyboard";
private bool Powered => _powerDevice.Powered;
public override void Initialize()
{
base.Initialize();
@@ -119,7 +120,7 @@ namespace Content.Server.GameObjects.Components.Research
var soundCollection = _prototypeManager.Index<SoundCollectionPrototype>(_soundCollectionName);
var file = _random.Pick(soundCollection.PickFiles);
var audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
audioSystem.Play(file);
audioSystem.Play(file,Owner,AudioParams.Default);
}

View File

@@ -75,6 +75,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
serializer.DataField(ref _cooldownTime, "cooldownTime", 1f);
}
public virtual bool OnHitEntities(IReadOnlyList<IEntity> entities)
{
return false;
}
void IAttack.Attack(AttackEventArgs eventArgs)
{
var curTime = IoCManager.Resolve<IGameTiming>().CurTime;
@@ -101,6 +106,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
}
}
if(OnHitEntities(hitEntities)) return;
var audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
var emitter = hitEntities.Count == 0 ? eventArgs.User : hitEntities[0];
audioSystem.Play(hitEntities.Count > 0 ? _hitSound : "/Audio/weapons/punchmiss.ogg", emitter);

View File

@@ -0,0 +1,93 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Weapon.Melee
{
[RegisterComponent]
public class StunbatonComponent : MeleeWeaponComponent, IUse
{
[Dependency] private IRobustRandom _robustRandom;
[Dependency] private IEntitySystemManager _entitySystemManager;
public override string Name => "Stunbaton";
private bool _activated = false;
[ViewVariables(VVAccess.ReadWrite)]
private float _paralyzeChance = 0.25f;
[ViewVariables(VVAccess.ReadWrite)]
private float _paralyzeTime = 10f;
[ViewVariables(VVAccess.ReadWrite)]
private float _slowdownTime = 5f;
[ViewVariables]
public bool Activated => _activated;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _paralyzeChance, "paralyzeChance", 0.25f);
serializer.DataField(ref _paralyzeTime, "paralyzeTime", 10f);
serializer.DataField(ref _slowdownTime, "slowdownTime", 5f);
}
public override bool OnHitEntities(IReadOnlyList<IEntity> entities)
{
if (!Activated || entities.Count == 0)
return false;
_entitySystemManager.GetEntitySystem<AudioSystem>()
.Play("/Audio/weapons/egloves.ogg", Owner.Transform.GridPosition, AudioHelpers.WithVariation(0.25f));
foreach (var entity in entities)
{
if (!entity.TryGetComponent(out StunnableComponent stunnable)) continue;
if(_robustRandom.Prob(_paralyzeChance))
stunnable.Paralyze(_paralyzeTime);
else
stunnable.Slowdown(_slowdownTime);
}
return false;
}
public bool UseEntity(UseEntityEventArgs eventArgs)
{
var sprite = Owner.GetComponent<SpriteComponent>();
var item = Owner.GetComponent<ItemComponent>();
if (_activated)
{
item.EquippedPrefix = "off";
sprite.LayerSetState(0, "stunbaton_off");
_activated = false;
}
else
{
_entitySystemManager.GetEntitySystem<AudioSystem>()
.Play(AudioHelpers.GetRandomFileFromSoundCollection("sparks"), Owner.Transform.GridPosition, AudioHelpers.WithVariation(0.25f));
item.EquippedPrefix = "on";
sprite.LayerSetState(0, "stunbaton_on");
_activated = true;
}
return true;
}
}
}

View File

@@ -22,6 +22,9 @@ namespace Content.Server.GameObjects.EntitySystems
bool CanEmote() => true;
bool CanAttack() => true;
bool CanEquip() => true;
bool CanUnequip() => true;
bool CanChangeDirection() => true;
}
public class ActionBlockerSystem : EntitySystem
@@ -119,5 +122,41 @@ namespace Content.Server.GameObjects.EntitySystems
return canattack;
}
public static bool CanEquip(IEntity entity)
{
bool canequip = true;
foreach (var actionblockercomponents in entity.GetAllComponents<IActionBlocker>())
{
canequip &= actionblockercomponents.CanEquip();
}
return canequip;
}
public static bool CanUnequip(IEntity entity)
{
bool canunequip = true;
foreach (var actionblockercomponents in entity.GetAllComponents<IActionBlocker>())
{
canunequip &= actionblockercomponents.CanUnequip();
}
return canunequip;
}
public static bool CanChangeDirection(IEntity entity)
{
bool canchangedirection = true;
foreach (var actionblockercomponents in entity.GetAllComponents<IActionBlocker>())
{
canchangedirection &= actionblockercomponents.CanChangeDirection();
}
return canchangedirection;
}
}
}

View File

@@ -454,13 +454,14 @@ namespace Content.Server.GameObjects.EntitySystems
var item = hands.GetActiveHand?.Owner;
if(ActionBlockerSystem.CanChangeDirection(player))
playerTransform.LocalRotation = new Angle(coordinates.ToMapPos(_mapManager) - playerTransform.MapPosition.Position);
if (!ActionBlockerSystem.CanInteract(player))
{
return;
}
playerTransform.LocalRotation = new Angle(coordinates.ToMapPos(_mapManager) - playerTransform.MapPosition.Position);
// TODO: Check if client should be able to see that object to click on it in the first place
// Clicked on empty space behavior, try using ranged attack

View File

@@ -0,0 +1,65 @@
using Content.Server.GameObjects.Components.Power;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Maths;
using System;
namespace Content.Server.GameObjects.EntitySystems
{
/// <summary>
/// Responsible for maintaining the solar-panel sun angle and updating <see cref='SolarPanelComponent'/> coverage.
/// </summary>
[UsedImplicitly]
public class PowerSolarSystem: EntitySystem
{
/// <summary>
/// The current sun angle.
/// </summary>
public Angle TowardsSun = Angle.South;
public override void Initialize()
{
EntityQuery = new TypeEntityQuery(typeof(SolarPanelComponent));
}
public override void Update(float frameTime)
{
TowardsSun += Angle.FromDegrees(frameTime / 10);
TowardsSun = TowardsSun.Reduced();
foreach (var entity in RelevantEntities)
{
// In the 'sunRelative' coordinate system:
// the sun is considered to be an infinite distance directly up.
// this is the rotation of the panel relative to that.
// directly upwards (theta = 0) = coverage 1
// left/right 90 degrees (abs(theta) = (pi / 2)) = coverage 0
// directly downwards (abs(theta) = pi) = coverage -1
// as TowardsSun + = CCW,
// panelRelativeToSun should - = CW
var panelRelativeToSun = entity.Transform.WorldRotation - TowardsSun;
// essentially, given cos = X & sin = Y & Y is 'downwards',
// then for the first 90 degrees of rotation in either direction,
// this plots the lower-right quadrant of a circle.
// now basically assume a line going from the negated X/Y to there,
// and that's the hypothetical solar panel.
//
// since, again, the sun is considered to be an infinite distance upwards,
// this essentially means Cos(panelRelativeToSun) is half of the cross-section,
// and since the full cross-section has a max of 2, effectively-halving it is fine.
//
// as for when it goes negative, it only does that when (abs(theta) > pi)
// and that's expected behavior.
float coverage = (float) Math.Max(0, Math.Cos(panelRelativeToSun));
// Would determine occlusion, but that requires raytraces.
// And I'm not sure where those are in the codebase.
// Luckily, auto-rotation isn't in yet, so it won't matter anyway.
// Total coverage calculated; apply it to the panel.
var panel = entity.GetComponent<SolarPanelComponent>();
panel.Coverage = coverage;
}
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.EntitySystems
{
public class StunSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
EntityQuery = new TypeEntityQuery(typeof(StunnableComponent));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var entity in RelevantEntities)
{
entity.GetComponent<StunnableComponent>().Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,74 @@
using Content.Server.Interfaces.GameObjects;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Mobs
{
public static class StandingStateHelper
{
/// <summary>
/// Set's the mob standing state to down.
/// </summary>
/// <param name="entity">The mob in question</param>
/// <param name="playSound">Whether to play a sound when falling down or not</param>
/// <param name="dropItems">Whether to make the mob drop all the items on his hands</param>
/// <returns>False if the mob was already downed or couldn't set the state</returns>
public static bool Down(IEntity entity, bool playSound = true, bool dropItems = true)
{
if (!entity.TryGetComponent(out AppearanceComponent appearance)) return false;
appearance.TryGetData<SharedSpeciesComponent.MobState>(SharedSpeciesComponent.MobVisuals.RotationState, out var oldState);
var newState = SharedSpeciesComponent.MobState.Down;
if (newState == oldState)
return false;
appearance.SetData(SharedSpeciesComponent.MobVisuals.RotationState, newState);
if (playSound)
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>()
.Play(AudioHelpers.GetRandomFileFromSoundCollection("bodyfall"), entity, AudioHelpers.WithVariation(0.25f));
if(dropItems)
DropAllItemsInHands(entity);
return true;
}
/// <summary>
/// Sets the mob's standing state to standing.
/// </summary>
/// <param name="entity">The mob in question.</param>
/// <returns>False if the mob was already standing or couldn't set the state</returns>
public static bool Standing(IEntity entity)
{
if (!entity.TryGetComponent(out AppearanceComponent appearance)) return false;
appearance.TryGetData<SharedSpeciesComponent.MobState>(SharedSpeciesComponent.MobVisuals.RotationState, out var oldState);
var newState = SharedSpeciesComponent.MobState.Standing;
if (newState == oldState)
return false;
appearance.SetData(SharedSpeciesComponent.MobVisuals.RotationState, newState);
return true;
}
public static void DropAllItemsInHands(IEntity entity)
{
if (!entity.TryGetComponent(out IHandsComponent hands)) return;
foreach (var heldItem in hands.GetAllHeldItems())
{
hands.Drop(heldItem.Owner);
}
}
}
}

View File

@@ -2,9 +2,12 @@ using System;
using Content.Shared.GameObjects.Components.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared.Audio
@@ -18,5 +21,11 @@ namespace Content.Shared.Audio
var scale = (float)(IoCManager.Resolve<IRobustRandom>().NextGaussian(1, amplitude));
return AudioParams.Default.WithPitchScale(scale);
}
public static string GetRandomFileFromSoundCollection(string name)
{
var soundCollection = IoCManager.Resolve<IPrototypeManager>().Index<SoundCollectionPrototype>(name);
return IoCManager.Resolve<IRobustRandom>().Pick(soundCollection.PickFiles);
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
/// <summary>
/// Mob is standing up
/// </summary>
Stand,
Standing,
/// <summary>
/// Mob is laying down

View File

@@ -32,5 +32,6 @@ namespace Content.Shared.GameObjects.Components.Mobs
Health,
Hunger,
Thirst,
Stun,
}
}

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
- type: entity
parent: airlock_engineering
id: airlock_engineering_locked
name: Engineering Airlock
suffix: Locked
components:
- type: AccessReader
required: ["engineering"]

View File

@@ -40,16 +40,6 @@
- type: Icon
sprite: Buildings/Doors/airlock_engineering_glass.rsi
- type: entity
parent: airlock_engineering
id: airlock_engineering_locked
name: Locked Engineering Airlock
components:
- type: AccessReader
required: ["engineering"]
- type: entity
parent: Airlock
id: airlock_medical

View File

@@ -47,7 +47,7 @@
id: MinimoogInstrument
components:
- type: Instrument
program: 7
program: 81
- type: Sprite
sprite: Objects/Instruments/otherinstruments.rsi
state: minimoog

View File

@@ -62,6 +62,35 @@
- type: SnapGrid
offset: Center
- type: entity
id: SolarPanel
name: Solar Panel
description: Generates power from sunlight. Usually used to power replacements for sunlight. Fragile.
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
shapes:
- !type:PhysShapeAabb
layer: 31
- type: Sprite
sprite: Buildings/solar_panel.rsi
state: normal
- type: Icon
sprite: Buildings/solar_panel.rsi
state: normal
- type: PowerGenerator
- type: SolarPanel
supply: 1500
- type: Rotatable
- type: SnapGrid
offset: Center
- type: Damageable
- type: Breakable
thresholdvalue: 100
- type: entity
id: WPPnobattery
name: WPPnobattery

View File

@@ -17,7 +17,7 @@
id: SynthesizerInstrument
components:
- type: Instrument
program: 2
program: 62
- type: Sprite
sprite: Objects/Instruments/h_synthesizer.rsi
state: icon

View File

@@ -0,0 +1,25 @@
- type: entity
name: Stun baton
parent: BaseItem
id: Stunbaton
components:
- type: Sprite
sprite: Objects/Melee/stunbaton.rsi
state: stunbaton_off
- type: Icon
sprite: Objects/Melee/stunbaton.rsi
state: stunbaton_off
- type: Stunbaton
damage: 1
range: 0.75
arcwidth: 0
arc: default
- type: Item
Size: 10
sprite: Objects/Melee/stunbaton.rsi
HeldPrefix: off
- type: ItemCooldown

View File

@@ -131,6 +131,8 @@
- type: FootstepSound
- type: HumanoidAppearance
- type: HumanInventoryController
- type: Stunnable
- type: AnimationPlayer
- type: entity
save: false

View File

@@ -2,13 +2,14 @@
parent: ReagentItem
id: WaterTank
name: Water Tank
suffix: Empty
description: "A water tank. It is used to store high amounts of water."
components:
- type: Sprite
texture: Buildings/WaterTank.png
texture: Buildings/watertank.png
- type: Icon
texture: Buildings/WaterTank.png
texture: Buildings/watertank.png
- type: Clickable
- type: InteractionOutline
@@ -37,6 +38,7 @@
- type: entity
parent: WaterTank
id: WaterTankFull
suffix: Full
components:
- type: Solution
contents:

View File

@@ -0,0 +1,7 @@
- type: sound_collection
id: bodyfall
files:
- /Audio/effects/bodyfall1.ogg
- /Audio/effects/bodyfall2.ogg
- /Audio/effects/bodyfall3.ogg
- /Audio/effects/bodyfall4.ogg

View File

@@ -0,0 +1,7 @@
- type: sound_collection
id: sparks
files:
- /Audio/effects/sparks1.ogg
- /Audio/effects/sparks2.ogg
- /Audio/effects/sparks3.ogg
- /Audio/effects/sparks4.ogg

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

View File

@@ -0,0 +1 @@
{"version":1,"license":"CC-BY-SA-3.0","copyright":"Taken from https://github.com/discordia-space/CEV-Eris/blob/d1e0161af146835f4fb79d21a6200caa9cc842d0/icons/obj/power.dmi and modified.","size":{"x":32,"y":32},"states":[{"name":"normal","select":[],"flags":{},"directions":8},{"name":"broken","select":[],"flags":{},"directions":1}]}

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

View File

@@ -0,0 +1,143 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC BY-SA 3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at b8758256b31013946fdbd320ca043a663c399656",
"states":
[
{
"name": "off-inhand-left",
"directions": 4,
"delays": [
[
1.0
],
[
1.0
],
[
1.0
],
[
1.0
]
]
},
{
"name": "off-inhand-right",
"directions": 4,
"delays": [
[
1.0
],
[
1.0
],
[
1.0
],
[
1.0
]
]
},
{
"name": "on-inhand-left",
"directions": 4,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
],
[
0.1,
0.1,
0.1,
0.1,
],
[
0.1,
0.1,
0.1,
0.1,
],
[
0.1,
0.1,
0.1,
0.1,
],
]
},
{
"name": "on-inhand-right",
"directions": 4,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
],
[
0.1,
0.1,
0.1,
0.1,
],
[
0.1,
0.1,
0.1,
0.1,
],
[
0.1,
0.1,
0.1,
0.1,
],
]
},
{
"name": "stunbaton_off",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "stunbaton_nocell",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "stunbaton_on",
"directions": 1,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -58,3 +58,5 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=swsl/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=underplating/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Wirecutter/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Stunnable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=swsl/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>