Merge branch 'master' into 20-06-24-movement-prediction

This commit is contained in:
Pieter-Jan Briers
2020-06-24 04:04:43 +02:00
2259 changed files with 16436 additions and 11772 deletions

View File

@@ -54,8 +54,9 @@ namespace Content.Client.Commands
} }
return !anyAction; return !anyAction;
#endif #else
return true; return true;
#endif
} }
} }
} }

View File

@@ -55,8 +55,9 @@ namespace Content.Client.Commands
} }
return !anyAction; return !anyAction;
#endif #else
return true; return true;
#endif
} }
} }
} }

View File

@@ -0,0 +1,24 @@
using Robust.Client.Interfaces.Console;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.IoC;
namespace Content.Client.Commands
{
public class ToggleOutlineCommand : IConsoleCommand
{
public string Command => "toggleoutline";
public string Description => "Toggles outline drawing on entities.";
public string Help => "";
public bool Execute(IDebugConsole console, params string[] args)
{
var _configurationManager = IoCManager.Resolve<IConfigurationManager>();
var old = _configurationManager.GetCVar<bool>("outline.enabled");
_configurationManager.SetCVar("outline.enabled", !old);
console.AddLine($"Draw outlines set to: {_configurationManager.GetCVar<bool>("outline.enabled")}");
return false;
}
}
}

View File

@@ -24,6 +24,7 @@ using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.State; using Robust.Client.Interfaces.State;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -41,6 +42,7 @@ namespace Content.Client
[Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner; [Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner;
[Dependency] private readonly IGameController _gameController; [Dependency] private readonly IGameController _gameController;
[Dependency] private readonly IStateManager _stateManager; [Dependency] private readonly IStateManager _stateManager;
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649 #pragma warning restore 649
public override void Init() public override void Init()
@@ -85,8 +87,6 @@ namespace Content.Client
"Multitool", "Multitool",
"Wrench", "Wrench",
"Crowbar", "Crowbar",
"HitscanWeapon",
"ProjectileWeapon",
"Projectile", "Projectile",
"MeleeWeapon", "MeleeWeapon",
"Storeable", "Storeable",
@@ -100,8 +100,8 @@ namespace Content.Client
"LightBulb", "LightBulb",
"Healing", "Healing",
"Catwalk", "Catwalk",
"BallisticMagazine", "RangedMagazine",
"BallisticBullet", "Ammo",
"HitscanWeaponCapacitor", "HitscanWeaponCapacitor",
"PowerCell", "PowerCell",
"WeaponCapacitorCharger", "WeaponCapacitorCharger",
@@ -144,6 +144,13 @@ namespace Content.Client
"Bucket", "Bucket",
"Puddle", "Puddle",
"CanSpill", "CanSpill",
"SpeedLoader",
"Hitscan",
"BoltActionBarrel",
"PumpBarrel",
"RevolverBarrel",
"ExplosiveProjectile",
"StunnableProjectile",
"RandomPottedPlant", "RandomPottedPlant",
"CommunicationsConsole", "CommunicationsConsole",
"BarSign", "BarSign",
@@ -162,6 +169,10 @@ namespace Content.Client
"SecureEntityStorage", "SecureEntityStorage",
"PresetIdCard", "PresetIdCard",
"SolarControlConsole", "SolarControlConsole",
"BatteryBarrel",
"FlashExplosive",
"FlashProjectile",
"Utensil",
}; };
foreach (var ignoreName in registerIgnore) foreach (var ignoreName in registerIgnore)
@@ -209,6 +220,8 @@ namespace Content.Client
{ {
IoCManager.Resolve<IMapManager>().CreateNewMapEntity(MapId.Nullspace); IoCManager.Resolve<IMapManager>().CreateNewMapEntity(MapId.Nullspace);
}; };
_configurationManager.RegisterCVar("outline.enabled", true);
} }
/// <summary> /// <summary>

View File

@@ -95,6 +95,9 @@ namespace Content.Client.GameObjects.Components.Doors
public override void OnChangeData(AppearanceComponent component) public override void OnChangeData(AppearanceComponent component)
{ {
if (component.Owner.Deleted)
return;
var sprite = component.Owner.GetComponent<ISpriteComponent>(); var sprite = component.Owner.GetComponent<ISpriteComponent>();
var animPlayer = component.Owner.GetComponent<AnimationPlayerComponent>(); var animPlayer = component.Owner.GetComponent<AnimationPlayerComponent>();
if (!component.TryGetData(DoorVisuals.VisualState, out DoorVisualState state)) if (!component.TryGetData(DoorVisuals.VisualState, out DoorVisualState state))

View File

@@ -203,31 +203,30 @@ namespace Content.Client.GameObjects
buttonDict.Add(slot, button); buttonDict.Add(slot, button);
} }
const int size = ButtonSize; const int sizep = (ButtonSize + ButtonSeparation);
const int sep = ButtonSeparation;
const int rSep = RightSeparation;
// Left column. // Left column.
AddButton(Slots.EYES, "glasses", (0, size + sep)); AddButton(Slots.EYES, "glasses", (0, 0));
AddButton(Slots.INNERCLOTHING, "uniform", (0, 2 * (size + sep))); AddButton(Slots.NECK, "neck", (0, sizep));
AddButton(Slots.EXOSUITSLOT1, "suit_storage", (0, 3 * (size + sep))); AddButton(Slots.INNERCLOTHING, "uniform", (0, 2 * sizep));
// Middle column. // Middle column.
AddButton(Slots.HEAD, "head", (size + sep, 0)); AddButton(Slots.HEAD, "head", (sizep, 0));
AddButton(Slots.MASK, "mask", (size + sep, size + sep)); AddButton(Slots.MASK, "mask", (sizep, sizep));
AddButton(Slots.OUTERCLOTHING, "suit", (size + sep, 2 * (size + sep))); AddButton(Slots.OUTERCLOTHING, "suit", (sizep, 2 * sizep));
AddButton(Slots.SHOES, "shoes", (size + sep, 3 * (size + sep))); AddButton(Slots.SHOES, "shoes", (sizep, 3 * sizep));
// Right column // Right column
AddButton(Slots.EARS, "ears", (2 * (size + sep), 0)); AddButton(Slots.EARS, "ears", (2 * sizep, 0));
AddButton(Slots.IDCARD, "id", (2 * (size + sep), size + sep)); AddButton(Slots.IDCARD, "id", (2 * sizep, sizep));
AddButton(Slots.GLOVES, "gloves", (2 * (size + sep), 2 * (size + sep))); AddButton(Slots.EXOSUITSLOT1, "suit_storage", (2 * sizep, 2 * sizep));
AddButton(Slots.POCKET1, "pocket", (2 * sizep, 3 * sizep));
// Far right column. // Far right column.
AddButton(Slots.BACKPACK, "back", (rSep + 3 * (size + sep), 0)); AddButton(Slots.BACKPACK, "back", (3 * sizep, 0));
AddButton(Slots.BELT, "belt", (rSep + 3 * (size + sep), size + sep)); AddButton(Slots.BELT, "belt", (3 * sizep, sizep));
AddButton(Slots.POCKET1, "pocket", (rSep + 3 * (size + sep), 2 * (size + sep))); AddButton(Slots.GLOVES, "gloves", (3 * sizep, 2 * sizep));
AddButton(Slots.POCKET2, "pocket", (rSep + 3 * (size + sep), 3 * (size + sep))); AddButton(Slots.POCKET2, "pocket", (3 * sizep, 3 * sizep));
} }
} }
} }

View File

@@ -59,7 +59,7 @@ namespace Content.Client.GameObjects
public IEntity GetEntity(string index) public IEntity GetEntity(string index)
{ {
if (_hands.TryGetValue(index, out var entity)) if (!string.IsNullOrEmpty(index) && _hands.TryGetValue(index, out var entity))
{ {
return entity; return entity;
} }
@@ -166,6 +166,8 @@ namespace Content.Client.GameObjects
{ {
_hands.Add(slot, null); _hands.Add(slot, null);
} }
serializer.DataField(this, x => ActiveIndex, "defaultHand", _hands.Keys.LastOrDefault());
} }
public override void HandleMessage(ComponentMessage message, IComponent component) public override void HandleMessage(ComponentMessage message, IComponent component)

View File

@@ -0,0 +1,149 @@
using System;
using System.Threading;
using Content.Shared.GameObjects.Components.Weapons;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Graphics.Overlays;
using Robust.Client.Interfaces.Graphics;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Client.GameObjects.Components.Weapons
{
[RegisterComponent]
public sealed class ClientFlashableComponent : SharedFlashableComponent
{
private CancellationTokenSource _cancelToken;
private TimeSpan _startTime;
private double _duration;
private FlashOverlay _overlay;
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
if (curState == null)
{
return;
}
var playerManager = IoCManager.Resolve<IPlayerManager>();
if (playerManager.LocalPlayer.ControlledEntity != Owner)
{
return;
}
var newState = (FlashComponentState) curState;
if (newState.Time == default)
{
return;
}
// Few things here:
// 1. If a shorter duration flash is applied then don't do anything
// 2. If the client-side time is later than when the flash should've ended don't do anything
var currentTime = IoCManager.Resolve<IGameTiming>().CurTime.TotalSeconds;
var newEndTime = newState.Time.TotalSeconds + newState.Duration;
var currentEndTime = _startTime.TotalSeconds + _duration;
if (currentEndTime > newEndTime)
{
return;
}
if (currentTime > newEndTime)
{
DisableOverlay();
return;
}
_startTime = newState.Time;
_duration = newState.Duration;
EnableOverlay(newEndTime - currentTime);
}
private void EnableOverlay(double duration)
{
// If the timer gets reset
if (_overlay != null)
{
_overlay.Duration = _duration;
_overlay.StartTime = _startTime;
_cancelToken.Cancel();
}
else
{
var overlayManager = IoCManager.Resolve<IOverlayManager>();
_overlay = new FlashOverlay(_duration);
overlayManager.AddOverlay(_overlay);
}
_cancelToken = new CancellationTokenSource();
Timer.Spawn((int) duration * 1000, DisableOverlay, _cancelToken.Token);
}
private void DisableOverlay()
{
if (_overlay == null)
{
return;
}
var overlayManager = IoCManager.Resolve<IOverlayManager>();
overlayManager.RemoveOverlay(_overlay.ID);
_overlay = null;
_cancelToken.Cancel();
_cancelToken = null;
}
}
public sealed class FlashOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly IGameTiming _timer;
private readonly IClyde _displayManager;
public TimeSpan StartTime { get; set; }
public double Duration { get; set; }
public FlashOverlay(double duration) : base(nameof(FlashOverlay))
{
_timer = IoCManager.Resolve<IGameTiming>();
_displayManager = IoCManager.Resolve<IClyde>();
StartTime = _timer.CurTime;
Duration = duration;
}
protected override void Draw(DrawingHandleBase handle)
{
var elapsedTime = (_timer.CurTime - StartTime).TotalSeconds;
if (elapsedTime > Duration)
{
return;
}
var screenHandle = (DrawingHandleScreen) handle;
screenHandle.DrawRect(
new UIBox2(0.0f, 0.0f, _displayManager.ScreenSize.X, _displayManager.ScreenSize.Y),
Color.White.WithAlpha(GetAlpha(elapsedTime / Duration))
);
}
private float GetAlpha(double ratio)
{
// Ideally you just want a smooth slope to finish it so it's not jarring at the end
// By all means put in a better curve
const float slope = -9.0f;
const float exponent = 0.1f;
const float yOffset = 9.0f;
const float xOffset = 0.0f;
// Overkill but easy to adjust if you want to mess around with the design
var result = (float) Math.Clamp(slope * (float) Math.Pow(ratio - xOffset, exponent) + yOffset, 0.0, 1.0);
DebugTools.Assert(!float.IsNaN(result));
return result;
}
}
}

View File

@@ -1,41 +0,0 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.Utility;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Weapons.Ranged
{
public sealed class BallisticMagazineVisualizer2D : AppearanceVisualizer
{
private string _baseState;
private int _steps;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_baseState = node.GetNode("base_state").AsString();
_steps = node.GetNode("steps").AsInt();
}
public override void OnChangeData(AppearanceComponent component)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(BallisticMagazineVisuals.AmmoCapacity, out int capacity))
{
return;
}
if (!component.TryGetData(BallisticMagazineVisuals.AmmoLeft, out int current))
{
return;
}
var step = ContentHelpers.RoundToLevels(current, capacity, _steps);
sprite.LayerSetState(0, $"{_baseState}-{step}");
}
}
}

View File

@@ -1,50 +0,0 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.Utility;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Weapons.Ranged
{
public sealed class BallisticMagazineWeaponVisualizer2D : AppearanceVisualizer
{
private string _baseState;
private int _steps;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_baseState = node.GetNode("base_state").AsString();
_steps = node.GetNode("steps").AsInt();
}
public override void OnChangeData(AppearanceComponent component)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
component.TryGetData(BallisticMagazineWeaponVisuals.MagazineLoaded, out bool loaded);
if (loaded)
{
if (!component.TryGetData(BallisticMagazineWeaponVisuals.AmmoCapacity, out int capacity))
{
return;
}
if (!component.TryGetData(BallisticMagazineWeaponVisuals.AmmoLeft, out int current))
{
return;
}
// capacity is - 1 as normally a bullet is chambered so max state is virtually never hit.
var step = ContentHelpers.RoundToLevels(current, capacity - 1, _steps);
sprite.LayerSetState(0, $"{_baseState}-{step}");
}
else
{
sprite.LayerSetState(0, _baseState);
}
}
}
}

View File

@@ -1,28 +1,26 @@
using System; using System;
using Content.Client.Animations; using Content.Client.Animations;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets; using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility; using Content.Client.Utility;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged; using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Robust.Client.Animations; using Robust.Client.Animations;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.Animations; using Robust.Shared.Animations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Network;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Players; using Robust.Shared.Players;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using static Content.Client.StaticIoC;
namespace Content.Client.GameObjects.Components.Weapons.Ranged namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
{ {
[RegisterComponent] [RegisterComponent]
public class BallisticMagazineWeaponComponent : Component, IItemStatus public class ClientMagazineBarrelComponent : Component, IItemStatus
{ {
private static readonly Animation AlarmAnimationSmg = new Animation private static readonly Animation AlarmAnimationSmg = new Animation
{ {
@@ -70,8 +68,8 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
} }
}; };
public override string Name => "BallisticMagazineWeapon"; public override string Name => "MagazineBarrel";
public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON; public override uint? NetID => ContentNetIDs.MAGAZINE_BARREL;
private StatusControl _statusControl; private StatusControl _statusControl;
@@ -101,11 +99,11 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
public override void HandleComponentState(ComponentState curState, ComponentState nextState) public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{ {
if (!(curState is BallisticMagazineWeaponComponentState cast)) if (!(curState is MagazineBarrelComponentState cast))
return; return;
Chambered = cast.Chambered; Chambered = cast.Chambered;
MagazineCount = cast.MagazineCount; MagazineCount = cast.Magazine;
_statusControl?.Update(); _statusControl?.Update();
} }
@@ -115,7 +113,8 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
switch (message) switch (message)
{ {
case BmwComponentAutoEjectedMessage _:
case MagazineAutoEjectMessage _:
_statusControl?.PlayAlarmAnimation(); _statusControl?.PlayAlarmAnimation();
return; return;
} }
@@ -138,13 +137,13 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
private sealed class StatusControl : Control private sealed class StatusControl : Control
{ {
private readonly BallisticMagazineWeaponComponent _parent; private readonly ClientMagazineBarrelComponent _parent;
private readonly HBoxContainer _bulletsListTop; private readonly HBoxContainer _bulletsListTop;
private readonly HBoxContainer _bulletsListBottom; private readonly HBoxContainer _bulletsListBottom;
private readonly TextureRect _chamberedBullet; private readonly TextureRect _chamberedBullet;
private readonly Label _noMagazineLabel; private readonly Label _noMagazineLabel;
public StatusControl(BallisticMagazineWeaponComponent parent) public StatusControl(ClientMagazineBarrelComponent parent)
{ {
_parent = parent; _parent = parent;
SizeFlagsHorizontal = SizeFlags.FillExpand; SizeFlagsHorizontal = SizeFlags.FillExpand;
@@ -181,7 +180,7 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
}, },
(_chamberedBullet = new TextureRect (_chamberedBullet = new TextureRect
{ {
Texture = ResC.GetTexture("/Textures/UserInterface/status/bullets/chambered.png"), Texture = StaticIoC.ResC.GetTexture("/Textures/UserInterface/status/bullets/chambered.png"),
SizeFlagsVertical = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.ShrinkCenter,
SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill, SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
}) })
@@ -223,7 +222,7 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
texturePath = "/Textures/UserInterface/status/bullets/tiny.png"; texturePath = "/Textures/UserInterface/status/bullets/tiny.png";
} }
var texture = ResC.GetTexture(texturePath); var texture = StaticIoC.ResC.GetTexture(texturePath);
const int tinyMaxRow = 60; const int tinyMaxRow = 60;

View File

@@ -0,0 +1,38 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels.Visualizers
{
[UsedImplicitly]
public sealed class BarrelBoltVisualizer2D : AppearanceVisualizer
{
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, $"bolt-open");
}
public override void OnChangeData(AppearanceComponent component)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(BarrelBoltVisuals.BoltOpen, out bool boltOpen))
{
return;
}
if (boltOpen)
{
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, "bolt-open");
}
else
{
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, "bolt-closed");
}
}
}
}

View File

@@ -0,0 +1,110 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels.Visualizers
{
[UsedImplicitly]
public sealed class MagVisualizer2D : AppearanceVisualizer
{
private bool _magLoaded;
private string _magState;
private int _magSteps;
private bool _zeroVisible;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_magState = node.GetNode("magState").AsString();
_magSteps = node.GetNode("steps").AsInt();
_zeroVisible = node.GetNode("zeroVisible").AsBool();
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetState(RangedBarrelVisualLayers.Mag, $"{_magState}-{_magSteps-1}");
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetState(RangedBarrelVisualLayers.MagUnshaded, $"{_magState}-unshaded-{_magSteps-1}");
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
}
public override void OnChangeData(AppearanceComponent component)
{
// tl;dr
// 1.If no mag then hide it OR
// 2. If step 0 isn't visible then hide it (mag or unshaded)
// 3. Otherwise just do mag / unshaded as is
var sprite = component.Owner.GetComponent<ISpriteComponent>();
component.TryGetData(MagazineBarrelVisuals.MagLoaded, out _magLoaded);
if (_magLoaded)
{
if (!component.TryGetData(AmmoVisuals.AmmoMax, out int capacity))
{
return;
}
if (!component.TryGetData(AmmoVisuals.AmmoCount, out int current))
{
return;
}
var step = ContentHelpers.RoundToLevels(current, capacity, _magSteps);
if (step == 0 && !_zeroVisible)
{
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
return;
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, true);
sprite.LayerSetState(RangedBarrelVisualLayers.Mag, $"{_magState}-{step}");
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, true);
sprite.LayerSetState(RangedBarrelVisualLayers.MagUnshaded, $"{_magState}-unshaded-{step}");
}
}
else
{
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels.Visualizers
{
[UsedImplicitly]
public sealed class SpentAmmoVisualizer2D : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(AmmoVisuals.Spent, out bool spent))
{
return;
}
sprite.LayerSetState(AmmoVisualLayers.Base, spent ? "spent" : "base");
}
}
public enum AmmoVisualLayers
{
Base,
}
}

View File

@@ -4,12 +4,35 @@ using Robust.Shared.Map;
namespace Content.Client.GameObjects.Components.Weapons.Ranged namespace Content.Client.GameObjects.Components.Weapons.Ranged
{ {
// Yeah I put it all in the same enum, don't judge me
public enum RangedBarrelVisualLayers
{
Base,
BaseUnshaded,
Bolt,
Mag,
MagUnshaded,
}
[RegisterComponent] [RegisterComponent]
public sealed class ClientRangedWeaponComponent : SharedRangedWeaponComponent public sealed class ClientRangedWeaponComponent : SharedRangedWeaponComponent
{ {
public FireRateSelector FireRateSelector { get; private set; } = FireRateSelector.Safety;
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is RangedWeaponComponentState rangedState))
{
return;
}
FireRateSelector = rangedState.FireRateSelector;
}
public void SyncFirePos(GridCoordinates worldPos) public void SyncFirePos(GridCoordinates worldPos)
{ {
SendNetworkMessage(new SyncFirePosMessage(worldPos)); SendNetworkMessage(new FirePosComponentMessage(worldPos));
} }
} }
} }

View File

@@ -1,32 +0,0 @@
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.Utility;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Power
{
public class HitscanWeaponVisualizer2D : AppearanceVisualizer
{
private string _prefix;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_prefix = node.GetNode("prefix").AsString();
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData(PowerCellVisuals.ChargeLevel, out float fraction))
{
sprite.LayerSetState(0, $"{_prefix}_{ContentHelpers.RoundToLevels(fraction, 1, 5) * 25}");
}
}
}
}

View File

@@ -1,5 +1,7 @@
using Content.Client.GameObjects.Components.Weapons.Ranged; using System;
using Content.Client.GameObjects.Components.Weapons.Ranged;
using Content.Client.Interfaces.GameObjects; using Content.Client.Interfaces.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Robust.Client.GameObjects.EntitySystems; using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.Input;
@@ -26,8 +28,8 @@ namespace Content.Client.GameObjects.EntitySystems
private InputSystem _inputSystem; private InputSystem _inputSystem;
private CombatModeSystem _combatModeSystem; private CombatModeSystem _combatModeSystem;
private bool _isFirstShot;
private bool _blocked; private bool _blocked;
private int _shotCounter;
public override void Initialize() public override void Initialize()
{ {
@@ -47,17 +49,14 @@ namespace Content.Client.GameObjects.EntitySystems
return; return;
} }
var canFireSemi = _isFirstShot;
var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use); var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down) if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down)
{ {
_isFirstShot = true; _shotCounter = 0;
_blocked = false; _blocked = false;
return; return;
} }
_isFirstShot = false;
var entity = _playerManager.LocalPlayer.ControlledEntity; var entity = _playerManager.LocalPlayer.ControlledEntity;
if (entity == null || !entity.TryGetComponent(out IHandsComponent hands)) if (entity == null || !entity.TryGetComponent(out IHandsComponent hands))
{ {
@@ -71,6 +70,25 @@ namespace Content.Client.GameObjects.EntitySystems
return; return;
} }
switch (weapon.FireRateSelector)
{
case FireRateSelector.Safety:
_blocked = true;
return;
case FireRateSelector.Single:
if (_shotCounter >= 1)
{
_blocked = true;
return;
}
break;
case FireRateSelector.Automatic:
break;
default:
throw new ArgumentOutOfRangeException();
}
if (_blocked) if (_blocked)
{ {
return; return;
@@ -81,10 +99,7 @@ namespace Content.Client.GameObjects.EntitySystems
if (!_mapManager.TryFindGridAt(worldPos, out var grid)) if (!_mapManager.TryFindGridAt(worldPos, out var grid))
grid = _mapManager.GetDefaultGrid(worldPos.MapId); grid = _mapManager.GetDefaultGrid(worldPos.MapId);
if (weapon.Automatic || canFireSemi)
{
weapon.SyncFirePos(grid.MapToGrid(worldPos)); weapon.SyncFirePos(grid.MapToGrid(worldPos));
} }
} }
} }
}

View File

@@ -24,6 +24,7 @@ namespace Content.Client.GameTicking
[ViewVariables] public bool IsGameStarted { get; private set; } [ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public string ServerInfoBlob { get; private set; } [ViewVariables] public string ServerInfoBlob { get; private set; }
[ViewVariables] public DateTime StartTime { get; private set; } [ViewVariables] public DateTime StartTime { get; private set; }
[ViewVariables] public bool Paused { get; private set; }
public event Action InfoBlobUpdated; public event Action InfoBlobUpdated;
public event Action LobbyStatusUpdated; public event Action LobbyStatusUpdated;
@@ -36,6 +37,7 @@ namespace Content.Client.GameTicking
_netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame), JoinGame); _netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame), JoinGame);
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus); _netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus);
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo); _netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo);
_netManager.RegisterNetMessage<MsgTickerLobbyCountdown>(nameof(MsgTickerLobbyCountdown), LobbyCountdown);
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd); _netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd);
_initialized = true; _initialized = true;
@@ -53,6 +55,7 @@ namespace Content.Client.GameTicking
StartTime = message.StartTime; StartTime = message.StartTime;
IsGameStarted = message.IsRoundStarted; IsGameStarted = message.IsRoundStarted;
AreWeReady = message.YouAreReady; AreWeReady = message.YouAreReady;
Paused = message.Paused;
LobbyStatusUpdated?.Invoke(); LobbyStatusUpdated?.Invoke();
} }
@@ -69,6 +72,12 @@ namespace Content.Client.GameTicking
_stateManager.RequestStateChange<GameScreen>(); _stateManager.RequestStateChange<GameScreen>();
} }
private void LobbyCountdown(MsgTickerLobbyCountdown message)
{
StartTime = message.StartTime;
Paused = message.Paused;
}
private void RoundEnd(MsgRoundEndMessage message) private void RoundEnd(MsgRoundEndMessage message)
{ {

View File

@@ -39,7 +39,7 @@ namespace Content.Client.Input
ghost.AddFunction(EngineKeyFunctions.MoveDown); ghost.AddFunction(EngineKeyFunctions.MoveDown);
ghost.AddFunction(EngineKeyFunctions.MoveLeft); ghost.AddFunction(EngineKeyFunctions.MoveLeft);
ghost.AddFunction(EngineKeyFunctions.MoveRight); ghost.AddFunction(EngineKeyFunctions.MoveRight);
ghost.AddFunction(EngineKeyFunctions.Run); ghost.AddFunction(EngineKeyFunctions.Walk);
ghost.AddFunction(ContentKeyFunctions.OpenContextMenu); ghost.AddFunction(ContentKeyFunctions.OpenContextMenu);
common.AddFunction(ContentKeyFunctions.OpenEntitySpawnWindow); common.AddFunction(ContentKeyFunctions.OpenEntitySpawnWindow);

View File

@@ -8,6 +8,7 @@ namespace Content.Client.Interfaces
string ServerInfoBlob { get; } string ServerInfoBlob { get; }
bool AreWeReady { get; } bool AreWeReady { get; }
DateTime StartTime { get; } DateTime StartTime { get; }
bool Paused { get; }
void Initialize(); void Initialize();
event Action InfoBlobUpdated; event Action InfoBlobUpdated;

View File

@@ -12,6 +12,7 @@ using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Input; using Robust.Shared.Input;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Timing; using Robust.Shared.Interfaces.Timing;
@@ -36,6 +37,8 @@ namespace Content.Client.State
[Dependency] private readonly IGameTiming _timing; [Dependency] private readonly IGameTiming _timing;
[Dependency] private readonly IMapManager _mapManager; [Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager;
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649 #pragma warning restore 649
private IEntity _lastHoveredEntity; private IEntity _lastHoveredEntity;
@@ -72,6 +75,15 @@ namespace Content.Client.State
} }
InteractionOutlineComponent outline; InteractionOutlineComponent outline;
if(!_configurationManager.GetCVar<bool>("outline.enabled"))
{
if(entityToClick != null && entityToClick.TryGetComponent(out outline))
{
outline.OnMouseLeave(); //Prevent outline remains from persisting post command.
}
return;
}
if (entityToClick == _lastHoveredEntity) if (entityToClick == _lastHoveredEntity)
{ {
if (entityToClick != null && entityToClick.TryGetComponent(out outline)) if (entityToClick != null && entityToClick.TryGetComponent(out outline))

View File

@@ -123,6 +123,13 @@ namespace Content.Client.State
} }
string text; string text;
if (_clientGameTicker.Paused)
{
text = Loc.GetString("Paused");
}
else
{
var difference = _clientGameTicker.StartTime - DateTime.UtcNow; var difference = _clientGameTicker.StartTime - DateTime.UtcNow;
if (difference.Ticks < 0) if (difference.Ticks < 0)
{ {
@@ -139,6 +146,7 @@ namespace Content.Client.State
{ {
text = $"{(int) Math.Floor(difference.TotalMinutes)}:{difference.Seconds:D2}"; text = $"{(int) Math.Floor(difference.TotalMinutes)}:{difference.Seconds:D2}";
} }
}
_lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text); _lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text);
} }

View File

@@ -37,7 +37,7 @@ namespace Content.IntegrationTests
{ {
} }
public void StartRound() public void StartRound(bool force = false)
{ {
} }
@@ -81,12 +81,33 @@ namespace Content.IntegrationTests
public IEnumerable<GameRule> ActiveGameRules { get; } = Array.Empty<GameRule>(); public IEnumerable<GameRule> ActiveGameRules { get; } = Array.Empty<GameRule>();
public void SetStartPreset(Type type) public bool TryGetPreset(string name, out Type type)
{
type = default;
return false;
}
public void SetStartPreset(Type type, bool force = false)
{ {
} }
public void SetStartPreset(string type) public void SetStartPreset(string name, bool force = false)
{ {
} }
public bool DelayStart(TimeSpan time)
{
return true;
}
public bool PauseStart(bool pause = true)
{
return true;
}
public bool TogglePause()
{
return false;
}
} }
} }

View File

@@ -5,6 +5,7 @@ namespace Content.Server.AI.Operators
{ {
public abstract class AiOperator public abstract class AiOperator
{ {
public bool HasStartup => _hasStartup;
private bool _hasStartup = false; private bool _hasStartup = false;
private bool _hasShutdown = false; private bool _hasShutdown = false;

View File

@@ -1,100 +0,0 @@
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Weapon.Ranged;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Operators.Combat.Ranged
{
public class ShootAtEntityOperator : AiOperator
{
private IEntity _owner;
private IEntity _target;
private float _accuracy;
private float _burstTime;
private float _elapsedTime;
public ShootAtEntityOperator(IEntity owner, IEntity target, float accuracy, float burstTime = 0.5f)
{
_owner = owner;
_target = target;
_accuracy = accuracy;
_burstTime = burstTime;
}
public override bool TryStartup()
{
if (!base.TryStartup())
{
return true;
}
if (!_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
{
return false;
}
if (!combatModeComponent.IsInCombatMode)
{
combatModeComponent.IsInCombatMode = true;
}
return true;
}
public override void Shutdown(Outcome outcome)
{
base.Shutdown(outcome);
if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
{
combatModeComponent.IsInCombatMode = false;
}
}
public override Outcome Execute(float frameTime)
{
// TODO: Probably just do all the checks on first try and then after that repeat the fire.
if (_burstTime <= _elapsedTime)
{
return Outcome.Success;
}
_elapsedTime += frameTime;
if (_target.TryGetComponent(out DamageableComponent damageableComponent))
{
if (damageableComponent.IsDead())
{
return Outcome.Success;
}
}
if (!_owner.TryGetComponent(out HandsComponent hands) || hands.GetActiveHand == null)
{
return Outcome.Failed;
}
var equippedWeapon = hands.GetActiveHand.Owner;
if ((_target.Transform.GridPosition.Position - _owner.Transform.GridPosition.Position).Length >
_owner.GetComponent<AiControllerComponent>().VisionRadius)
{
// Not necessarily a hard fail, more of a soft fail
return Outcome.Failed;
}
// Unless RangedWeaponComponent is removed from hitscan weapons this shouldn't happen
if (!equippedWeapon.TryGetComponent(out RangedWeaponComponent rangedWeaponComponent))
{
return Outcome.Failed;
}
// TODO: Accuracy
rangedWeaponComponent.AiFire(_owner, _target.Transform.GridPosition);
return Outcome.Continuing;
}
}
}

View File

@@ -1,41 +0,0 @@
using System;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Operators.Combat.Ranged
{
public class WaitForHitscanChargeOperator : AiOperator
{
private float _lastCharge = 0.0f;
private float _lastFill = 0.0f;
private HitscanWeaponComponent _hitscan;
public WaitForHitscanChargeOperator(IEntity entity)
{
if (!entity.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
{
throw new InvalidOperationException();
}
_hitscan = hitscanWeaponComponent;
}
public override Outcome Execute(float frameTime)
{
if (_hitscan.CapacitorComponent.Capacity - _hitscan.CapacitorComponent.Charge < 0.01f)
{
return Outcome.Success;
}
// If we're not charging then just stop
_lastFill = _hitscan.CapacitorComponent.Charge - _lastCharge;
_lastCharge = _hitscan.CapacitorComponent.Charge;
if (_lastFill == 0.0f)
{
return Outcome.Failed;
}
return Outcome.Continuing;
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Content.Server.AI.Operators.Inventory
/// <returns></returns> /// <returns></returns>
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)
{ {
if (!_owner.TryGetComponent(out HandsComponent handsComponent)) if (!_owner.TryGetComponent(out HandsComponent handsComponent) || handsComponent.FindHand(_entity) == null)
{ {
return Outcome.Failed; return Outcome.Failed;
} }

View File

@@ -3,6 +3,7 @@ using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components; using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -24,16 +25,16 @@ namespace Content.Server.AI.Operators.Inventory
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)
{ {
if (!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
{
return Outcome.Failed;
}
if (!ContainerHelpers.TryGetContainer(_target, out var container)) if (!ContainerHelpers.TryGetContainer(_target, out var container))
{ {
return Outcome.Success; return Outcome.Success;
} }
if (!InteractionChecks.InRangeUnobstructed(_owner, container.Owner.Transform.MapPosition, ignoredEnt: container.Owner))
{
return Outcome.Failed;
}
if (!container.Owner.TryGetComponent(out EntityStorageComponent storageComponent) || if (!container.Owner.TryGetComponent(out EntityStorageComponent storageComponent) ||
storageComponent.IsWeldedShut) storageComponent.IsWeldedShut)
{ {

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
@@ -240,10 +241,11 @@ namespace Content.Server.AI.Operators.Movement
var startGrid = _mapManager.GetGrid(Owner.Transform.GridID).GetTileRef(Owner.Transform.GridPosition); var startGrid = _mapManager.GetGrid(Owner.Transform.GridID).GetTileRef(Owner.Transform.GridPosition);
var endGrid = _mapManager.GetGrid(TargetGrid.GridID).GetTileRef(TargetGrid);; var endGrid = _mapManager.GetGrid(TargetGrid.GridID).GetTileRef(TargetGrid);;
// _routeCancelToken = new CancellationTokenSource(); var access = AccessReader.FindAccessTags(Owner);
RouteJob = _pathfinder.RequestPath(new PathfindingArgs( RouteJob = _pathfinder.RequestPath(new PathfindingArgs(
Owner.Uid, Owner.Uid,
access,
collisionMask, collisionMask,
startGrid, startGrid,
endGrid, endGrid,

View File

@@ -3,7 +3,6 @@ using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory; using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Utility.Considerations; using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Melee; using Content.Server.AI.Utility.Considerations.Combat.Melee;
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
using Content.Server.AI.Utility.Considerations.Inventory; using Content.Server.AI.Utility.Considerations.Inventory;
using Content.Server.AI.Utility.Curves; using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState; using Content.Server.AI.WorldState;
@@ -41,9 +40,6 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
protected override Consideration[] Considerations { get; } = { protected override Consideration[] Considerations { get; } = {
new MeleeWeaponEquippedCon( new MeleeWeaponEquippedCon(
new InverseBoolCurve()), new InverseBoolCurve()),
// We'll prioritise equipping ranged weapons; If we try and score this then it'll just keep swapping between ranged and melee
new RangedWeaponEquippedCon(
new InverseBoolCurve()),
new CanPutTargetInHandsCon( new CanPutTargetInHandsCon(
new BoolCurve()), new BoolCurve()),
new MeleeWeaponSpeedCon( new MeleeWeaponSpeedCon(

View File

@@ -29,11 +29,17 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
public override void SetupOperators(Blackboard context) public override void SetupOperators(Blackboard context)
{ {
var moveOperator = new MoveToEntityOperator(Owner, _entity);
var equipped = context.GetState<EquippedEntityState>().GetValue(); var equipped = context.GetState<EquippedEntityState>().GetValue();
MoveToEntityOperator moveOperator;
if (equipped != null && equipped.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent)) if (equipped != null && equipped.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent))
{ {
moveOperator.DesiredRange = meleeWeaponComponent.Range - 0.01f; moveOperator = new MoveToEntityOperator(Owner, _entity, meleeWeaponComponent.Range - 0.01f);
}
// I think it's possible for this to happen given planning is time-sliced?
// TODO: At this point we should abort
else
{
moveOperator = new MoveToEntityOperator(Owner, _entity);
} }
ActionOperators = new Queue<AiOperator>(new AiOperator[] ActionOperators = new Queue<AiOperator>(new AiOperator[]
@@ -64,7 +70,7 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)), new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
// Somewhat prioritise distance // Somewhat prioritise distance
new DistanceCon( new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)), new QuadraticCurve(-1.0f, 1.0f, 1.02f, 0.0f)),
// Prefer weaker targets // Prefer weaker targets
new TargetHealthCon( new TargetHealthCon(
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)), new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),

View File

@@ -42,7 +42,7 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
new HasMeleeWeaponCon( new HasMeleeWeaponCon(
new InverseBoolCurve()), new InverseBoolCurve()),
new DistanceCon( new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)), new QuadraticCurve(-1.0f, 1.0f, 1.02f, 0.0f)),
new MeleeWeaponDamageCon( new MeleeWeaponDamageCon(
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)), new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
new MeleeWeaponSpeedCon( new MeleeWeaponSpeedCon(

View File

@@ -1,97 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Combat.Ranged;
using Content.Server.AI.Operators.Movement;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.Considerations.Movement;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.AI.WorldState.States.Movement;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
{
public sealed class BallisticAttackEntity : UtilityAction
{
private IEntity _entity;
private MoveToEntityOperator _moveOperator;
public BallisticAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void Shutdown()
{
base.Shutdown();
if (_moveOperator != null)
{
_moveOperator.MovedATile -= InLos;
}
}
public override void SetupOperators(Blackboard context)
{
_moveOperator = new MoveToEntityOperator(Owner, _entity);
_moveOperator.MovedATile += InLos;
// TODO: Accuracy in blackboard
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
_moveOperator,
new ShootAtEntityOperator(Owner, _entity, 0.7f),
});
// We will do a quick check now to see if we even need to move which also saves a pathfind
InLos();
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<TargetEntityState>().SetValue(_entity);
context.GetState<MoveTargetState>().SetValue(_entity);
var equipped = context.GetState<EquippedEntityState>().GetValue();
context.GetState<WeaponEntityState>().SetValue(equipped);
}
protected override Consideration[] Considerations { get; } = {
// Check if we have a weapon; easy-out
new BallisticWeaponEquippedCon(
new BoolCurve()),
new BallisticAmmoCon(
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
// Don't attack a dead target
new TargetIsDeadCon(
new InverseBoolCurve()),
// Deprioritise a target in crit
new TargetIsCritCon(
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
// Somewhat prioritise distance
new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
// Prefer weaker targets
new TargetHealthCon(
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
};
private void InLos()
{
// This should only be called if the movement operator is the current one;
// if that turns out not to be the case we can just add a check here.
if (Visibility.InLineOfSight(Owner, _entity))
{
_moveOperator.HaveArrived();
var mover = ActionOperators.Dequeue();
mover.Shutdown(Outcome.Success);
}
}
}
}

View File

@@ -1,53 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.Considerations.Inventory;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Combat;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
{
public sealed class DropEmptyBallistic : UtilityAction
{
public sealed override float Bonus => 20.0f;
private IEntity _entity;
public DropEmptyBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new DropEntityOperator(Owner, _entity)
});
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<TargetEntityState>().SetValue(_entity);
context.GetState<WeaponEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
new TargetInOurInventoryCon(
new BoolCurve()),
// Need to put in hands to drop
new CanPutTargetInHandsCon(
new BoolCurve()),
// Drop that sucker
new BallisticAmmoCon(
new InverseBoolCurve()),
};
}
}

View File

@@ -1,53 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Melee;
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.Considerations.Inventory;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Combat;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
{
public sealed class EquipBallistic : UtilityAction
{
private IEntity _entity;
public EquipBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity)
});
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<WeaponEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
new EquippedBallisticCon(
new InverseBoolCurve()),
new MeleeWeaponEquippedCon(
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
new CanPutTargetInHandsCon(
new BoolCurve()),
new BallisticAmmoCon(
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
new RangedWeaponFireRateCon(
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
};
}
}

View File

@@ -1,46 +0,0 @@
using Content.Server.AI.Operators.Sequences;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Containers;
using Content.Server.AI.Utility.Considerations.Hands;
using Content.Server.AI.Utility.Considerations.Movement;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Movement;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
{
public sealed class PickUpAmmo : UtilityAction
{
private IEntity _entity;
public PickUpAmmo(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<MoveTargetState>().SetValue(_entity);
context.GetState<TargetEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
//TODO: Consider ammo's type and what guns we have
new TargetAccessibleCon(
new BoolCurve()),
new FreeHandCon(
new BoolCurve()),
new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
};
}
}

View File

@@ -1,57 +0,0 @@
using Content.Server.AI.Operators.Sequences;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.Considerations.Containers;
using Content.Server.AI.Utility.Considerations.Hands;
using Content.Server.AI.Utility.Considerations.Movement;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.AI.WorldState.States.Movement;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
{
public sealed class PickUpBallisticMagWeapon : UtilityAction
{
private IEntity _entity;
public PickUpBallisticMagWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<MoveTargetState>().SetValue(_entity);
context.GetState<TargetEntityState>().SetValue(_entity);
context.GetState<WeaponEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
new HeldRangedWeaponsCon(
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
new TargetAccessibleCon(
new BoolCurve()),
new FreeHandCon(
new BoolCurve()),
// For now don't grab empty guns - at least until we can start storing stuff in inventory
new BallisticAmmoCon(
new BoolCurve()),
new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
new RangedWeaponFireRateCon(
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
// TODO: Ballistic accuracy? Depends how the design transitions
};
}
}

View File

@@ -1,69 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Operators.Movement;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
using Content.Server.AI.Utility.Considerations.Movement;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.AI.WorldState.States.Movement;
using Content.Server.GameObjects.Components.Power.Chargers;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
{
public sealed class PutHitscanInCharger : UtilityAction
{
// Maybe a bad idea to not allow override
public override bool CanOverride => false;
private readonly IEntity _charger;
public PutHitscanInCharger(IEntity owner, IEntity charger, float weight) : base(owner)
{
_charger = charger;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
var weapon = context.GetState<EquippedEntityState>().GetValue();
if (weapon == null || _charger.GetComponent<WeaponCapacitorChargerComponent>().HeldItem != null)
{
ActionOperators = new Queue<AiOperator>();
return;
}
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new MoveToEntityOperator(Owner, _charger),
new InteractWithEntityOperator(Owner, _charger),
// Separate task will deal with picking it up
});
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<MoveTargetState>().SetValue(_charger);
context.GetState<TargetEntityState>().SetValue(_charger);
}
protected override Consideration[] Considerations { get; } =
{
new HitscanWeaponEquippedCon(
new BoolCurve()),
new HitscanChargerFullCon(
new InverseBoolCurve()),
new HitscanChargerRateCon(
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
new HitscanChargeCon(
new QuadraticCurve(-1.2f, 2.0f, 1.2f, 0.0f)),
};
}
}

View File

@@ -1,52 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
using Content.Server.AI.Utility.Considerations.Inventory;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Combat;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
{
public sealed class DropEmptyHitscan : UtilityAction
{
private IEntity _entity;
public DropEmptyHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new DropEntityOperator(Owner, _entity)
});
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<TargetEntityState>().SetValue(_entity);
context.GetState<WeaponEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
new TargetInOurInventoryCon(
new BoolCurve()),
// Need to put in hands to drop
new CanPutTargetInHandsCon(
new BoolCurve()),
// If completely empty then drop that sucker
new HitscanChargeCon(
new InverseBoolCurve()),
};
}
}

View File

@@ -1,55 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Melee;
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
using Content.Server.AI.Utility.Considerations.Inventory;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Combat;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
{
public sealed class EquipHitscan : UtilityAction
{
private IEntity _entity;
public EquipHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity)
});
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<WeaponEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
new EquippedHitscanCon(
new InverseBoolCurve()),
new MeleeWeaponEquippedCon(
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
new CanPutTargetInHandsCon(
new BoolCurve()),
new HitscanChargeCon(
new QuadraticCurve(1.0f, 1.0f, 0.0f, 0.0f)),
new RangedWeaponFireRateCon(
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
new HitscanWeaponDamageCon(
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
};
}
}

View File

@@ -1,96 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Combat.Ranged;
using Content.Server.AI.Operators.Movement;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
using Content.Server.AI.Utility.Considerations.Movement;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.AI.WorldState.States.Movement;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
{
public sealed class HitscanAttackEntity : UtilityAction
{
private IEntity _entity;
private MoveToEntityOperator _moveOperator;
public HitscanAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void Shutdown()
{
base.Shutdown();
if (_moveOperator != null)
{
_moveOperator.MovedATile -= InLos;
}
}
public override void SetupOperators(Blackboard context)
{
_moveOperator = new MoveToEntityOperator(Owner, _entity);
_moveOperator.MovedATile += InLos;
// TODO: Accuracy in blackboard
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
_moveOperator,
new ShootAtEntityOperator(Owner, _entity, 0.7f),
});
InLos();
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<TargetEntityState>().SetValue(_entity);
context.GetState<MoveTargetState>().SetValue(_entity);
var equipped = context.GetState<EquippedEntityState>().GetValue();
context.GetState<WeaponEntityState>().SetValue(equipped);
}
protected override Consideration[] Considerations { get; } = {
// Check if we have a weapon; easy-out
new HitscanWeaponEquippedCon(
new BoolCurve()),
new HitscanChargeCon(
new QuadraticCurve(1.0f, 0.1f, 0.0f, 0.0f)),
// Don't attack a dead target
new TargetIsDeadCon(
new InverseBoolCurve()),
// Deprioritise a target in crit
new TargetIsCritCon(
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
// Somewhat prioritise distance
new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
// Prefer weaker targets
new TargetHealthCon(
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
};
private void InLos()
{
// This should only be called if the movement operator is the current one;
// if that turns out not to be the case we can just add a check here.
if (Visibility.InLineOfSight(Owner, _entity))
{
_moveOperator.HaveArrived();
var mover = ActionOperators.Dequeue();
mover.Shutdown(Outcome.Success);
}
}
}
}

View File

@@ -1,65 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Combat.Ranged;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Operators.Movement;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
using Content.Server.AI.Utility.Considerations.Containers;
using Content.Server.AI.Utility.Considerations.Hands;
using Content.Server.AI.Utility.Considerations.Movement;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Movement;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
{
public sealed class PickUpHitscanFromCharger : UtilityAction
{
private IEntity _entity;
private IEntity _charger;
public PickUpHitscanFromCharger(IEntity owner, IEntity entity, IEntity charger, float weight) : base(owner)
{
_entity = entity;
_charger = charger;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new MoveToEntityOperator(Owner, _charger),
new WaitForHitscanChargeOperator(_entity),
new PickupEntityOperator(Owner, _entity),
});
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<MoveTargetState>().SetValue(_entity);
context.GetState<TargetEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
new HeldRangedWeaponsCon(
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
new TargetAccessibleCon(
new BoolCurve()),
new FreeHandCon(
new BoolCurve()),
new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
// TODO: ChargerHasPower
new RangedWeaponFireRateCon(
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
new HitscanWeaponDamageCon(
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
};
}
}

View File

@@ -1,59 +0,0 @@
using Content.Server.AI.Operators.Sequences;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
using Content.Server.AI.Utility.Considerations.Containers;
using Content.Server.AI.Utility.Considerations.Hands;
using Content.Server.AI.Utility.Considerations.Movement;
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.AI.WorldState.States.Movement;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
{
public sealed class PickUpHitscanWeapon : UtilityAction
{
private IEntity _entity;
public PickUpHitscanWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
{
_entity = entity;
Bonus = weight;
}
public override void SetupOperators(Blackboard context)
{
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
}
protected override void UpdateBlackboard(Blackboard context)
{
base.UpdateBlackboard(context);
context.GetState<MoveTargetState>().SetValue(_entity);
context.GetState<TargetEntityState>().SetValue(_entity);
context.GetState<WeaponEntityState>().SetValue(_entity);
}
protected override Consideration[] Considerations { get; } = {
new HeldRangedWeaponsCon(
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
new TargetAccessibleCon(
new BoolCurve()),
new FreeHandCon(
new BoolCurve()),
// For now don't grab empty guns - at least until we can start storing stuff in inventory
new HitscanChargeCon(
new BoolCurve()),
new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
// TODO: Weapon charge level
new RangedWeaponFireRateCon(
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
new HitscanWeaponDamageCon(
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
};
}
}

View File

@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Idle
new StoredStateIsNullCon<LastOpenedStorageState, IEntity>( new StoredStateIsNullCon<LastOpenedStorageState, IEntity>(
new InverseBoolCurve()), new InverseBoolCurve()),
new DistanceCon( new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)), new QuadraticCurve(-1.0f, 1.0f, 1.02f, 0.0f)),
}; };
public override void SetupOperators(Blackboard context) public override void SetupOperators(Blackboard context)
{ {

View File

@@ -35,7 +35,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
new ThirstCon( new ThirstCon(
new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)), new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)),
new DistanceCon( new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)), new QuadraticCurve(-1.0f, 1.0f, 1.02f, 0.0f)),
new DrinkValueCon( new DrinkValueCon(
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)), new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)),
}; };

View File

@@ -35,7 +35,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Food
new HungerCon( new HungerCon(
new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)), new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)),
new DistanceCon( new DistanceCon(
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)), new QuadraticCurve(-1.0f, 1.0f, 1.02f, 0.0f)),
new FoodValueCon( new FoodValueCon(
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)), new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)),
}; };

View File

@@ -126,6 +126,9 @@ namespace Content.Server.AI.Utility.AiLogic
{ {
damageableComponent.DamageThresholdPassed -= DeathHandle; damageableComponent.DamageThresholdPassed -= DeathHandle;
} }
var currentOp = CurrentAction?.ActionOperators.Peek();
currentOp?.Shutdown(Outcome.Failed);
} }
private void DeathHandle(object sender, DamageThresholdPassedEventArgs eventArgs) private void DeathHandle(object sender, DamageThresholdPassedEventArgs eventArgs)
@@ -153,6 +156,12 @@ namespace Content.Server.AI.Utility.AiLogic
return; return;
} }
var currentOp = CurrentAction?.ActionOperators.Peek();
if (currentOp != null && currentOp.HasStartup)
{
currentOp.Shutdown(Outcome.Failed);
}
CurrentAction = action; CurrentAction = action;
action.SetupOperators(_blackboard); action.SetupOperators(_blackboard);
} }

View File

@@ -1,13 +1,5 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions; using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
using Content.Server.AI.Utility.ExpandableActions.Combat;
using Content.Server.AI.Utility.ExpandableActions.Combat.Melee; using Content.Server.AI.Utility.ExpandableActions.Combat.Melee;
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged;
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Utility.BehaviorSets namespace Content.Server.AI.Utility.BehaviorSets
@@ -18,19 +10,11 @@ namespace Content.Server.AI.Utility.BehaviorSets
{ {
Actions = new IAiUtility[] Actions = new IAiUtility[]
{ {
new PickUpRangedExp(),
// TODO: Reload Ballistic // TODO: Reload Ballistic
new DropEmptyBallisticExp(),
// TODO: Ideally long-term we should just store the weapons in backpack // TODO: Ideally long-term we should just store the weapons in backpack
new DropEmptyHitscanExp(),
new EquipMeleeExp(), new EquipMeleeExp(),
new EquipBallisticExp(),
new EquipHitscanExp(),
new PickUpHitscanFromChargersExp(),
new ChargeEquippedHitscanExp(),
new RangedAttackNearbySpeciesExp(),
new PickUpMeleeWeaponExp(), new PickUpMeleeWeaponExp(),
new MeleeAttackNearbySpeciesExp(), new MeleeAttackNearbyPlayerExp(),
}; };
} }
} }

View File

@@ -1,39 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
{
public class BallisticAmmoCon : Consideration
{
public BallisticAmmoCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var weapon = context.GetState<WeaponEntityState>().GetValue();
if (weapon == null || !weapon.TryGetComponent(out BallisticMagazineWeaponComponent ballistic))
{
return 0.0f;
}
var contained = ballistic.MagazineSlot.ContainedEntity;
if (contained == null)
{
return 0.0f;
}
var mag = contained.GetComponent<BallisticMagazineComponent>();
if (mag.CountLoaded == 0)
{
// TODO: Do this better
return ballistic.GetChambered(0) != null ? 1.0f : 0.0f;
}
return (float) mag.CountLoaded / mag.Capacity;
}
}
}

View File

@@ -1,25 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
{
public class BallisticWeaponEquippedCon : Consideration
{
public BallisticWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var equipped = context.GetState<EquippedEntityState>().GetValue();
if (equipped == null)
{
return 0.0f;
}
// Maybe change this to BallisticMagazineWeapon
return equipped.HasComponent<BallisticMagazineWeaponComponent>() ? 1.0f : 0.0f;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
{
public class EquippedBallisticCon : Consideration
{
public EquippedBallisticCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var equipped = context.GetState<EquippedEntityState>().GetValue();
if (equipped == null || !equipped.HasComponent<BallisticMagazineWeaponComponent>())
{
return 0.0f;
}
return 1.0f;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
{
public class HasTargetLosCon : Consideration
{
public HasTargetLosCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
var target = context.GetState<TargetEntityState>().GetValue();
if (target == null)
{
return 0.0f;
}
return Visibility.InLineOfSight(owner, target) ? 1.0f : 0.0f;
}
}
}

View File

@@ -1,29 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Melee;
using Content.Server.GameObjects.Components.Weapon.Ranged;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
{
public sealed class HeldRangedWeaponsCon : Consideration
{
public HeldRangedWeaponsCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var count = 0;
const int max = 3;
foreach (var item in context.GetState<InventoryState>().GetValue())
{
if (item.HasComponent<RangedWeaponComponent>())
{
count++;
}
}
return (float) count / max;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
{
public sealed class EquippedHitscanCon : Consideration
{
public EquippedHitscanCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var equipped = context.GetState<EquippedEntityState>().GetValue();
if (equipped == null || !equipped.HasComponent<HitscanWeaponComponent>())
{
return 0.0f;
}
return 1.0f;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
{
public sealed class HitscanChargeCon : Consideration
{
public HitscanChargeCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var weapon = context.GetState<WeaponEntityState>().GetValue();
if (weapon == null || !weapon.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
{
return 0.0f;
}
return hitscanWeaponComponent.CapacitorComponent.Charge / hitscanWeaponComponent.CapacitorComponent.Capacity;
}
}
}

View File

@@ -1,26 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.GameObjects.Components.Power.Chargers;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
{
public sealed class HitscanChargerFullCon : Consideration
{
public HitscanChargerFullCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var target = context.GetState<TargetEntityState>().GetValue();
if (target == null ||
!target.TryGetComponent(out WeaponCapacitorChargerComponent chargerComponent) ||
chargerComponent.HeldItem != null)
{
return 1.0f;
}
return 0.0f;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.GameObjects.Components.Power.Chargers;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
{
public sealed class HitscanChargerRateCon : Consideration
{
public HitscanChargerRateCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var target = context.GetState<TargetEntityState>().GetValue();
if (target == null || !target.TryGetComponent(out WeaponCapacitorChargerComponent weaponCharger))
{
return 0.0f;
}
// AI don't care about efficiency, psfft!
return weaponCharger.TransferRatio;
}
}
}

View File

@@ -1,25 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
{
public sealed class HitscanWeaponDamageCon : Consideration
{
public HitscanWeaponDamageCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var weapon = context.GetState<WeaponEntityState>().GetValue();
if (weapon == null || !weapon.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
{
return 0.0f;
}
// Just went with max health
return hitscanWeaponComponent.Damage / 300.0f;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
{
public sealed class HitscanWeaponEquippedCon : Consideration
{
public HitscanWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var equipped = context.GetState<EquippedEntityState>().GetValue();
if (equipped == null)
{
return 0.0f;
}
return equipped.HasComponent<HitscanWeaponComponent>() ? 1.0f : 0.0f;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Ranged;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
{
public sealed class RangedWeaponEquippedCon : Consideration
{
public RangedWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var equipped = context.GetState<EquippedEntityState>().GetValue();
if (equipped == null || !equipped.HasComponent<RangedWeaponComponent>())
{
return 0.0f;
}
return 1.0f;
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.AI.Utility.Curves;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States.Combat;
using Content.Server.GameObjects.Components.Weapon.Ranged;
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
{
public class RangedWeaponFireRateCon : Consideration
{
public RangedWeaponFireRateCon(IResponseCurve curve) : base(curve) {}
public override float GetScore(Blackboard context)
{
var weapon = context.GetState<WeaponEntityState>().GetValue();
if (weapon == null || !weapon.TryGetComponent(out RangedWeaponComponent ranged))
{
return 0.0f;
}
return ranged.FireRate / 100.0f;
}
}
}

View File

@@ -31,6 +31,12 @@ namespace Content.Server.AI.Utility.Considerations.Containers
return 0.0f; return 0.0f;
} }
} }
else
{
// If we're in a container (e.g. held or whatever) then we probably can't get it. Only exception
// Is a locker / crate
return 0.0f;
}
} }
return 1.0f; return 1.0f;

View File

@@ -17,9 +17,8 @@ namespace Content.Server.AI.Utility.Considerations.Movement
return 0.0f; return 0.0f;
} }
// TODO: Remove 1 -
// Kind of just pulled a max distance out of nowhere. Add 0.01 just in case it's reaally far and we have no choice so it'll still be considered at least. // Kind of just pulled a max distance out of nowhere. Add 0.01 just in case it's reaally far and we have no choice so it'll still be considered at least.
return 1 - ((target.Transform.GridPosition.Position - self.Transform.GridPosition.Position).Length / 100 + 0.01f); return (target.Transform.GridPosition.Position - self.Transform.GridPosition.Position).Length / 100 + 0.01f;
} }
} }
} }

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic
{
public sealed class DropEmptyBallisticExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
foreach (var entity in context.GetState<InventoryState>().GetValue())
{
if (entity.HasComponent<BallisticMagazineWeaponComponent>())
{
yield return new DropEmptyBallistic(owner, entity, Bonus);
}
}
}
}
}

View File

@@ -1,24 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Inventory;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic
{
public sealed class EquipBallisticExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
foreach (var entity in context.GetState<InventoryState>().GetValue())
{
yield return new EquipBallistic(owner, entity, Bonus);
}
}
}
}

View File

@@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic
{
public sealed class PickUpAmmoExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
if (!owner.TryGetComponent(out AiControllerComponent controller))
{
throw new InvalidOperationException();
}
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(BallisticMagazineComponent),
controller.VisionRadius))
{
yield return new PickUpAmmo(owner, entity, Bonus);
}
}
}
}

View File

@@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Power.Chargers;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
{
public sealed class ChargeEquippedHitscanExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
if (!owner.TryGetComponent(out AiControllerComponent controller))
{
throw new InvalidOperationException();
}
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(WeaponCapacitorChargerComponent),
controller.VisionRadius))
{
yield return new PutHitscanInCharger(owner, entity, Bonus);
}
}
}
}

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
{
public class DropEmptyHitscanExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
foreach (var entity in context.GetState<InventoryState>().GetValue())
{
if (entity.HasComponent<HitscanWeaponComponent>())
{
yield return new DropEmptyHitscan(owner, entity, Bonus);
}
}
}
}
}

View File

@@ -1,24 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Inventory;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
{
public sealed class EquipHitscanExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
foreach (var entity in context.GetState<InventoryState>().GetValue())
{
yield return new EquipHitscan(owner, entity, Bonus);
}
}
}
}

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Power.Chargers;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
{
public sealed class PickUpHitscanFromChargersExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
if (!owner.TryGetComponent(out AiControllerComponent controller))
{
throw new InvalidOperationException();
}
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(WeaponCapacitorChargerComponent),
controller.VisionRadius))
{
var contained = entity.GetComponent<WeaponCapacitorChargerComponent>().HeldItem;
if (contained != null)
{
yield return new PickUpHitscanFromCharger(owner, entity, contained, Bonus);
}
}
}
}
}

View File

@@ -1,35 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Combat.Nearby;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged
{
public sealed class PickUpRangedExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatPrepBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
foreach (var entity in context.GetState<NearbyRangedWeapons>().GetValue())
{
if (entity.HasComponent<HitscanWeaponComponent>())
{
yield return new PickUpHitscanWeapon(owner, entity, Bonus);
}
if (entity.HasComponent<BallisticMagazineWeaponComponent>())
{
yield return new PickUpBallisticMagWeapon(owner, entity, Bonus);
}
}
}
}
}

View File

@@ -1,26 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Mobs;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged
{
public sealed class RangedAttackNearbySpeciesExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
foreach (var entity in context.GetState<NearbySpeciesState>().GetValue())
{
yield return new HitscanAttackEntity(owner, entity, Bonus);
yield return new BallisticAttackEntity(owner, entity, Bonus);
}
}
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utils;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Power.Chargers;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.WorldState.States.Combat.Nearby
{
[UsedImplicitly]
public sealed class NearbyLaserChargersState : StateData<List<IEntity>>
{
public override string Name => "NearbyLaserChargers";
public override List<IEntity> GetValue()
{
var nearby = new List<IEntity>();
if (!Owner.TryGetComponent(out AiControllerComponent controller))
{
return nearby;
}
foreach (var result in Visibility
.GetNearestEntities(Owner.Transform.GridPosition, typeof(WeaponCapacitorChargerComponent), controller.VisionRadius))
{
nearby.Add(result);
}
return nearby;
}
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utils;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.WorldState.States.Combat.Nearby
{
[UsedImplicitly]
public sealed class NearbyLaserWeapons : StateData<List<IEntity>>
{
public override string Name => "NearbyLaserWeapons";
public override List<IEntity> GetValue()
{
var result = new List<IEntity>();
if (!Owner.TryGetComponent(out AiControllerComponent controller))
{
return result;
}
foreach (var entity in Visibility
.GetNearestEntities(Owner.Transform.GridPosition, typeof(HitscanWeaponComponent), controller.VisionRadius))
{
result.Add(entity);
}
return result;
}
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utils;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Weapon.Ranged;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.WorldState.States.Combat.Nearby
{
[UsedImplicitly]
public sealed class NearbyRangedWeapons : CachedStateData<List<IEntity>>
{
public override string Name => "NearbyRangedWeapons";
protected override List<IEntity> GetTrueValue()
{
var result = new List<IEntity>();
if (!Owner.TryGetComponent(out AiControllerComponent controller))
{
return result;
}
foreach (var entity in Visibility
.GetNearestEntities(Owner.Transform.GridPosition, typeof(RangedWeaponComponent), controller.VisionRadius))
{
result.Add(entity);
}
return result;
}
}
}

View File

@@ -1,16 +0,0 @@
using JetBrains.Annotations;
namespace Content.Server.AI.WorldState.States.Combat.Ranged
{
[UsedImplicitly]
public sealed class Accuracy : StateData<float>
{
public override string Name => "Accuracy";
public override float GetValue()
{
// TODO: Maybe just make it a SetValue (maybe make a third type besides sensor / daemon called settablestate)
return 1.0f;
}
}
}

View File

@@ -1,17 +0,0 @@
using JetBrains.Annotations;
namespace Content.Server.AI.WorldState.States.Combat.Ranged
{
/// <summary>
/// How long to wait between bursts
/// </summary>
[UsedImplicitly]
public sealed class BurstCooldown : PlanningStateData<float>
{
public override string Name => "BurstCooldown";
public override void Reset()
{
Value = 0.0f;
}
}
}

View File

@@ -1,39 +0,0 @@
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
using JetBrains.Annotations;
namespace Content.Server.AI.WorldState.States.Combat.Ranged
{
/// <summary>
/// Gets the discrete ammo count
/// </summary>
[UsedImplicitly]
public sealed class EquippedRangedWeaponAmmo : StateData<int?>
{
public override string Name => "EquippedRangedWeaponAmmo";
public override int? GetValue()
{
if (!Owner.TryGetComponent(out HandsComponent handsComponent))
{
return null;
}
var equippedItem = handsComponent.GetActiveHand?.Owner;
if (equippedItem == null) return null;
if (equippedItem.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
{
return (int) hitscanWeaponComponent.CapacitorComponent.Charge / hitscanWeaponComponent.BaseFireCost;
}
if (equippedItem.TryGetComponent(out BallisticMagazineWeaponComponent ballisticComponent))
{
return ballisticComponent.MagazineSlot.ContainedEntities.Count;
}
return null;
}
}
}

View File

@@ -1,17 +0,0 @@
using JetBrains.Annotations;
namespace Content.Server.AI.WorldState.States.Combat.Ranged
{
/// <summary>
/// How many shots to take before cooling down
/// </summary>
[UsedImplicitly]
public sealed class MaxBurstCount : PlanningStateData<int>
{
public override string Name => "BurstCount";
public override void Reset()
{
Value = 0;
}
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Access
{
public sealed class AccessReaderChangeMessage : EntitySystemMessage
{
public EntityUid Uid { get; }
public bool Enabled { get; }
public AccessReaderChangeMessage(EntityUid uid, bool enabled)
{
Uid = uid;
Enabled = enabled;
}
}
}

View File

@@ -69,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Access
} }
[CanBeNull] [CanBeNull]
private static ICollection<string> FindAccessTags(IEntity entity) public static ICollection<string> FindAccessTags(IEntity entity)
{ {
if (entity.TryGetComponent(out IAccess accessComponent)) if (entity.TryGetComponent(out IAccess accessComponent))
{ {

View File

@@ -17,6 +17,7 @@ using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.Components.Chemistry namespace Content.Server.GameObjects.Components.Chemistry
@@ -219,12 +220,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
protected override void GetData(IEntity user, SolutionComponent component, VerbData data) protected override void GetData(IEntity user, SolutionComponent component, VerbData data)
{ {
if (user.TryGetComponent<HandsComponent>(out var hands)) if (!ActionBlockerSystem.CanInteract(user) ||
{ !user.TryGetComponent<HandsComponent>(out var hands) ||
if (hands.GetActiveHand != null) hands.GetActiveHand == null ||
{ !hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
{ {
data.Visibility = VerbVisibility.Invisible;
return;
}
if ((solution.Capabilities & SolutionCaps.PourOut) != 0 && if ((solution.Capabilities & SolutionCaps.PourOut) != 0 &&
(component.Capabilities & SolutionCaps.PourIn) != 0) (component.Capabilities & SolutionCaps.PourIn) != 0)
{ {
@@ -234,9 +238,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
data.Text= $"Transfer liquid from [{heldEntityName}] to [{myName}]."; data.Text= $"Transfer liquid from [{heldEntityName}] to [{myName}].";
return; return;
} }
}
}
}
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
} }
@@ -318,12 +319,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
protected override void GetData(IEntity user, SolutionComponent component, VerbData data) protected override void GetData(IEntity user, SolutionComponent component, VerbData data)
{ {
if (user.TryGetComponent<HandsComponent>(out var hands)) if (!ActionBlockerSystem.CanInteract(user) ||
{ !user.TryGetComponent<HandsComponent>(out var hands) ||
if (hands.GetActiveHand != null) hands.GetActiveHand == null ||
{ !hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
{ {
data.Visibility = VerbVisibility.Invisible;
return;
}
if ((solution.Capabilities & SolutionCaps.PourIn) != 0 && if ((solution.Capabilities & SolutionCaps.PourIn) != 0 &&
(component.Capabilities & SolutionCaps.PourOut) != 0) (component.Capabilities & SolutionCaps.PourOut) != 0)
{ {
@@ -333,9 +337,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
data.Text = $"Transfer liquid from [{myName}] to [{heldEntityName}]."; data.Text = $"Transfer liquid from [{myName}] to [{heldEntityName}].";
return; return;
} }
}
}
}
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
} }

View File

@@ -154,6 +154,8 @@ namespace Content.Server.GameObjects
State = DoorState.Open; State = DoorState.Open;
SetAppearance(DoorVisualState.Open); SetAppearance(DoorVisualState.Open);
}, _cancellationTokenSource.Token); }, _cancellationTokenSource.Token);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner.Uid, false));
} }
public virtual bool CanClose() public virtual bool CanClose()
@@ -203,6 +205,7 @@ namespace Content.Server.GameObjects
occluder.Enabled = true; occluder.Enabled = true;
} }
}, _cancellationTokenSource.Token); }, _cancellationTokenSource.Token);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner.Uid, true));
return true; return true;
} }

View File

@@ -0,0 +1,68 @@
using Content.Server.GameObjects.Components.Weapon;
using Content.Server.GameObjects.EntitySystems;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Explosion
{
/// <summary>
/// When triggered will flash in an area around the object and destroy itself
/// </summary>
[RegisterComponent]
public class FlashExplosiveComponent : Component, ITimerTrigger, IDestroyAct
{
public override string Name => "FlashExplosive";
private float _range;
private double _duration;
private string _sound;
private bool _deleteOnFlash;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _range, "range", 7.0f);
serializer.DataField(ref _duration, "duration", 8.0);
serializer.DataField(ref _sound, "sound", "/Audio/effects/flash_bang.ogg");
serializer.DataField(ref _deleteOnFlash, "deleteOnFlash", true);
}
public bool Explode()
{
// If we're in a locker or whatever then can't flash anything
ContainerHelpers.TryGetContainer(Owner, out var container);
if (container == null || !container.Owner.HasComponent<EntityStorageComponent>())
{
ServerFlashableComponent.FlashAreaHelper(Owner, _range, _duration);
}
if (_sound != null)
{
EntitySystem.Get<AudioSystem>().PlayAtCoords(_sound, Owner.Transform.GridPosition);
}
if (_deleteOnFlash && !Owner.Deleted)
{
Owner.Delete();
}
return true;
}
bool ITimerTrigger.Trigger(TimerTriggerEventArgs eventArgs)
{
return Explode();
}
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
{
Explode();
}
}
}

View File

@@ -1,7 +1,9 @@
using System; using System;
using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -21,7 +23,8 @@ namespace Content.Server.GameObjects.Components.Fluids
{ {
protected override void GetData(IEntity user, CanSpillComponent component, VerbData data) protected override void GetData(IEntity user, CanSpillComponent component, VerbData data)
{ {
if (!component.Owner.TryGetComponent(out SolutionComponent solutionComponent)) if (!ActionBlockerSystem.CanInteract(user) ||
!component.Owner.TryGetComponent(out SolutionComponent solutionComponent))
{ {
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
return; return;

View File

@@ -23,7 +23,7 @@ using static Content.Shared.GameObjects.SharedInventoryComponent.ClientInventory
namespace Content.Server.GameObjects namespace Content.Server.GameObjects
{ {
[RegisterComponent] [RegisterComponent]
public class InventoryComponent : SharedInventoryComponent public class InventoryComponent : SharedInventoryComponent, IExAct
{ {
#pragma warning disable 649 #pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager; [Dependency] private readonly IEntitySystemManager _entitySystemManager;
@@ -397,5 +397,25 @@ namespace Content.Server.GameObjects
} }
return new InventoryComponentState(list); return new InventoryComponentState(list);
} }
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
{
if (eventArgs.Severity < ExplosionSeverity.Heavy)
{
return;
}
foreach (var slot in SlotContainers.Values.ToList())
{
foreach (var entity in slot.ContainedEntities)
{
var exActs = entity.GetAllComponents<IExAct>();
foreach (var exAct in exActs)
{
exAct.OnExplosion(eventArgs);
}
}
}
}
} }
} }

View File

@@ -3,6 +3,7 @@
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects; using Content.Server.Interfaces.GameObjects;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
@@ -68,6 +69,8 @@ namespace Content.Server.GameObjects
AddHand(handsname); AddHand(handsname);
} }
} }
serializer.DataField(ref activeIndex, "defaultHand", orderedHands.LastOrDefault());
} }
public IEnumerable<ItemComponent> GetAllHeldItems() public IEnumerable<ItemComponent> GetAllHeldItems()

View File

@@ -5,6 +5,7 @@ using Content.Server.Interfaces.GameObjects;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
@@ -240,6 +241,12 @@ namespace Content.Server.GameObjects.Components.Interactable
{ {
protected override void GetData(IEntity user, HandheldLightComponent component, VerbData data) protected override void GetData(IEntity user, HandheldLightComponent component, VerbData data)
{ {
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component.Cell == null) if (component.Cell == null)
{ {
data.Text = "Eject cell (cell missing)"; data.Text = "Eject cell (cell missing)";

View File

@@ -7,6 +7,7 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.GameObjects.Components.Storage; using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
@@ -338,8 +339,13 @@ namespace Content.Server.GameObjects.Components
{ {
protected override void GetData(IEntity user, EntityStorageComponent component, VerbData data) protected override void GetData(IEntity user, EntityStorageComponent component, VerbData data)
{ {
component.OpenVerbGetData(user, component, data); if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
component.OpenVerbGetData(user, component, data);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -351,6 +357,12 @@ namespace Content.Server.GameObjects.Components
protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data) protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data)
{ {
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (IsWeldedShut) if (IsWeldedShut)
{ {
data.Visibility = VerbVisibility.Disabled; data.Visibility = VerbVisibility.Disabled;

View File

@@ -117,7 +117,9 @@ namespace Content.Server.GameObjects
{ {
protected override void GetData(IEntity user, ItemComponent component, VerbData data) protected override void GetData(IEntity user, ItemComponent component, VerbData data)
{ {
if (ContainerHelpers.IsInContainer(component.Owner) || !component.CanPickup(user)) if (!ActionBlockerSystem.CanInteract(user) ||
ContainerHelpers.IsInContainer(component.Owner) ||
!component.CanPickup(user))
{ {
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
return; return;

View File

@@ -3,6 +3,7 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces; using Content.Server.Interfaces;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Storage; using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
@@ -139,7 +140,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
{ {
protected override void GetData(IEntity user, SecureEntityStorageComponent component, VerbData data) protected override void GetData(IEntity user, SecureEntityStorageComponent component, VerbData data)
{ {
if (component.Open) if (!ActionBlockerSystem.CanInteract(user) || component.Open)
{ {
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
return; return;

View File

@@ -32,7 +32,7 @@ namespace Content.Server.GameObjects
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IStorageComponent))] [ComponentReference(typeof(IStorageComponent))]
public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct
{ {
#pragma warning disable 649 #pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager; [Dependency] private readonly IMapManager _mapManager;
@@ -364,6 +364,24 @@ namespace Content.Server.GameObjects
} }
} }
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
{
if (eventArgs.Severity < ExplosionSeverity.Heavy)
{
return;
}
var storedEntities = storage.ContainedEntities.ToList();
foreach (var entity in storedEntities)
{
var exActs = entity.GetAllComponents<IExAct>();
foreach (var exAct in exActs)
{
exAct.OnExplosion(eventArgs);
}
}
}
/// <summary> /// <summary>
/// Inserts an entity into the storage component from the players active hand. /// Inserts an entity into the storage component from the players active hand.
/// </summary> /// </summary>

View File

@@ -2,18 +2,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using Content.Server.Interfaces.GameTicking; using Content.Server.Interfaces.GameTicking;
using NFluidsynth;
using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random; using Robust.Shared.Interfaces.Random;
using Robust.Shared.Interfaces.Reflection; using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using SQLitePCL;
using Logger = Robust.Shared.Log.Logger; using Logger = Robust.Shared.Log.Logger;
namespace Content.Server.GameObjects.Components.Markers namespace Content.Server.GameObjects.Components.Markers

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Threading;
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;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Markers
{
[RegisterComponent]
public class TimedSpawnerComponent : Component
{
#pragma warning disable 649
[Dependency] private IEntityManager _entityManager;
[Dependency] private IRobustRandom _robustRandom;
#pragma warning restore 649
public override string Name => "TimedSpawner";
[ViewVariables(VVAccess.ReadWrite)]
public List<string> Prototypes { get; set; } = new List<string>();
[ViewVariables(VVAccess.ReadWrite)]
public float Chance { get; set; } = 1.0f;
[ViewVariables(VVAccess.ReadWrite)]
public int IntervalSeconds { get; set; } = 60;
[ViewVariables(VVAccess.ReadWrite)]
public int MinimumEntitiesSpawned { get; set; } = 1;
[ViewVariables(VVAccess.ReadWrite)]
public int MaximumEntitiesSpawned { get; set; } = 1;
private CancellationTokenSource TokenSource;
public override void Initialize()
{
base.Initialize();
SetupTimer();
}
protected override void Shutdown()
{
base.Shutdown();
TokenSource.Cancel();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => Prototypes, "prototypes", new List<string>());
serializer.DataField(this, x => Chance, "chance", 1.0f);
serializer.DataField(this, x => IntervalSeconds, "intervalSeconds", 60);
serializer.DataField(this, x => MinimumEntitiesSpawned, "minimumEntitiesSpawned", 1);
serializer.DataField(this, x => MaximumEntitiesSpawned, "maximumEntitiesSpawned", 1);
if(MinimumEntitiesSpawned > MaximumEntitiesSpawned)
throw new ArgumentException("MaximumEntitiesSpawned can't be lower than MinimumEntitiesSpawned!");
}
private void SetupTimer()
{
TokenSource?.Cancel();
TokenSource = new CancellationTokenSource();
Timer.SpawnRepeating(TimeSpan.FromSeconds(IntervalSeconds), OnTimerFired, TokenSource.Token);
}
private void OnTimerFired()
{
if (!_robustRandom.Prob(Chance))
return;
var number = _robustRandom.Next(MinimumEntitiesSpawned, MaximumEntitiesSpawned);
for (int i = 0; i < number; i++)
{
var entity = _robustRandom.Pick(Prototypes);
_entityManager.SpawnEntity(entity, Owner.Transform.GridPosition);
}
}
}
}

View File

@@ -5,6 +5,7 @@ using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Medical; using Content.Shared.GameObjects.Components.Medical;
using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.Components.Power;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.Components.UserInterface;
@@ -133,6 +134,12 @@ namespace Content.Server.GameObjects.Components.Medical
{ {
protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data) protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data)
{ {
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = "Enter"; data.Text = "Enter";
data.Visibility = component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible; data.Visibility = component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible;
} }
@@ -148,6 +155,12 @@ namespace Content.Server.GameObjects.Components.Medical
{ {
protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data) protected override void GetData(IEntity user, MedicalScannerComponent component, VerbData data)
{ {
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = "Eject"; data.Text = "Eject";
data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible; data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible;
} }

View File

@@ -187,6 +187,17 @@ namespace Content.Server.GameObjects.Components.Mobs
_statusRemoveCancellation = new CancellationTokenSource(); _statusRemoveCancellation = new CancellationTokenSource();
} }
public void ResetStuns()
{
_stunnedTimer = 0f;
_slowdownTimer = 0f;
if (KnockedDown)
StandingStateHelper.Standing(Owner);
_knockdownTimer = 0f;
}
public void Update(float delta) public void Update(float delta)
{ {
if (Stunned) if (Stunned)

View File

@@ -59,7 +59,7 @@ namespace Content.Server.GameObjects.Components.Movement
protected override void Shutdown() protected override void Shutdown()
{ {
base.Shutdown(); base.Shutdown();
Processor.Shutdown(); Processor?.Shutdown();
} }
/// <summary> /// <summary>
@@ -125,6 +125,6 @@ namespace Content.Server.GameObjects.Components.Movement
[ViewVariables(VVAccess.ReadWrite)] public float StepSoundDistance { get; set; } [ViewVariables(VVAccess.ReadWrite)] public float StepSoundDistance { get; set; }
public void SetVelocityDirection(Direction direction, ushort subTick, bool enabled) { } public void SetVelocityDirection(Direction direction, ushort subTick, bool enabled) { }
public void SetSprinting(ushort subTick, bool enabled) { } public void SetSprinting(ushort subTick, bool walking) { }
} }
} }

View File

@@ -74,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Movement
} }
} }
public void SetSprinting(ushort subTick, bool enabled) public void SetSprinting(ushort subTick, bool walking)
{ {
// Shuttles can't sprint. // Shuttles can't sprint.
} }

View File

@@ -1,8 +1,11 @@
using System; using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.Sound; using Content.Server.GameObjects.Components.Utensil;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Utensil;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -10,6 +13,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -32,18 +36,28 @@ namespace Content.Server.GameObjects.Components.Nutrition
private SolutionComponent _contents; private SolutionComponent _contents;
[ViewVariables] [ViewVariables]
private ReagentUnit _transferAmount; private ReagentUnit _transferAmount;
private UtensilType _utensilsNeeded;
public int UsesRemaining => _contents.CurrentVolume == 0 public int UsesRemaining => _contents.CurrentVolume == 0
? ?
0 : Math.Max(1, (int)Math.Ceiling((_contents.CurrentVolume / _transferAmount).Float())); 0 : Math.Max(1, (int)Math.Ceiling((_contents.CurrentVolume / _transferAmount).Float()));
public override void ExposeData(ObjectSerializer serializer) public override void ExposeData(ObjectSerializer serializer)
{ {
base.ExposeData(serializer); base.ExposeData(serializer);
serializer.DataField(ref _useSound, "useSound", "/Audio/items/eatfood.ogg"); serializer.DataField(ref _useSound, "useSound", "/Audio/items/eatfood.ogg");
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5)); serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5));
serializer.DataField(ref _trashPrototype, "trash", "TrashPlate"); serializer.DataField(ref _trashPrototype, "trash", null);
if (serializer.Reading)
{
var utensils = serializer.ReadDataField("utensils", new List<UtensilType>());
foreach (var utensil in utensils)
{
_utensilsNeeded |= utensil;
Dirty();
}
}
} }
public override void Initialize() public override void Initialize()
@@ -55,15 +69,27 @@ namespace Content.Server.GameObjects.Components.Nutrition
bool IUse.UseEntity(UseEntityEventArgs eventArgs) bool IUse.UseEntity(UseEntityEventArgs eventArgs)
{ {
if (_utensilsNeeded != UtensilType.None)
{
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You need to use a {0} to eat that!", _utensilsNeeded));
return false;
}
return TryUseFood(eventArgs.User, null); return TryUseFood(eventArgs.User, null);
} }
// Feeding someone else
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
if (eventArgs.Target == null)
{
return;
}
TryUseFood(eventArgs.User, eventArgs.Target); TryUseFood(eventArgs.User, eventArgs.Target);
} }
private bool TryUseFood(IEntity user, IEntity target) public bool TryUseFood(IEntity user, IEntity target, UtensilComponent utensilUsed = null)
{ {
if (user == null) if (user == null)
{ {
@@ -78,20 +104,65 @@ namespace Content.Server.GameObjects.Components.Nutrition
var trueTarget = target ?? user; var trueTarget = target ?? user;
if (trueTarget.TryGetComponent(out StomachComponent stomachComponent)) if (!trueTarget.TryGetComponent(out StomachComponent stomach))
{ {
return false;
}
var utensils = utensilUsed != null
? new List<UtensilComponent> {utensilUsed}
: null;
if (_utensilsNeeded != UtensilType.None)
{
utensils = new List<UtensilComponent>();
var types = UtensilType.None;
if (user.TryGetComponent(out HandsComponent hands))
{
foreach (var item in hands.GetAllHeldItems())
{
if (!item.Owner.TryGetComponent(out UtensilComponent utensil))
{
continue;
}
utensils.Add(utensil);
types |= utensil.Types;
}
}
if (!types.HasFlag(_utensilsNeeded))
{
trueTarget.PopupMessage(user, Loc.GetString("You need to be holding a {0} to eat that!", _utensilsNeeded));
return false;
}
}
if (!InteractionChecks.InRangeUnobstructed(user, trueTarget.Transform.MapPosition))
{
return false;
}
var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume); var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume);
var split = _contents.SplitSolution(transferAmount); var split = _contents.SplitSolution(transferAmount);
if (stomachComponent.TryTransferSolution(split)) if (!stomach.TryTransferSolution(split))
{
_entitySystem.GetEntitySystem<AudioSystem>()
.PlayFromEntity(_useSound, trueTarget, AudioParams.Default.WithVolume(-1f));
trueTarget.PopupMessage(user, Loc.GetString("Nom"));
}
else
{ {
_contents.TryAddSolution(split); _contents.TryAddSolution(split);
trueTarget.PopupMessage(user, Loc.GetString("You can't eat any more!")); trueTarget.PopupMessage(user, Loc.GetString("You can't eat any more!"));
return false;
}
_entitySystem.GetEntitySystem<AudioSystem>()
.PlayFromEntity(_useSound, trueTarget, AudioParams.Default.WithVolume(-1f));
trueTarget.PopupMessage(user, Loc.GetString("Nom"));
// If utensils were used
if (utensils != null)
{
foreach (var utensil in utensils)
{
utensil.TryBreak(user);
} }
} }
@@ -100,21 +171,35 @@ namespace Content.Server.GameObjects.Components.Nutrition
return true; return true;
} }
if (string.IsNullOrEmpty(_trashPrototype))
{
Owner.Delete();
return true;
}
//We're empty. Become trash. //We're empty. Become trash.
var position = Owner.Transform.GridPosition; var position = Owner.Transform.GridPosition;
Owner.Delete();
var finisher = Owner.EntityManager.SpawnEntity(_trashPrototype, position); var finisher = Owner.EntityManager.SpawnEntity(_trashPrototype, position);
if (user.TryGetComponent(out HandsComponent handsComponent) && finisher.TryGetComponent(out ItemComponent itemComponent))
{
if (handsComponent.CanPutInHand(itemComponent))
{
handsComponent.PutInHand(itemComponent);
return true;
}
}
finisher.Transform.GridPosition = user.Transform.GridPosition;
return true;
// If the user is holding the item
if (user.TryGetComponent(out HandsComponent handsComponent) &&
handsComponent.IsHolding(Owner))
{
Owner.Delete();
// Put the trash in the user's hand
if (finisher.TryGetComponent(out ItemComponent item) &&
handsComponent.CanPutInHand(item))
{
handsComponent.PutInHand(item);
}
}
else
{
Owner.Delete();
}
return true;
} }
} }
} }

View File

@@ -8,6 +8,7 @@ using Content.Server.Interfaces;
using Content.Server.Interfaces.PDA; using Content.Server.Interfaces.PDA;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.PDA; using Content.Shared.GameObjects.Components.PDA;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.Components.UserInterface;
@@ -250,6 +251,12 @@ namespace Content.Server.GameObjects.Components.PDA
{ {
protected override void GetData(IEntity user, PDAComponent component, VerbData data) protected override void GetData(IEntity user, PDAComponent component, VerbData data)
{ {
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("Eject ID"); data.Text = Loc.GetString("Eject ID");
data.Visibility = component.IdSlotEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible; data.Visibility = component.IdSlotEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible;
} }

Some files were not shown because too many files have changed in this diff Show More