Merge remote-tracking branch 'upstream/master' into weapon_anims
# Conflicts: # Resources/Prototypes/MeleeWeaponAnimations/default.yml
@@ -2,9 +2,13 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
using Robust.Shared.Exceptions;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Log;
|
||||||
|
using Robust.Shared.Interfaces.Reflection;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
namespace Content.Benchmarks
|
namespace Content.Benchmarks
|
||||||
{
|
{
|
||||||
@@ -14,7 +18,17 @@ namespace Content.Benchmarks
|
|||||||
|
|
||||||
private IComponentManager _componentManager;
|
private IComponentManager _componentManager;
|
||||||
|
|
||||||
[Params(500, 1000, 5000)] public int N { get; set; }
|
[Params(5000)] public int N { get; set; }
|
||||||
|
|
||||||
|
public static void TestRun()
|
||||||
|
{
|
||||||
|
var x = new ComponentManagerGetAllComponents
|
||||||
|
{
|
||||||
|
N = 500
|
||||||
|
};
|
||||||
|
x.Setup();
|
||||||
|
x.Run();
|
||||||
|
}
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
@@ -23,6 +37,13 @@ namespace Content.Benchmarks
|
|||||||
IoCManager.InitThread();
|
IoCManager.InitThread();
|
||||||
|
|
||||||
IoCManager.Register<IComponentManager, ComponentManager>();
|
IoCManager.Register<IComponentManager, ComponentManager>();
|
||||||
|
IoCManager.Register<IRuntimeLog, RuntimeLog>();
|
||||||
|
IoCManager.Register<ILogManager, LogManager>();
|
||||||
|
IoCManager.Register<IDynamicTypeFactory, DynamicTypeFactory>();
|
||||||
|
IoCManager.Register<IEntitySystemManager, EntitySystemManager>();
|
||||||
|
var entityManager = new Mock<IEntityManager>().Object;
|
||||||
|
IoCManager.RegisterInstance<IEntityManager>(entityManager);
|
||||||
|
IoCManager.RegisterInstance<IReflectionManager>(new Mock<IReflectionManager>().Object);
|
||||||
|
|
||||||
var dummyReg = new Mock<IComponentRegistration>();
|
var dummyReg = new Mock<IComponentRegistration>();
|
||||||
dummyReg.SetupGet(p => p.Name).Returns("Dummy");
|
dummyReg.SetupGet(p => p.Name).Returns("Dummy");
|
||||||
@@ -34,17 +55,19 @@ namespace Content.Benchmarks
|
|||||||
var componentFactory = new Mock<IComponentFactory>();
|
var componentFactory = new Mock<IComponentFactory>();
|
||||||
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
|
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
|
||||||
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg.Object);
|
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg.Object);
|
||||||
|
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] {typeof(DummyComponent)});
|
||||||
|
|
||||||
IoCManager.RegisterInstance<IComponentFactory>(componentFactory.Object);
|
IoCManager.RegisterInstance<IComponentFactory>(componentFactory.Object);
|
||||||
|
|
||||||
IoCManager.BuildGraph();
|
IoCManager.BuildGraph();
|
||||||
|
|
||||||
_componentManager = IoCManager.Resolve<IComponentManager>();
|
_componentManager = IoCManager.Resolve<IComponentManager>();
|
||||||
|
_componentManager.Initialize();
|
||||||
|
|
||||||
// Initialize N entities with one component.
|
// Initialize N entities with one component.
|
||||||
for (var i = 0; i < N; i++)
|
for (var i = 0; i < N; i++)
|
||||||
{
|
{
|
||||||
var entity = new Entity();
|
var entity = new Entity();
|
||||||
|
entity.SetManagers(entityManager);
|
||||||
entity.SetUid(new EntityUid(i + 1));
|
entity.SetUid(new EntityUid(i + 1));
|
||||||
_entities.Add(entity);
|
_entities.Add(entity);
|
||||||
|
|
||||||
@@ -65,6 +88,16 @@ namespace Content.Benchmarks
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int Noop()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
_componentManager.TryGetComponent(default, out DummyComponent _);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
private class DummyComponent : Component
|
private class DummyComponent : Component
|
||||||
{
|
{
|
||||||
public override string Name => "Dummy";
|
public override string Name => "Dummy";
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ namespace Content.Benchmarks
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
BenchmarkRunner.Run<StereoToMonoBenchmark>();
|
BenchmarkRunner.Run<ComponentManagerGetAllComponents>();
|
||||||
|
//ComponentManagerGetAllComponents.TestRun();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ namespace Content.Client.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
return !anyAction;
|
return !anyAction;
|
||||||
#endif
|
#else
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,9 @@ namespace Content.Client.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
return !anyAction;
|
return !anyAction;
|
||||||
#endif
|
#else
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
Content.Client/Commands/ToggleOutlineCommand.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
@@ -223,6 +225,8 @@ namespace Content.Client
|
|||||||
{
|
{
|
||||||
IoCManager.Resolve<IMapManager>().CreateNewMapEntity(MapId.Nullspace);
|
IoCManager.Resolve<IMapManager>().CreateNewMapEntity(MapId.Nullspace);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_configurationManager.RegisterCVar("outline.enabled", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -249,7 +253,10 @@ namespace Content.Client
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void DetachPlayerFromEntity(EntityDetachedEventArgs eventArgs)
|
public static void DetachPlayerFromEntity(EntityDetachedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
eventArgs.OldEntity.RemoveComponent<CharacterInterface>();
|
if (!eventArgs.OldEntity.Deleted)
|
||||||
|
{
|
||||||
|
eventArgs.OldEntity.RemoveComponent<CharacterInterface>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PostInit()
|
public override void PostInit()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Content.Client.Animations;
|
|||||||
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.Barrels;
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
using Robust.Client.Animations;
|
using Robust.Client.Animations;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
@@ -112,10 +113,10 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
|
|||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
case BmwComponentAutoEjectedMessage _:
|
case MagazineAutoEjectMessage _:
|
||||||
_statusControl?.PlayAlarmAnimation();
|
_statusControl?.PlayAlarmAnimation();
|
||||||
return;*/
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,10 +79,9 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.LastPosition.HasValue)
|
// Entity is no longer valid, update around the last position it was at.
|
||||||
|
if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
|
||||||
{
|
{
|
||||||
// Entity is no longer valid, update around the last position it was at.
|
|
||||||
var grid = _mapManager.GetGrid(ev.LastPosition.Value.grid);
|
|
||||||
var pos = ev.LastPosition.Value.pos;
|
var pos = ev.LastPosition.Value.pos;
|
||||||
|
|
||||||
AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(1, 0), ev.Offset));
|
AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(1, 0), ev.Offset));
|
||||||
|
|||||||
@@ -60,7 +60,11 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
|
|
||||||
private void HandleDirtyEvent(SubFloorHideDirtyEvent ev)
|
private void HandleDirtyEvent(SubFloorHideDirtyEvent ev)
|
||||||
{
|
{
|
||||||
var grid = _mapManager.GetGrid(ev.Sender.Transform.GridID);
|
if (!_mapManager.TryGetGrid(ev.Sender.Transform.GridID, out var grid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var indices = grid.WorldToTile(ev.Sender.Transform.WorldPosition);
|
var indices = grid.WorldToTile(ev.Sender.Transform.WorldPosition);
|
||||||
UpdateTile(grid, indices);
|
UpdateTile(grid, indices);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ namespace Content.IntegrationTests.Tests
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + prototype.ID + "' threw: " + e.Message);
|
Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + prototype.ID + "' threw: " + e.Message);
|
||||||
Assert.Fail();
|
//Assert.Fail();
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Server.Interfaces.Maps;
|
using Robust.Server.Interfaces.Maps;
|
||||||
@@ -37,19 +38,34 @@ namespace Content.IntegrationTests.Tests
|
|||||||
string one;
|
string one;
|
||||||
string two;
|
string two;
|
||||||
|
|
||||||
using (var stream = userData.Open(new ResourcePath("save load save 1.yml"), FileMode.Open))
|
var rp1 = new ResourcePath("save load save 1.yml");
|
||||||
|
using (var stream = userData.Open(rp1, FileMode.Open))
|
||||||
using (var reader = new StreamReader(stream))
|
using (var reader = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
one = reader.ReadToEnd();
|
one = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stream = userData.Open(new ResourcePath("save load save 2.yml"), FileMode.Open))
|
var rp2 = new ResourcePath("save load save 2.yml");
|
||||||
|
using (var stream = userData.Open(rp2, FileMode.Open))
|
||||||
using (var reader = new StreamReader(stream))
|
using (var reader = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
two = reader.ReadToEnd();
|
two = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(one, Is.EqualTo(two));
|
Assert.Multiple(() => {
|
||||||
|
Assert.That(one, Is.EqualTo(two));
|
||||||
|
var failed = TestContext.CurrentContext.Result.Assertions.FirstOrDefault();
|
||||||
|
if (failed != null)
|
||||||
|
{
|
||||||
|
var path1 = Path.Combine(userData.RootDir!,rp1.ToRelativeSystemPath());
|
||||||
|
var path2 = Path.Combine(userData.RootDir!,rp2.ToRelativeSystemPath());
|
||||||
|
TestContext.AddTestAttachment(path1);
|
||||||
|
TestContext.AddTestAttachment(path2);
|
||||||
|
TestContext.Error.WriteLine("Complete output:");
|
||||||
|
TestContext.Error.WriteLine(path1);
|
||||||
|
TestContext.Error.WriteLine(path2);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -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;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -219,23 +219,23 @@ 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) ||
|
||||||
|
hands.GetActiveHand == null ||
|
||||||
|
!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
|
||||||
{
|
{
|
||||||
if (hands.GetActiveHand != null)
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
{
|
return;
|
||||||
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
|
}
|
||||||
{
|
|
||||||
if ((solution.Capabilities & SolutionCaps.PourOut) != 0 &&
|
|
||||||
(component.Capabilities & SolutionCaps.PourIn) != 0)
|
|
||||||
{
|
|
||||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
|
||||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
|
||||||
|
|
||||||
data.Text= $"Transfer liquid from [{heldEntityName}] to [{myName}].";
|
if ((solution.Capabilities & SolutionCaps.PourOut) != 0 &&
|
||||||
return;
|
(component.Capabilities & SolutionCaps.PourIn) != 0)
|
||||||
}
|
{
|
||||||
}
|
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||||
}
|
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||||
|
|
||||||
|
data.Text= $"Transfer liquid from [{heldEntityName}] to [{myName}].";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Visibility = VerbVisibility.Invisible;
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
@@ -318,23 +318,23 @@ 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) ||
|
||||||
|
hands.GetActiveHand == null ||
|
||||||
|
!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
|
||||||
{
|
{
|
||||||
if (hands.GetActiveHand != null)
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
{
|
return;
|
||||||
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
|
}
|
||||||
{
|
|
||||||
if ((solution.Capabilities & SolutionCaps.PourIn) != 0 &&
|
|
||||||
(component.Capabilities & SolutionCaps.PourOut) != 0)
|
|
||||||
{
|
|
||||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
|
||||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
|
||||||
|
|
||||||
data.Text = $"Transfer liquid from [{myName}] to [{heldEntityName}].";
|
if ((solution.Capabilities & SolutionCaps.PourIn) != 0 &&
|
||||||
return;
|
(component.Capabilities & SolutionCaps.PourOut) != 0)
|
||||||
}
|
{
|
||||||
}
|
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||||
}
|
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||||
|
|
||||||
|
data.Text = $"Transfer liquid from [{myName}] to [{heldEntityName}].";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Visibility = VerbVisibility.Invisible;
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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 Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -21,7 +22,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;
|
||||||
|
|||||||
@@ -240,6 +240,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)";
|
||||||
|
|||||||
@@ -338,8 +338,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 +356,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;
|
||||||
|
|||||||
@@ -116,7 +116,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;
|
||||||
|
|||||||
@@ -139,7 +139,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;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace Content.Server.GameObjects
|
|||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
serializer.DataField(ref StorageCapacityMax, "Capacity", 10000);
|
serializer.DataField(ref StorageCapacityMax, "Capacity", 10000);
|
||||||
serializer.DataField(ref StorageUsed, "used", 0);
|
//serializer.DataField(ref StorageUsed, "used", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -348,7 +348,7 @@ namespace Content.Server.GameObjects
|
|||||||
|
|
||||||
foreach (var entity in storage.ContainedEntities)
|
foreach (var entity in storage.ContainedEntities)
|
||||||
{
|
{
|
||||||
var item = entity.GetComponent<ItemComponent>();
|
var item = entity.GetComponent<StoreableComponent>();
|
||||||
StorageUsed += item.ObjectSize;
|
StorageUsed += item.ObjectSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,6 +133,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 +154,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,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)
|
||||||
|
|||||||
@@ -250,6 +250,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, PowerCellChargerComponent component, VerbData data)
|
protected override void GetData(IEntity user, PowerCellChargerComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!user.TryGetComponent(out HandsComponent handsComponent))
|
if (!user.TryGetComponent(out HandsComponent handsComponent))
|
||||||
{
|
{
|
||||||
data.Visibility = VerbVisibility.Invisible;
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
@@ -99,6 +105,12 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, PowerCellChargerComponent component, VerbData data)
|
protected override void GetData(IEntity user, PowerCellChargerComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (component._container.ContainedEntity == null)
|
if (component._container.ContainedEntity == null)
|
||||||
{
|
{
|
||||||
data.Text = "Eject";
|
data.Text = "Eject";
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, WeaponCapacitorChargerComponent component, VerbData data)
|
protected override void GetData(IEntity user, WeaponCapacitorChargerComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!user.TryGetComponent(out HandsComponent handsComponent))
|
if (!user.TryGetComponent(out HandsComponent handsComponent))
|
||||||
{
|
{
|
||||||
data.Visibility = VerbVisibility.Invisible;
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
@@ -89,6 +95,12 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, WeaponCapacitorChargerComponent component, VerbData data)
|
protected override void GetData(IEntity user, WeaponCapacitorChargerComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (component._container.ContainedEntity == null)
|
if (component._container.ContainedEntity == null)
|
||||||
{
|
{
|
||||||
data.Visibility = VerbVisibility.Disabled;
|
data.Visibility = VerbVisibility.Disabled;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -37,6 +38,12 @@ namespace Content.Server.GameObjects.Components
|
|||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, RotatableComponent component, VerbData data)
|
protected override void GetData(IEntity user, RotatableComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.CategoryData = VerbCategories.Rotate;
|
data.CategoryData = VerbCategories.Rotate;
|
||||||
data.Text = "Rotate clockwise";
|
data.Text = "Rotate clockwise";
|
||||||
data.IconTexture = "/Textures/UserInterface/VerbIcons/rotate_cw.svg.96dpi.png";
|
data.IconTexture = "/Textures/UserInterface/VerbIcons/rotate_cw.svg.96dpi.png";
|
||||||
@@ -53,6 +60,12 @@ namespace Content.Server.GameObjects.Components
|
|||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, RotatableComponent component, VerbData data)
|
protected override void GetData(IEntity user, RotatableComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.CategoryData = VerbCategories.Rotate;
|
data.CategoryData = VerbCategories.Rotate;
|
||||||
data.Text = "Rotate counter-clockwise";
|
data.Text = "Rotate counter-clockwise";
|
||||||
data.IconTexture = "/Textures/UserInterface/VerbIcons/rotate_ccw.svg.96dpi.png";
|
data.IconTexture = "/Textures/UserInterface/VerbIcons/rotate_ccw.svg.96dpi.png";
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
|
|||||||
[ViewVariables] private ContainerSlot _cellContainer;
|
[ViewVariables] private ContainerSlot _cellContainer;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
private float _paralyzeChance = 0.25f;
|
private float _paralyzeChanceNoSlowdown = 0.35f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
private float _paralyzeChanceWithSlowdown = 0.85f;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
private float _paralyzeTime = 10f;
|
private float _paralyzeTime = 10f;
|
||||||
@@ -75,7 +78,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
|
|||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
serializer.DataField(ref _paralyzeChance, "paralyzeChance", 0.25f);
|
serializer.DataField(ref _paralyzeChanceNoSlowdown, "paralyzeChanceNoSlowdown", 0.35f);
|
||||||
|
serializer.DataField(ref _paralyzeChanceWithSlowdown, "paralyzeChanceWithSlowdown", 0.85f);
|
||||||
serializer.DataField(ref _paralyzeTime, "paralyzeTime", 10f);
|
serializer.DataField(ref _paralyzeTime, "paralyzeTime", 10f);
|
||||||
serializer.DataField(ref _slowdownTime, "slowdownTime", 5f);
|
serializer.DataField(ref _slowdownTime, "slowdownTime", 5f);
|
||||||
}
|
}
|
||||||
@@ -92,10 +96,16 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
|
|||||||
{
|
{
|
||||||
if (!entity.TryGetComponent(out StunnableComponent stunnable)) continue;
|
if (!entity.TryGetComponent(out StunnableComponent stunnable)) continue;
|
||||||
|
|
||||||
if(_robustRandom.Prob(_paralyzeChance))
|
if(!stunnable.SlowedDown)
|
||||||
stunnable.Paralyze(_paralyzeTime);
|
if(_robustRandom.Prob(_paralyzeChanceNoSlowdown))
|
||||||
|
stunnable.Paralyze(_paralyzeTime);
|
||||||
|
else
|
||||||
|
stunnable.Slowdown(_slowdownTime);
|
||||||
else
|
else
|
||||||
stunnable.Slowdown(_slowdownTime);
|
if(_robustRandom.Prob(_paralyzeChanceWithSlowdown))
|
||||||
|
stunnable.Paralyze(_paralyzeTime);
|
||||||
|
else
|
||||||
|
stunnable.Slowdown(_slowdownTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.DeductCharge(EnergyPerUse);
|
cell.DeductCharge(EnergyPerUse);
|
||||||
@@ -252,6 +262,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
|
|||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, StunbatonComponent component, VerbData data)
|
protected override void GetData(IEntity user, StunbatonComponent 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)";
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Melee
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class UnarmedCombatComponent : MeleeWeaponComponent
|
||||||
|
{
|
||||||
|
public override string Name => "UnarmedCombat";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,9 +63,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
|||||||
_ammoContainer.Insert(entity);
|
_ammoContainer.Insert(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IMapInit.MapInit()
|
void IMapInit.MapInit()
|
||||||
{
|
{
|
||||||
_unspawnedCount += _capacity;
|
_unspawnedCount += _capacity;
|
||||||
@@ -117,7 +117,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
|||||||
Owner.PopupMessage(user, Loc.GetString("No room"));
|
Owner.PopupMessage(user, Loc.GetString("No room"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_spawnedAmmo.Push(entity);
|
_spawnedAmmo.Push(entity);
|
||||||
_ammoContainer.Insert(entity);
|
_ammoContainer.Insert(entity);
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
@@ -136,7 +136,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
|||||||
for (var i = 0; i < Math.Max(10, rangedMagazine.ShotsLeft); i++)
|
for (var i = 0; i < Math.Max(10, rangedMagazine.ShotsLeft); i++)
|
||||||
{
|
{
|
||||||
var ammo = rangedMagazine.TakeAmmo();
|
var ammo = rangedMagazine.TakeAmmo();
|
||||||
|
|
||||||
if (!TryInsertAmmo(eventArgs.User, ammo))
|
if (!TryInsertAmmo(eventArgs.User, ammo))
|
||||||
{
|
{
|
||||||
rangedMagazine.TryInsertAmmo(eventArgs.User, ammo);
|
rangedMagazine.TryInsertAmmo(eventArgs.User, ammo);
|
||||||
@@ -146,7 +146,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
|||||||
{
|
{
|
||||||
var ejectCount = Math.Min(count, Capacity);
|
var ejectCount = Math.Min(count, Capacity);
|
||||||
var ejectAmmo = new List<IEntity>(ejectCount);
|
var ejectAmmo = new List<IEntity>(ejectCount);
|
||||||
|
|
||||||
for (var i = 0; i < Math.Min(count, Capacity); i++)
|
for (var i = 0; i < Math.Min(count, Capacity); i++)
|
||||||
{
|
{
|
||||||
var ammo = TakeAmmo();
|
var ammo = TakeAmmo();
|
||||||
@@ -200,13 +200,19 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
|||||||
{
|
{
|
||||||
return TryUse(eventArgs.User);
|
return TryUse(eventArgs.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
// So if you have 200 rounds in a box and that suddenly creates 200 entities you're not having a fun time
|
// So if you have 200 rounds in a box and that suddenly creates 200 entities you're not having a fun time
|
||||||
[Verb]
|
[Verb]
|
||||||
private sealed class DumpVerb : Verb<AmmoBoxComponent>
|
private sealed class DumpVerb : Verb<AmmoBoxComponent>
|
||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, AmmoBoxComponent component, VerbData data)
|
protected override void GetData(IEntity user, AmmoBoxComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.Text = Loc.GetString("Dump 10");
|
data.Text = Loc.GetString("Dump 10");
|
||||||
data.Visibility = component.AmmoLeft > 0 ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
data.Visibility = component.AmmoLeft > 0 ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
}
|
}
|
||||||
@@ -217,4 +223,4 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
// Originally I had this logic shared with PumpBarrel and used a couple of variables to control things
|
// Originally I had this logic shared with PumpBarrel and used a couple of variables to control things
|
||||||
// but it felt a lot messier to play around with, especially when adding verbs
|
// but it felt a lot messier to play around with, especially when adding verbs
|
||||||
|
|
||||||
public override string Name => "BoltActionBarrel";
|
public override string Name => "BoltActionBarrel";
|
||||||
|
|
||||||
public override int ShotsLeft
|
public override int ShotsLeft
|
||||||
@@ -62,7 +62,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
}
|
}
|
||||||
|
|
||||||
var soundSystem = EntitySystem.Get<AudioSystem>();
|
var soundSystem = EntitySystem.Get<AudioSystem>();
|
||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
{
|
{
|
||||||
if (_soundBoltOpen != null)
|
if (_soundBoltOpen != null)
|
||||||
@@ -77,7 +77,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
soundSystem.PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
soundSystem.PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_boltOpen = value;
|
_boltOpen = value;
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
serializer.DataField(ref _soundBoltClosed, "soundBoltClosed", "/Audio/Guns/Bolt/rifle_bolt_closed.ogg");
|
serializer.DataField(ref _soundBoltClosed, "soundBoltClosed", "/Audio/Guns/Bolt/rifle_bolt_closed.ogg");
|
||||||
serializer.DataField(ref _soundInsert, "soundInsert", "/Audio/Guns/MagIn/bullet_insert.ogg");
|
serializer.DataField(ref _soundInsert, "soundInsert", "/Audio/Guns/MagIn/bullet_insert.ogg");
|
||||||
}
|
}
|
||||||
|
|
||||||
void IMapInit.MapInit()
|
void IMapInit.MapInit()
|
||||||
{
|
{
|
||||||
if (_fillPrototype != null)
|
if (_fillPrototype != null)
|
||||||
@@ -137,7 +137,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
_appearanceComponent = appearanceComponent;
|
_appearanceComponent = appearanceComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
var ammoComponent = chamberedEntity.GetComponent<AmmoComponent>();
|
var ammoComponent = chamberedEntity.GetComponent<AmmoComponent>();
|
||||||
if (!ammoComponent.Caseless)
|
if (!ammoComponent.Caseless)
|
||||||
{
|
{
|
||||||
EjectCasing(chamberedEntity);
|
EjectCasing(chamberedEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundCycle, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundCycle, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
@@ -264,9 +264,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Owner.PopupMessage(user, Loc.GetString("No room"));
|
Owner.PopupMessage(user, Loc.GetString("No room"));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +279,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
// Dirty();
|
// Dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cycle(true);
|
Cycle(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -288,12 +288,18 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
private sealed class OpenBoltVerb : Verb<BoltActionBarrelComponent>
|
private sealed class OpenBoltVerb : Verb<BoltActionBarrelComponent>
|
||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data)
|
protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.Text = Loc.GetString("Open bolt");
|
data.Text = Loc.GetString("Open bolt");
|
||||||
data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
|
data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
|
||||||
}
|
}
|
||||||
@@ -303,12 +309,18 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
component.BoltOpen = true;
|
component.BoltOpen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
private sealed class CloseBoltVerb : Verb<BoltActionBarrelComponent>
|
private sealed class CloseBoltVerb : Verb<BoltActionBarrelComponent>
|
||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data)
|
protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.Text = Loc.GetString("Close bolt");
|
data.Text = Loc.GetString("Close bolt");
|
||||||
data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
}
|
}
|
||||||
@@ -319,4 +331,4 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,15 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
var capacity = serializer.ReadDataField("capacity", 6);
|
|
||||||
_ammoSlots = new IEntity[capacity];
|
if (serializer.Reading)
|
||||||
|
{
|
||||||
|
var capacity = serializer.ReadDataField("capacity", 6);
|
||||||
|
_ammoSlots = new IEntity[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Writing?
|
||||||
|
|
||||||
|
|
||||||
// Sounds
|
// Sounds
|
||||||
serializer.DataField(ref _soundEject, "soundEject", "/Audio/Guns/MagOut/revolver_magout.ogg");
|
serializer.DataField(ref _soundEject, "soundEject", "/Audio/Guns/MagOut/revolver_magout.ogg");
|
||||||
@@ -60,7 +67,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
_appearanceComponent = appearanceComponent;
|
_appearanceComponent = appearanceComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +85,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ammoComponent.Caliber != _caliber)
|
if (ammoComponent.Caliber != _caliber)
|
||||||
{
|
{
|
||||||
Owner.PopupMessage(user, Loc.GetString("Wrong caliber"));
|
Owner.PopupMessage(user, Loc.GetString("Wrong caliber"));
|
||||||
@@ -208,12 +215,18 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
private sealed class SpinRevolverVerb : Verb<RevolverBarrelComponent>
|
private sealed class SpinRevolverVerb : Verb<RevolverBarrelComponent>
|
||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, RevolverBarrelComponent component, VerbData data)
|
protected override void GetData(IEntity user, RevolverBarrelComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.Text = Loc.GetString("Spin");
|
data.Text = Loc.GetString("Spin");
|
||||||
if (component.Capacity <= 1)
|
if (component.Capacity <= 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
if (energyRatio < 1.0)
|
if (energyRatio < 1.0)
|
||||||
{
|
{
|
||||||
var newDamages = new Dictionary<DamageType, int>(projectileComponent.Damages);
|
var newDamages = new Dictionary<DamageType, int>(projectileComponent.Damages.Count);
|
||||||
foreach (var (damageType, damage) in projectileComponent.Damages)
|
foreach (var (damageType, damage) in projectileComponent.Damages)
|
||||||
{
|
{
|
||||||
newDamages.Add(damageType, (int) (damage * energyRatio));
|
newDamages.Add(damageType, (int) (damage * energyRatio));
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||||
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -25,7 +26,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
public override string Name => "MagazineBarrel";
|
public override string Name => "MagazineBarrel";
|
||||||
public override uint? NetID => ContentNetIDs.MAGAZINE_BARREL;
|
public override uint? NetID => ContentNetIDs.MAGAZINE_BARREL;
|
||||||
|
|
||||||
private ContainerSlot _chamberContainer;
|
private ContainerSlot _chamberContainer;
|
||||||
[ViewVariables] public bool HasMagazine => _magazineContainer.ContainedEntity != null;
|
[ViewVariables] public bool HasMagazine => _magazineContainer.ContainedEntity != null;
|
||||||
private ContainerSlot _magazineContainer;
|
private ContainerSlot _magazineContainer;
|
||||||
@@ -117,11 +118,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
count = (rangedMagazineComponent.ShotsLeft, rangedMagazineComponent.Capacity);
|
count = (rangedMagazineComponent.ShotsLeft, rangedMagazineComponent.Capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MagazineBarrelComponentState(
|
return new MagazineBarrelComponentState(
|
||||||
_chamberContainer.ContainedEntity != null,
|
_chamberContainer.ContainedEntity != null,
|
||||||
FireRateSelector,
|
FireRateSelector,
|
||||||
count,
|
count,
|
||||||
SoundGunshot);
|
SoundGunshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
_appearanceComponent = appearanceComponent;
|
_appearanceComponent = appearanceComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
_chamberContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-chamber", Owner);
|
_chamberContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-chamber", Owner);
|
||||||
_magazineContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-magazine", Owner);
|
_magazineContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-magazine", Owner);
|
||||||
}
|
}
|
||||||
@@ -193,7 +194,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
var ammoComponent = chamberEntity.GetComponent<AmmoComponent>();
|
var ammoComponent = chamberEntity.GetComponent<AmmoComponent>();
|
||||||
if (!ammoComponent.Caseless)
|
if (!ammoComponent.Caseless)
|
||||||
{
|
{
|
||||||
EjectCasing(chamberEntity);
|
EjectCasing(chamberEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
// If you're really into gunporn you could put a sound here
|
// If you're really into gunporn you could put a sound here
|
||||||
_chamberContainer.Insert(nextRound);
|
_chamberContainer.Insert(nextRound);
|
||||||
}
|
}
|
||||||
|
|
||||||
var soundSystem = EntitySystem.Get<AudioSystem>();
|
var soundSystem = EntitySystem.Get<AudioSystem>();
|
||||||
|
|
||||||
if (_autoEjectMag && magazine != null && magazine.GetComponent<RangedMagazineComponent>().ShotsLeft == 0)
|
if (_autoEjectMag && magazine != null && magazine.GetComponent<RangedMagazineComponent>().ShotsLeft == 0)
|
||||||
@@ -217,6 +218,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
}
|
}
|
||||||
|
|
||||||
_magazineContainer.Remove(magazine);
|
_magazineContainer.Remove(magazine);
|
||||||
|
SendNetworkMessage(new MagazineAutoEjectMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextRound == null && !BoltOpen)
|
if (nextRound == null && !BoltOpen)
|
||||||
@@ -243,7 +245,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
soundSystem.PlayAtCoords(_soundRack, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
soundSystem.PlayAtCoords(_soundRack, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
@@ -308,7 +310,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
{
|
{
|
||||||
handsComponent.PutInHandOrDrop(mag.GetComponent<ItemComponent>());
|
handsComponent.PutInHandOrDrop(mag.GetComponent<ItemComponent>());
|
||||||
}
|
}
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
@@ -383,12 +385,18 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
private sealed class EjectMagazineVerb : Verb<ServerMagazineBarrelComponent>
|
private sealed class EjectMagazineVerb : Verb<ServerMagazineBarrelComponent>
|
||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.Text = Loc.GetString("Eject magazine");
|
data.Text = Loc.GetString("Eject magazine");
|
||||||
if (component.MagNeedsOpenBolt)
|
if (component.MagNeedsOpenBolt)
|
||||||
{
|
{
|
||||||
@@ -406,12 +414,18 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
component.RemoveMagazine(user);
|
component.RemoveMagazine(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
private sealed class OpenBoltVerb : Verb<ServerMagazineBarrelComponent>
|
private sealed class OpenBoltVerb : Verb<ServerMagazineBarrelComponent>
|
||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.Text = Loc.GetString("Open bolt");
|
data.Text = Loc.GetString("Open bolt");
|
||||||
data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
|
data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
|
||||||
}
|
}
|
||||||
@@ -421,12 +435,18 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
|||||||
component.ToggleBolt();
|
component.ToggleBolt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
private sealed class CloseBoltVerb : Verb<ServerMagazineBarrelComponent>
|
private sealed class CloseBoltVerb : Verb<ServerMagazineBarrelComponent>
|
||||||
{
|
{
|
||||||
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
||||||
{
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(user))
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.Text = Loc.GetString("Close bolt");
|
data.Text = Loc.GetString("Close bolt");
|
||||||
data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
using Robust.Shared.GameObjects.Components.Transform;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates
|
|
||||||
{
|
|
||||||
public struct CollidableMove : IPathfindingGraphUpdate
|
|
||||||
{
|
|
||||||
public MoveEvent MoveEvent { get; }
|
|
||||||
|
|
||||||
public CollidableMove(MoveEvent moveEvent)
|
|
||||||
{
|
|
||||||
MoveEvent = moveEvent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates
|
|
||||||
{
|
|
||||||
public class CollisionChange : IPathfindingGraphUpdate
|
|
||||||
{
|
|
||||||
public IEntity Owner { get; }
|
|
||||||
public bool Value { get; }
|
|
||||||
|
|
||||||
public CollisionChange(IEntity owner, bool value)
|
|
||||||
{
|
|
||||||
Owner = owner;
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using Robust.Shared.Map;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates
|
|
||||||
{
|
|
||||||
public struct GridRemoval : IPathfindingGraphUpdate
|
|
||||||
{
|
|
||||||
public GridId GridId { get; }
|
|
||||||
|
|
||||||
public GridRemoval(GridId gridId)
|
|
||||||
{
|
|
||||||
GridId = gridId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates
|
|
||||||
{
|
|
||||||
public interface IPathfindingGraphUpdate
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using Robust.Shared.Map;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates
|
|
||||||
{
|
|
||||||
public struct TileUpdate : IPathfindingGraphUpdate
|
|
||||||
{
|
|
||||||
public TileUpdate(TileRef tile)
|
|
||||||
{
|
|
||||||
Tile = tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TileRef Tile { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we couldn't get a nearby node that's good enough
|
// If we couldn't get a nearby node that's good enough
|
||||||
if (!Utils.TryEndNode(ref _endNode, _pathfindingArgs))
|
if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -88,9 +88,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If tile is untraversable it'll be null
|
// If tile is untraversable it'll be null
|
||||||
var tileCost = Utils.GetTileCost(_pathfindingArgs, currentNode, nextNode);
|
var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode);
|
||||||
|
|
||||||
if (tileCost == null || !Utils.DirectionTraversable(_pathfindingArgs.CollisionMask, currentNode, direction))
|
if (tileCost == null || !PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
// pFactor is tie-breaker where the fscore is otherwise equal.
|
// pFactor is tie-breaker where the fscore is otherwise equal.
|
||||||
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
|
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
|
||||||
// There's other ways to do it but future consideration
|
// There's other ways to do it but future consideration
|
||||||
var fScore = gScores[nextNode] + Utils.OctileDistance(_endNode, nextNode) * (1.0f + 1.0f / 1000.0f);
|
var fScore = gScores[nextNode] + PathfindingHelpers.OctileDistance(_endNode, nextNode) * (1.0f + 1.0f / 1000.0f);
|
||||||
openTiles.Add((fScore, nextNode));
|
openTiles.Add((fScore, nextNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var route = Utils.ReconstructPath(cameFrom, currentNode);
|
var route = PathfindingHelpers.ReconstructPath(cameFrom, currentNode);
|
||||||
|
|
||||||
if (route.Count == 1)
|
if (route.Count == 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we couldn't get a nearby node that's good enough
|
// If we couldn't get a nearby node that's good enough
|
||||||
if (!Utils.TryEndNode(ref _endNode, _pathfindingArgs))
|
if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
jumpNodes.Add(jumpNode);
|
jumpNodes.Add(jumpNode);
|
||||||
#endif
|
#endif
|
||||||
// GetJumpPoint should already check if we can traverse to the node
|
// GetJumpPoint should already check if we can traverse to the node
|
||||||
var tileCost = Utils.GetTileCost(_pathfindingArgs, currentNode, jumpNode);
|
var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, jumpNode);
|
||||||
|
|
||||||
if (tileCost == null)
|
if (tileCost == null)
|
||||||
{
|
{
|
||||||
@@ -108,7 +108,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
// pFactor is tie-breaker where the fscore is otherwise equal.
|
// pFactor is tie-breaker where the fscore is otherwise equal.
|
||||||
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
|
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
|
||||||
// There's other ways to do it but future consideration
|
// There's other ways to do it but future consideration
|
||||||
var fScore = gScores[jumpNode] + Utils.OctileDistance(_endNode, jumpNode) * (1.0f + 1.0f / 1000.0f);
|
var fScore = gScores[jumpNode] + PathfindingHelpers.OctileDistance(_endNode, jumpNode) * (1.0f + 1.0f / 1000.0f);
|
||||||
openTiles.Add((fScore, jumpNode));
|
openTiles.Add((fScore, jumpNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var route = Utils.ReconstructJumpPath(cameFrom, currentNode);
|
var route = PathfindingHelpers.ReconstructJumpPath(cameFrom, currentNode);
|
||||||
if (route.Count == 1)
|
if (route.Count == 1)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -161,7 +161,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
// We'll do opposite DirectionTraversable just because of how the method's setup
|
// We'll do opposite DirectionTraversable just because of how the method's setup
|
||||||
// Nodes should be 2-way anyway.
|
// Nodes should be 2-way anyway.
|
||||||
if (nextNode == null ||
|
if (nextNode == null ||
|
||||||
Utils.GetTileCost(_pathfindingArgs, currentNode, nextNode) == null)
|
PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode) == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -312,14 +312,14 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((closedNeighborOne == null || Utils.GetTileCost(_pathfindingArgs, currentNode, closedNeighborOne) == null)
|
if ((closedNeighborOne == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborOne) == null)
|
||||||
&& openNeighborOne != null && Utils.GetTileCost(_pathfindingArgs, currentNode, openNeighborOne) != null)
|
&& openNeighborOne != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborOne) != null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((closedNeighborTwo == null || Utils.GetTileCost(_pathfindingArgs, currentNode, closedNeighborTwo) == null)
|
if ((closedNeighborTwo == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborTwo) == null)
|
||||||
&& openNeighborTwo != null && Utils.GetTileCost(_pathfindingArgs, currentNode, openNeighborTwo) != null)
|
&& openNeighborTwo != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborTwo) != null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -371,14 +371,14 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((closedNeighborOne == null || !Utils.Traversable(_pathfindingArgs.CollisionMask, closedNeighborOne.CollisionMask)) &&
|
if ((closedNeighborOne == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborOne)) &&
|
||||||
(openNeighborOne != null && Utils.Traversable(_pathfindingArgs.CollisionMask, openNeighborOne.CollisionMask)))
|
(openNeighborOne != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborOne)))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((closedNeighborTwo == null || !Utils.Traversable(_pathfindingArgs.CollisionMask, closedNeighborTwo.CollisionMask)) &&
|
if ((closedNeighborTwo == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborTwo)) &&
|
||||||
(openNeighborTwo != null && Utils.Traversable(_pathfindingArgs.CollisionMask, openNeighborTwo.CollisionMask)))
|
(openNeighborTwo != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborTwo)))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
@@ -6,6 +7,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
public struct PathfindingArgs
|
public struct PathfindingArgs
|
||||||
{
|
{
|
||||||
public EntityUid Uid { get; }
|
public EntityUid Uid { get; }
|
||||||
|
public ICollection<string> Access { get; }
|
||||||
public int CollisionMask { get; }
|
public int CollisionMask { get; }
|
||||||
public TileRef Start { get; }
|
public TileRef Start { get; }
|
||||||
public TileRef End { get; }
|
public TileRef End { get; }
|
||||||
@@ -20,6 +22,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
|
|
||||||
public PathfindingArgs(
|
public PathfindingArgs(
|
||||||
EntityUid entityUid,
|
EntityUid entityUid,
|
||||||
|
ICollection<string> access,
|
||||||
int collisionMask,
|
int collisionMask,
|
||||||
TileRef start,
|
TileRef start,
|
||||||
TileRef end,
|
TileRef end,
|
||||||
@@ -29,6 +32,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
|
|||||||
bool allowSpace = false)
|
bool allowSpace = false)
|
||||||
{
|
{
|
||||||
Uid = entityUid;
|
Uid = entityUid;
|
||||||
|
Access = access;
|
||||||
CollisionMask = collisionMask;
|
CollisionMask = collisionMask;
|
||||||
Start = start;
|
Start = start;
|
||||||
End = end;
|
End = end;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Access;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
|
||||||
using Content.Server.GameObjects.EntitySystems.Pathfinding;
|
using Content.Server.GameObjects.EntitySystems.Pathfinding;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -10,19 +11,19 @@ using Robust.Shared.Maths;
|
|||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
||||||
{
|
{
|
||||||
public static class Utils
|
public static class PathfindingHelpers
|
||||||
{
|
{
|
||||||
public static bool TryEndNode(ref PathfindingNode endNode, PathfindingArgs pathfindingArgs)
|
public static bool TryEndNode(ref PathfindingNode endNode, PathfindingArgs pathfindingArgs)
|
||||||
{
|
{
|
||||||
if (!Traversable(pathfindingArgs.CollisionMask, endNode.CollisionMask))
|
if (!Traversable(pathfindingArgs.CollisionMask, pathfindingArgs.Access, endNode))
|
||||||
{
|
{
|
||||||
if (pathfindingArgs.Proximity > 0.0f)
|
if (pathfindingArgs.Proximity > 0.0f)
|
||||||
{
|
{
|
||||||
// TODO: Should make this account for proximities,
|
// TODO: Should make this account for proximities,
|
||||||
// probably some kind of breadth-first search to find a valid one
|
// probably some kind of breadth-first search to find a valid one
|
||||||
foreach (var (direction, node) in endNode.Neighbors)
|
foreach (var (_, node) in endNode.Neighbors)
|
||||||
{
|
{
|
||||||
if (Traversable(pathfindingArgs.CollisionMask, node.CollisionMask))
|
if (Traversable(pathfindingArgs.CollisionMask, pathfindingArgs.Access, node))
|
||||||
{
|
{
|
||||||
endNode = node;
|
endNode = node;
|
||||||
return true;
|
return true;
|
||||||
@@ -36,7 +37,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DirectionTraversable(int collisionMask, PathfindingNode currentNode, Direction direction)
|
public static bool DirectionTraversable(int collisionMask, ICollection<string> access, PathfindingNode currentNode, Direction direction)
|
||||||
{
|
{
|
||||||
// If it's a diagonal we need to check NSEW to see if we can get to it and stop corner cutting, NE needs N and E etc.
|
// If it's a diagonal we need to check NSEW to see if we can get to it and stop corner cutting, NE needs N and E etc.
|
||||||
// Given there's different collision layers stored for each node in the graph it's probably not worth it to cache this
|
// Given there's different collision layers stored for each node in the graph it's probably not worth it to cache this
|
||||||
@@ -51,32 +52,32 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
{
|
{
|
||||||
case Direction.NorthEast:
|
case Direction.NorthEast:
|
||||||
if (northNeighbor == null || eastNeighbor == null) return false;
|
if (northNeighbor == null || eastNeighbor == null) return false;
|
||||||
if (!Traversable(collisionMask, northNeighbor.CollisionMask) ||
|
if (!Traversable(collisionMask, access, northNeighbor) ||
|
||||||
!Traversable(collisionMask, eastNeighbor.CollisionMask))
|
!Traversable(collisionMask, access, eastNeighbor))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Direction.NorthWest:
|
case Direction.NorthWest:
|
||||||
if (northNeighbor == null || westNeighbor == null) return false;
|
if (northNeighbor == null || westNeighbor == null) return false;
|
||||||
if (!Traversable(collisionMask, northNeighbor.CollisionMask) ||
|
if (!Traversable(collisionMask, access, northNeighbor) ||
|
||||||
!Traversable(collisionMask, westNeighbor.CollisionMask))
|
!Traversable(collisionMask, access, westNeighbor))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Direction.SouthWest:
|
case Direction.SouthWest:
|
||||||
if (southNeighbor == null || westNeighbor == null) return false;
|
if (southNeighbor == null || westNeighbor == null) return false;
|
||||||
if (!Traversable(collisionMask, southNeighbor.CollisionMask) ||
|
if (!Traversable(collisionMask, access, southNeighbor) ||
|
||||||
!Traversable(collisionMask, westNeighbor.CollisionMask))
|
!Traversable(collisionMask, access, westNeighbor))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Direction.SouthEast:
|
case Direction.SouthEast:
|
||||||
if (southNeighbor == null || eastNeighbor == null) return false;
|
if (southNeighbor == null || eastNeighbor == null) return false;
|
||||||
if (!Traversable(collisionMask, southNeighbor.CollisionMask) ||
|
if (!Traversable(collisionMask, access, southNeighbor) ||
|
||||||
!Traversable(collisionMask, eastNeighbor.CollisionMask))
|
!Traversable(collisionMask, access, eastNeighbor))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -86,11 +87,24 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Traversable(int collisionMask, int nodeMask)
|
public static bool Traversable(int collisionMask, ICollection<string> access, PathfindingNode node)
|
||||||
{
|
{
|
||||||
return (collisionMask & nodeMask) == 0;
|
if ((collisionMask & node.BlockedCollisionMask) != 0)
|
||||||
}
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var reader in node.AccessReaders)
|
||||||
|
{
|
||||||
|
if (!reader.IsAllowed(access))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static Queue<TileRef> ReconstructPath(Dictionary<PathfindingNode, PathfindingNode> cameFrom, PathfindingNode current)
|
public static Queue<TileRef> ReconstructPath(Dictionary<PathfindingNode, PathfindingNode> cameFrom, PathfindingNode current)
|
||||||
{
|
{
|
||||||
var running = new Stack<TileRef>();
|
var running = new Stack<TileRef>();
|
||||||
@@ -194,6 +208,20 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
|
|
||||||
return 1.4f * dstX + (dstY - dstX);
|
return 1.4f * dstX + (dstY - dstX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static float OctileDistance(TileRef endTile, TileRef startTile)
|
||||||
|
{
|
||||||
|
// "Fast Euclidean" / octile.
|
||||||
|
// This implementation is written down in a few sources; it just saves doing sqrt.
|
||||||
|
int dstX = Math.Abs(startTile.X - endTile.X);
|
||||||
|
int dstY = Math.Abs(startTile.Y - endTile.Y);
|
||||||
|
if (dstX > dstY)
|
||||||
|
{
|
||||||
|
return 1.4f * dstY + (dstX - dstY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.4f * dstX + (dstY - dstX);
|
||||||
|
}
|
||||||
|
|
||||||
public static float ManhattanDistance(PathfindingNode endNode, PathfindingNode currentNode)
|
public static float ManhattanDistance(PathfindingNode endNode, PathfindingNode currentNode)
|
||||||
{
|
{
|
||||||
@@ -202,7 +230,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
|
|
||||||
public static float? GetTileCost(PathfindingArgs pathfindingArgs, PathfindingNode start, PathfindingNode end)
|
public static float? GetTileCost(PathfindingArgs pathfindingArgs, PathfindingNode start, PathfindingNode end)
|
||||||
{
|
{
|
||||||
if (!pathfindingArgs.NoClip && !Traversable(pathfindingArgs.CollisionMask, end.CollisionMask))
|
if (!pathfindingArgs.NoClip && !Traversable(pathfindingArgs.CollisionMask, pathfindingArgs.Access, end))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Access;
|
||||||
|
using Content.Server.GameObjects.Components.Doors;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
@@ -8,27 +14,34 @@ namespace Content.Server.GameObjects.EntitySystems.Pathfinding
|
|||||||
{
|
{
|
||||||
public class PathfindingNode
|
public class PathfindingNode
|
||||||
{
|
{
|
||||||
// TODO: Add access ID here
|
|
||||||
public PathfindingChunk ParentChunk => _parentChunk;
|
public PathfindingChunk ParentChunk => _parentChunk;
|
||||||
private readonly PathfindingChunk _parentChunk;
|
private readonly PathfindingChunk _parentChunk;
|
||||||
public TileRef TileRef { get; private set; }
|
|
||||||
public List<int> CollisionLayers { get; }
|
|
||||||
public int CollisionMask { get; private set; }
|
|
||||||
public Dictionary<Direction, PathfindingNode> Neighbors => _neighbors;
|
public Dictionary<Direction, PathfindingNode> Neighbors => _neighbors;
|
||||||
private Dictionary<Direction, PathfindingNode> _neighbors = new Dictionary<Direction, PathfindingNode>();
|
private Dictionary<Direction, PathfindingNode> _neighbors = new Dictionary<Direction, PathfindingNode>();
|
||||||
|
|
||||||
|
public TileRef TileRef { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whenever there's a change in the collision layers we update the mask as the graph has more reads than writes
|
||||||
|
/// </summary>
|
||||||
|
public int BlockedCollisionMask { get; private set; }
|
||||||
|
private readonly Dictionary<EntityUid, int> _blockedCollidables = new Dictionary<EntityUid, int>(0);
|
||||||
|
|
||||||
public PathfindingNode(PathfindingChunk parent, TileRef tileRef, List<int> collisionLayers = null)
|
public IReadOnlyCollection<EntityUid> PhysicsUids => _physicsUids;
|
||||||
|
private readonly HashSet<EntityUid> _physicsUids = new HashSet<EntityUid>(0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entities on this tile that require access to traverse
|
||||||
|
/// </summary>
|
||||||
|
/// We don't store the ICollection, at least for now, as we'd need to replicate the access code here
|
||||||
|
public IReadOnlyCollection<AccessReader> AccessReaders => _accessReaders.Values;
|
||||||
|
private readonly Dictionary<EntityUid, AccessReader> _accessReaders = new Dictionary<EntityUid, AccessReader>(0);
|
||||||
|
|
||||||
|
public PathfindingNode(PathfindingChunk parent, TileRef tileRef)
|
||||||
{
|
{
|
||||||
_parentChunk = parent;
|
_parentChunk = parent;
|
||||||
TileRef = tileRef;
|
TileRef = tileRef;
|
||||||
if (collisionLayers == null)
|
|
||||||
{
|
|
||||||
CollisionLayers = new List<int>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CollisionLayers = collisionLayers;
|
|
||||||
}
|
|
||||||
GenerateMask();
|
GenerateMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,25 +118,70 @@ namespace Content.Server.GameObjects.EntitySystems.Pathfinding
|
|||||||
TileRef = newTile;
|
TileRef = newTile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddCollisionLayer(int layer)
|
/// <summary>
|
||||||
|
/// Call if this entity is relevant for the pathfinder
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity"></param>
|
||||||
|
/// TODO: These 2 methods currently don't account for a bunch of changes (e.g. airlock unpowered, wrenching, etc.)
|
||||||
|
public void AddEntity(IEntity entity)
|
||||||
{
|
{
|
||||||
CollisionLayers.Add(layer);
|
// If we're a door
|
||||||
GenerateMask();
|
if (entity.HasComponent<AirlockComponent>() || entity.HasComponent<ServerDoorComponent>())
|
||||||
|
{
|
||||||
|
// If we need access to traverse this then add to readers, otherwise no point adding it (except for maybe tile costs in future)
|
||||||
|
// TODO: Check for powered I think (also need an event for when it's depowered
|
||||||
|
// AccessReader calls this whenever opening / closing but it can seem to get called multiple times
|
||||||
|
// Which may or may not be intended?
|
||||||
|
if (entity.TryGetComponent(out AccessReader accessReader) && !_accessReaders.ContainsKey(entity.Uid))
|
||||||
|
{
|
||||||
|
_accessReaders.Add(entity.Uid, accessReader);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.TryGetComponent(out CollidableComponent collidableComponent))
|
||||||
|
{
|
||||||
|
if (entity.TryGetComponent(out PhysicsComponent physicsComponent) && !physicsComponent.Anchored)
|
||||||
|
{
|
||||||
|
_physicsUids.Add(entity.Uid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_blockedCollidables.TryAdd(entity.Uid, collidableComponent.CollisionLayer);
|
||||||
|
GenerateMask();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveCollisionLayer(int layer)
|
public void RemoveEntity(IEntity entity)
|
||||||
{
|
{
|
||||||
CollisionLayers.Remove(layer);
|
if (_accessReaders.ContainsKey(entity.Uid))
|
||||||
GenerateMask();
|
{
|
||||||
|
_accessReaders.Remove(entity.Uid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.HasComponent<CollidableComponent>())
|
||||||
|
{
|
||||||
|
if (entity.TryGetComponent(out PhysicsComponent physicsComponent) && physicsComponent.Anchored)
|
||||||
|
{
|
||||||
|
_blockedCollidables.Remove(entity.Uid);
|
||||||
|
GenerateMask();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_physicsUids.Remove(entity.Uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateMask()
|
private void GenerateMask()
|
||||||
{
|
{
|
||||||
CollisionMask = 0x0;
|
BlockedCollisionMask = 0x0;
|
||||||
|
|
||||||
foreach (var layer in CollisionLayers)
|
foreach (var layer in _blockedCollidables.Values)
|
||||||
{
|
{
|
||||||
CollisionMask |= layer;
|
BlockedCollisionMask |= layer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.GameObjects.Components.Doors;
|
using Content.Server.GameObjects.Components.Access;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates;
|
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
|
||||||
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
using Content.Server.GameObjects.EntitySystems.JobQueues.Queues;
|
using Content.Server.GameObjects.EntitySystems.JobQueues.Queues;
|
||||||
using Content.Server.GameObjects.EntitySystems.Pathfinding;
|
using Content.Server.GameObjects.EntitySystems.Pathfinding;
|
||||||
|
using Content.Shared.Physics;
|
||||||
using Robust.Shared.GameObjects.Components;
|
using Robust.Shared.GameObjects.Components;
|
||||||
using Robust.Shared.GameObjects.Components.Transform;
|
using Robust.Shared.GameObjects.Components.Transform;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
@@ -14,6 +14,7 @@ using Robust.Shared.Interfaces.GameObjects;
|
|||||||
using Robust.Shared.Interfaces.Map;
|
using Robust.Shared.Interfaces.Map;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
||||||
{
|
{
|
||||||
@@ -29,18 +30,30 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
public class PathfindingSystem : EntitySystem
|
public class PathfindingSystem : EntitySystem
|
||||||
{
|
{
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IEntityManager _entitymanager;
|
||||||
[Dependency] private readonly IMapManager _mapManager;
|
[Dependency] private readonly IMapManager _mapManager;
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
|
|
||||||
public IReadOnlyDictionary<GridId, Dictionary<MapIndices, PathfindingChunk>> Graph => _graph;
|
public IReadOnlyDictionary<GridId, Dictionary<MapIndices, PathfindingChunk>> Graph => _graph;
|
||||||
private readonly Dictionary<GridId, Dictionary<MapIndices, PathfindingChunk>> _graph = new Dictionary<GridId, Dictionary<MapIndices, PathfindingChunk>>();
|
private readonly Dictionary<GridId, Dictionary<MapIndices, PathfindingChunk>> _graph = new Dictionary<GridId, Dictionary<MapIndices, PathfindingChunk>>();
|
||||||
// Every tick we queue up all the changes and do them at once
|
|
||||||
private readonly Queue<IPathfindingGraphUpdate> _queuedGraphUpdates = new Queue<IPathfindingGraphUpdate>();
|
|
||||||
private readonly PathfindingJobQueue _pathfindingQueue = new PathfindingJobQueue();
|
private readonly PathfindingJobQueue _pathfindingQueue = new PathfindingJobQueue();
|
||||||
|
|
||||||
|
// Queued pathfinding graph updates
|
||||||
|
private readonly Queue<CollisionChangeEvent> _collidableUpdateQueue = new Queue<CollisionChangeEvent>();
|
||||||
|
private readonly Queue<MoveEvent> _moveUpdateQueue = new Queue<MoveEvent>();
|
||||||
|
private readonly Queue<AccessReaderChangeMessage> _accessReaderUpdateQueue = new Queue<AccessReaderChangeMessage>();
|
||||||
|
private readonly Queue<TileRef> _tileUpdateQueue = new Queue<TileRef>();
|
||||||
|
|
||||||
// Need to store previously known entity positions for collidables for when they move
|
// Need to store previously known entity positions for collidables for when they move
|
||||||
private readonly Dictionary<IEntity, TileRef> _lastKnownPositions = new Dictionary<IEntity, TileRef>();
|
private readonly Dictionary<IEntity, TileRef> _lastKnownPositions = new Dictionary<IEntity, TileRef>();
|
||||||
|
|
||||||
|
public const int TrackedCollisionLayers = (int)
|
||||||
|
(CollisionGroup.Impassable |
|
||||||
|
CollisionGroup.MobImpassable |
|
||||||
|
CollisionGroup.SmallImpassable |
|
||||||
|
CollisionGroup.VaultImpassable);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ask for the pathfinder to gimme somethin
|
/// Ask for the pathfinder to gimme somethin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -68,51 +81,66 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
|
|
||||||
private void ProcessGraphUpdates()
|
private void ProcessGraphUpdates()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Math.Min(50, _queuedGraphUpdates.Count); i++)
|
var totalUpdates = 0;
|
||||||
|
|
||||||
|
foreach (var update in _collidableUpdateQueue)
|
||||||
{
|
{
|
||||||
var update = _queuedGraphUpdates.Dequeue();
|
var entity = _entitymanager.GetEntity(update.Owner);
|
||||||
switch (update)
|
if (update.CanCollide)
|
||||||
{
|
{
|
||||||
case CollidableMove move:
|
HandleCollidableAdd(entity);
|
||||||
HandleCollidableMove(move);
|
}
|
||||||
break;
|
else
|
||||||
case CollisionChange change:
|
{
|
||||||
if (change.Value)
|
HandleAccessRemove(entity);
|
||||||
{
|
|
||||||
HandleCollidableAdd(change.Owner);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HandleCollidableRemove(change.Owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case GridRemoval removal:
|
|
||||||
HandleGridRemoval(removal);
|
|
||||||
break;
|
|
||||||
case TileUpdate tile:
|
|
||||||
HandleTileUpdate(tile);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleGridRemoval(GridRemoval removal)
|
totalUpdates++;
|
||||||
{
|
}
|
||||||
if (!_graph.ContainsKey(removal.GridId))
|
|
||||||
|
_collidableUpdateQueue.Clear();
|
||||||
|
|
||||||
|
foreach (var update in _accessReaderUpdateQueue)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
var entity = _entitymanager.GetEntity(update.Uid);
|
||||||
|
if (update.Enabled)
|
||||||
|
{
|
||||||
|
HandleAccessAdd(entity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HandleAccessRemove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalUpdates++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_accessReaderUpdateQueue.Clear();
|
||||||
|
|
||||||
|
foreach (var tile in _tileUpdateQueue)
|
||||||
|
{
|
||||||
|
HandleTileUpdate(tile);
|
||||||
|
totalUpdates++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tileUpdateQueue.Clear();
|
||||||
|
var moveUpdateCount = Math.Max(50 - totalUpdates, 0);
|
||||||
|
|
||||||
|
// Other updates are high priority so for this we'll just defer it if there's a spike (explosion, etc.)
|
||||||
|
// If the move updates grow too large then we'll just do it
|
||||||
|
if (_moveUpdateQueue.Count > 100)
|
||||||
|
{
|
||||||
|
moveUpdateCount = _moveUpdateQueue.Count - 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
_graph.Remove(removal.GridId);
|
moveUpdateCount = Math.Min(moveUpdateCount, _moveUpdateQueue.Count);
|
||||||
}
|
|
||||||
|
for (var i = 0; i < moveUpdateCount; i++)
|
||||||
private void HandleTileUpdate(TileUpdate tile)
|
{
|
||||||
{
|
HandleCollidableMove(_moveUpdateQueue.Dequeue());
|
||||||
var chunk = GetChunk(tile.Tile);
|
}
|
||||||
chunk.UpdateNode(tile.Tile);
|
|
||||||
|
DebugTools.Assert(_moveUpdateQueue.Count < 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathfindingChunk GetChunk(TileRef tile)
|
public PathfindingChunk GetChunk(TileRef tile)
|
||||||
@@ -132,7 +160,6 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newChunk = CreateChunk(tile.GridIndex, mapIndices);
|
var newChunk = CreateChunk(tile.GridIndex, mapIndices);
|
||||||
|
|
||||||
return newChunk;
|
return newChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,13 +206,13 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
SubscribeLocalEvent<CollisionChangeEvent>(QueueCollisionEnabledEvent);
|
SubscribeLocalEvent<CollisionChangeEvent>(QueueCollisionEnabledEvent);
|
||||||
SubscribeLocalEvent<MoveEvent>(QueueCollidableMove);
|
SubscribeLocalEvent<MoveEvent>(QueueCollidableMove);
|
||||||
|
SubscribeLocalEvent<AccessReaderChangeMessage>(QueueAccessChangeEvent);
|
||||||
|
|
||||||
// Handle all the base grid changes
|
// Handle all the base grid changes
|
||||||
// Anything that affects traversal (i.e. collision layer) is handled separately.
|
// Anything that affects traversal (i.e. collision layer) is handled separately.
|
||||||
_mapManager.OnGridRemoved += QueueGridRemoval;
|
_mapManager.OnGridRemoved += HandleGridRemoval;
|
||||||
_mapManager.GridChanged += QueueGridChange;
|
_mapManager.GridChanged += QueueGridChange;
|
||||||
_mapManager.TileChanged += QueueTileChange;
|
_mapManager.TileChanged += QueueTileChange;
|
||||||
}
|
}
|
||||||
@@ -193,32 +220,85 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
base.Shutdown();
|
base.Shutdown();
|
||||||
_mapManager.OnGridRemoved -= QueueGridRemoval;
|
UnsubscribeLocalEvent<CollisionChangeEvent>();
|
||||||
|
UnsubscribeLocalEvent<MoveEvent>();
|
||||||
|
UnsubscribeLocalEvent<AccessReaderChangeMessage>();
|
||||||
|
|
||||||
|
_mapManager.OnGridRemoved -= HandleGridRemoval;
|
||||||
_mapManager.GridChanged -= QueueGridChange;
|
_mapManager.GridChanged -= QueueGridChange;
|
||||||
_mapManager.TileChanged -= QueueTileChange;
|
_mapManager.TileChanged -= QueueTileChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleTileUpdate(TileRef tile)
|
||||||
|
{
|
||||||
|
var node = GetNode(tile);
|
||||||
|
node.UpdateTile(tile);
|
||||||
|
}
|
||||||
|
|
||||||
public void ResettingCleanup()
|
public void ResettingCleanup()
|
||||||
{
|
{
|
||||||
_queuedGraphUpdates.Clear();
|
_graph.Clear();
|
||||||
|
_collidableUpdateQueue.Clear();
|
||||||
|
_moveUpdateQueue.Clear();
|
||||||
|
_accessReaderUpdateQueue.Clear();
|
||||||
|
_tileUpdateQueue.Clear();
|
||||||
|
_lastKnownPositions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueGridRemoval(GridId gridId)
|
private void HandleGridRemoval(GridId gridId)
|
||||||
{
|
{
|
||||||
_queuedGraphUpdates.Enqueue(new GridRemoval(gridId));
|
if (_graph.ContainsKey(gridId))
|
||||||
|
{
|
||||||
|
_graph.Remove(gridId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueGridChange(object sender, GridChangedEventArgs eventArgs)
|
private void QueueGridChange(object sender, GridChangedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
foreach (var (position, _) in eventArgs.Modified)
|
foreach (var (position, _) in eventArgs.Modified)
|
||||||
{
|
{
|
||||||
_queuedGraphUpdates.Enqueue(new TileUpdate(eventArgs.Grid.GetTileRef(position)));
|
_tileUpdateQueue.Enqueue(eventArgs.Grid.GetTileRef(position));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueTileChange(object sender, TileChangedEventArgs eventArgs)
|
private void QueueTileChange(object sender, TileChangedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
_queuedGraphUpdates.Enqueue(new TileUpdate(eventArgs.NewTile));
|
_tileUpdateQueue.Enqueue(eventArgs.NewTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueueAccessChangeEvent(AccessReaderChangeMessage message)
|
||||||
|
{
|
||||||
|
_accessReaderUpdateQueue.Enqueue(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleAccessAdd(IEntity entity)
|
||||||
|
{
|
||||||
|
if (entity.Deleted || !entity.HasComponent<AccessReader>())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var grid = _mapManager.GetGrid(entity.Transform.GridID);
|
||||||
|
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
|
||||||
|
|
||||||
|
var chunk = GetChunk(tileRef);
|
||||||
|
var node = chunk.GetNode(tileRef);
|
||||||
|
node.AddEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleAccessRemove(IEntity entity)
|
||||||
|
{
|
||||||
|
if (entity.Deleted || !entity.HasComponent<AccessReader>())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var grid = _mapManager.GetGrid(entity.Transform.GridID);
|
||||||
|
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
|
||||||
|
|
||||||
|
var chunk = GetChunk(tileRef);
|
||||||
|
var node = chunk.GetNode(tileRef);
|
||||||
|
node.RemoveEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region collidable
|
#region collidable
|
||||||
@@ -228,25 +308,22 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
/// <param name="entity"></param>
|
/// <param name="entity"></param>
|
||||||
private void HandleCollidableAdd(IEntity entity)
|
private void HandleCollidableAdd(IEntity entity)
|
||||||
{
|
{
|
||||||
// It's a grid / gone / a door / we already have it (which probably shouldn't happen)
|
|
||||||
if (entity.Prototype == null ||
|
if (entity.Prototype == null ||
|
||||||
entity.Deleted ||
|
entity.Deleted ||
|
||||||
entity.HasComponent<ServerDoorComponent>() ||
|
_lastKnownPositions.ContainsKey(entity) ||
|
||||||
entity.HasComponent<AirlockComponent>() ||
|
!entity.TryGetComponent(out CollidableComponent collidableComponent) ||
|
||||||
_lastKnownPositions.ContainsKey(entity))
|
!collidableComponent.CanCollide ||
|
||||||
|
(TrackedCollisionLayers & collidableComponent.CollisionLayer) == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var grid = _mapManager.GetGrid(entity.Transform.GridID);
|
var grid = _mapManager.GetGrid(entity.Transform.GridID);
|
||||||
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
|
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
|
||||||
|
|
||||||
var collisionLayer = entity.GetComponent<CollidableComponent>().CollisionLayer;
|
|
||||||
|
|
||||||
var chunk = GetChunk(tileRef);
|
var chunk = GetChunk(tileRef);
|
||||||
var node = chunk.GetNode(tileRef);
|
var node = chunk.GetNode(tileRef);
|
||||||
node.AddCollisionLayer(collisionLayer);
|
|
||||||
|
|
||||||
|
node.AddEntity(entity);
|
||||||
_lastKnownPositions.Add(entity, tileRef);
|
_lastKnownPositions.Add(entity, tileRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,46 +335,37 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
{
|
{
|
||||||
if (entity.Prototype == null ||
|
if (entity.Prototype == null ||
|
||||||
entity.Deleted ||
|
entity.Deleted ||
|
||||||
entity.HasComponent<ServerDoorComponent>() ||
|
!_lastKnownPositions.ContainsKey(entity) ||
|
||||||
entity.HasComponent<AirlockComponent>() ||
|
!entity.TryGetComponent(out CollidableComponent collidableComponent) ||
|
||||||
!_lastKnownPositions.ContainsKey(entity))
|
!collidableComponent.CanCollide ||
|
||||||
|
(TrackedCollisionLayers & collidableComponent.CollisionLayer) == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastKnownPositions.Remove(entity);
|
|
||||||
|
|
||||||
var grid = _mapManager.GetGrid(entity.Transform.GridID);
|
var grid = _mapManager.GetGrid(entity.Transform.GridID);
|
||||||
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
|
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
|
||||||
|
|
||||||
if (!entity.TryGetComponent(out CollidableComponent collidableComponent))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var collisionLayer = collidableComponent.CollisionLayer;
|
|
||||||
|
|
||||||
var chunk = GetChunk(tileRef);
|
var chunk = GetChunk(tileRef);
|
||||||
var node = chunk.GetNode(tileRef);
|
var node = chunk.GetNode(tileRef);
|
||||||
node.RemoveCollisionLayer(collisionLayer);
|
|
||||||
|
node.RemoveEntity(entity);
|
||||||
|
_lastKnownPositions.Remove(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueCollidableMove(MoveEvent moveEvent)
|
private void QueueCollidableMove(MoveEvent moveEvent)
|
||||||
{
|
{
|
||||||
_queuedGraphUpdates.Enqueue(new CollidableMove(moveEvent));
|
_moveUpdateQueue.Enqueue(moveEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCollidableMove(CollidableMove move)
|
private void HandleCollidableMove(MoveEvent moveEvent)
|
||||||
{
|
{
|
||||||
if (!_lastKnownPositions.ContainsKey(move.MoveEvent.Sender))
|
if (!_lastKnownPositions.ContainsKey(moveEvent.Sender))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pathfinding graph is tile-based so first we'll check if they're on a different tile and if we need to update.
|
// The pathfinding graph is tile-based so first we'll check if they're on a different tile and if we need to update.
|
||||||
// If you get entities bigger than 1 tile wide you'll need some other system so god help you.
|
// If you get entities bigger than 1 tile wide you'll need some other system so god help you.
|
||||||
var moveEvent = move.MoveEvent;
|
|
||||||
|
|
||||||
if (moveEvent.Sender.Deleted)
|
if (moveEvent.Sender.Deleted)
|
||||||
{
|
{
|
||||||
HandleCollidableRemove(moveEvent.Sender);
|
HandleCollidableRemove(moveEvent.Sender);
|
||||||
@@ -314,14 +382,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
|
|
||||||
_lastKnownPositions[moveEvent.Sender] = newTile;
|
_lastKnownPositions[moveEvent.Sender] = newTile;
|
||||||
|
|
||||||
if (!moveEvent.Sender.TryGetComponent(out CollidableComponent collidableComponent))
|
if (!moveEvent.Sender.HasComponent<CollidableComponent>())
|
||||||
{
|
{
|
||||||
HandleCollidableRemove(moveEvent.Sender);
|
HandleCollidableRemove(moveEvent.Sender);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var collisionLayer = collidableComponent.CollisionLayer;
|
|
||||||
|
|
||||||
var gridIds = new HashSet<GridId>(2) {oldTile.GridIndex, newTile.GridIndex};
|
var gridIds = new HashSet<GridId>(2) {oldTile.GridIndex, newTile.GridIndex};
|
||||||
|
|
||||||
foreach (var gridId in gridIds)
|
foreach (var gridId in gridIds)
|
||||||
@@ -330,33 +396,53 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
|
|||||||
{
|
{
|
||||||
var oldChunk = GetChunk(oldTile);
|
var oldChunk = GetChunk(oldTile);
|
||||||
var oldNode = oldChunk.GetNode(oldTile);
|
var oldNode = oldChunk.GetNode(oldTile);
|
||||||
oldNode.RemoveCollisionLayer(collisionLayer);
|
oldNode.RemoveEntity(moveEvent.Sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newTile.GridIndex == gridId)
|
if (newTile.GridIndex == gridId)
|
||||||
{
|
{
|
||||||
var newChunk = GetChunk(newTile);
|
var newChunk = GetChunk(newTile);
|
||||||
var newNode = newChunk.GetNode(newTile);
|
var newNode = newChunk.GetNode(newTile);
|
||||||
newNode.RemoveCollisionLayer(collisionLayer);
|
newNode.AddEntity(moveEvent.Sender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueCollisionEnabledEvent(CollisionChangeEvent collisionEvent)
|
private void QueueCollisionEnabledEvent(CollisionChangeEvent collisionEvent)
|
||||||
{
|
{
|
||||||
// TODO: Handle containers
|
_collidableUpdateQueue.Enqueue(collisionEvent);
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
var entity = entityManager.GetEntity(collisionEvent.Owner);
|
|
||||||
switch (collisionEvent.CanCollide)
|
|
||||||
{
|
|
||||||
case true:
|
|
||||||
_queuedGraphUpdates.Enqueue(new CollisionChange(entity, true));
|
|
||||||
break;
|
|
||||||
case false:
|
|
||||||
_queuedGraphUpdates.Enqueue(new CollisionChange(entity, false));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
// TODO: Need to rethink the pathfinder utils (traversable etc.). Maybe just chuck them all in PathfindingSystem
|
||||||
|
// Otherwise you get the steerer using this and the pathfinders using a different traversable.
|
||||||
|
// Also look at increasing tile cost the more physics entities are on it
|
||||||
|
public bool CanTraverse(IEntity entity, GridCoordinates grid)
|
||||||
|
{
|
||||||
|
var tile = _mapManager.GetGrid(grid.GridID).GetTileRef(grid);
|
||||||
|
var node = GetNode(tile);
|
||||||
|
return CanTraverse(entity, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanTraverse(IEntity entity, PathfindingNode node)
|
||||||
|
{
|
||||||
|
if (entity.TryGetComponent(out CollidableComponent collidableComponent) &&
|
||||||
|
(collidableComponent.CollisionMask & node.BlockedCollisionMask) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var access = AccessReader.FindAccessTags(entity);
|
||||||
|
|
||||||
|
foreach (var reader in node.AccessReaders)
|
||||||
|
{
|
||||||
|
if (!reader.IsAllowed(access))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.Components.Timing;
|
using Content.Server.GameObjects.Components.Timing;
|
||||||
@@ -955,22 +955,34 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify player has a hand, and find what object he is currently holding in his active hand
|
if (!ActionBlockerSystem.CanAttack(player))
|
||||||
if (!player.TryGetComponent<IHandsComponent>(out var hands))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = hands.GetActiveHand?.Owner;
|
|
||||||
|
|
||||||
// TODO: If item is null we need some kinda unarmed combat.
|
|
||||||
if (!ActionBlockerSystem.CanAttack(player) || item == null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventArgs = new AttackEventArgs(player, coordinates);
|
var eventArgs = new AttackEventArgs(player, coordinates);
|
||||||
foreach (var attackComponent in item.GetAllComponents<IAttack>())
|
|
||||||
|
// Verify player has a hand, and find what object he is currently holding in his active hand
|
||||||
|
if (player.TryGetComponent<IHandsComponent>(out var hands))
|
||||||
|
{
|
||||||
|
var item = hands.GetActiveHand?.Owner;
|
||||||
|
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
var attacked = false;
|
||||||
|
foreach (var attackComponent in item.GetAllComponents<IAttack>())
|
||||||
|
{
|
||||||
|
attackComponent.Attack(eventArgs);
|
||||||
|
attacked = true;
|
||||||
|
}
|
||||||
|
if (attacked)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var attackComponent in player.GetAllComponents<IAttack>())
|
||||||
{
|
{
|
||||||
attackComponent.Attack(eventArgs);
|
attackComponent.Attack(eventArgs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,6 +437,8 @@ namespace Content.Server.GameTicking
|
|||||||
lobbyCountdownMessage.Paused = Paused;
|
lobbyCountdownMessage.Paused = Paused;
|
||||||
_netManager.ServerSendToAll(lobbyCountdownMessage);
|
_netManager.ServerSendToAll(lobbyCountdownMessage);
|
||||||
|
|
||||||
|
_chatManager.DispatchServerAnnouncement($"Round start has been delayed for {time.TotalSeconds} seconds.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,6 +465,10 @@ namespace Content.Server.GameTicking
|
|||||||
lobbyCountdownMessage.Paused = Paused;
|
lobbyCountdownMessage.Paused = Paused;
|
||||||
_netManager.ServerSendToAll(lobbyCountdownMessage);
|
_netManager.ServerSendToAll(lobbyCountdownMessage);
|
||||||
|
|
||||||
|
_chatManager.DispatchServerAnnouncement(Paused
|
||||||
|
? "Round start has been paused."
|
||||||
|
: "Round start countdown is now resumed.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -838,6 +844,7 @@ namespace Content.Server.GameTicking
|
|||||||
msg.IsRoundStarted = RunLevel != GameRunLevel.PreRoundLobby;
|
msg.IsRoundStarted = RunLevel != GameRunLevel.PreRoundLobby;
|
||||||
msg.StartTime = _roundStartTimeUtc;
|
msg.StartTime = _roundStartTimeUtc;
|
||||||
msg.YouAreReady = ready;
|
msg.YouAreReady = ready;
|
||||||
|
msg.Paused = Paused;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameObjects;
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.Components.Nutrition;
|
using Content.Server.GameObjects.Components.Nutrition;
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
using Robust.Server.Console;
|
using Robust.Server.Console;
|
||||||
@@ -62,6 +63,10 @@ namespace Content.Server.GlobalVerbs
|
|||||||
{
|
{
|
||||||
thirst.ResetThirst();
|
thirst.ResetThirst();
|
||||||
}
|
}
|
||||||
|
if (target.TryGetComponent(out StunnableComponent stun))
|
||||||
|
{
|
||||||
|
stun.ResetStuns();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ namespace Content.Shared.GameObjects.Components.Sound
|
|||||||
|
|
||||||
public void ExposeData(ObjectSerializer serializer)
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
|
if (!serializer.Reading)
|
||||||
|
return;
|
||||||
|
|
||||||
Filename = serializer.ReadDataField("filename", "");
|
Filename = serializer.ReadDataField("filename", "");
|
||||||
Delay = serializer.ReadDataField("delay", 0u);
|
Delay = serializer.ReadDataField("delay", 0u);
|
||||||
RandomDelay = serializer.ReadDataField("randomdelay", 0u);
|
RandomDelay = serializer.ReadDataField("randomdelay", 0u);
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is sent if the MagazineBarrel AutoEjects the magazine
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class MagazineAutoEjectMessage : ComponentMessage {}
|
||||||
|
}
|
||||||
@@ -66,6 +66,7 @@ namespace Content.Shared
|
|||||||
public bool YouAreReady { get; set; }
|
public bool YouAreReady { get; set; }
|
||||||
// UTC.
|
// UTC.
|
||||||
public DateTime StartTime { get; set; }
|
public DateTime StartTime { get; set; }
|
||||||
|
public bool Paused { get; set; }
|
||||||
|
|
||||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||||
{
|
{
|
||||||
@@ -78,6 +79,7 @@ namespace Content.Shared
|
|||||||
|
|
||||||
YouAreReady = buffer.ReadBoolean();
|
YouAreReady = buffer.ReadBoolean();
|
||||||
StartTime = new DateTime(buffer.ReadInt64(), DateTimeKind.Utc);
|
StartTime = new DateTime(buffer.ReadInt64(), DateTimeKind.Utc);
|
||||||
|
Paused = buffer.ReadBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||||
@@ -91,6 +93,7 @@ namespace Content.Shared
|
|||||||
|
|
||||||
buffer.Write(YouAreReady);
|
buffer.Write(YouAreReady);
|
||||||
buffer.Write(StartTime.Ticks);
|
buffer.Write(StartTime.Ticks);
|
||||||
|
buffer.Write(Paused);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
Resources/Audio/Guns/Gunshots/rpgfire.ogg
Normal file
@@ -38,10 +38,9 @@
|
|||||||
- RifleBlackAk
|
- RifleBlackAk
|
||||||
- RifleCarbine
|
- RifleCarbine
|
||||||
- RifleDallas
|
- RifleDallas
|
||||||
- RifleIhHeavy
|
- RifleSTS
|
||||||
- RifleSolEot
|
- RifleVintorez
|
||||||
- RifleSolPara
|
- RifleWintermute
|
||||||
- RifleSts
|
|
||||||
chance: 0.75
|
chance: 0.75
|
||||||
gameRules:
|
gameRules:
|
||||||
- RuleSuspicion
|
- RuleSuspicion
|
||||||
@@ -62,14 +61,16 @@
|
|||||||
- type: ConditionalSpawner
|
- type: ConditionalSpawner
|
||||||
prototypes:
|
prototypes:
|
||||||
- PistolClarissa
|
- PistolClarissa
|
||||||
- PistolDeagle
|
- PistolColt
|
||||||
- PistolDeckard
|
|
||||||
- PistolGiskard
|
- PistolGiskard
|
||||||
- PistolGyro
|
- PistolHMPistol
|
||||||
- PistolLamia
|
- PistolLamia
|
||||||
- PistolMakarov
|
- PistolMandella
|
||||||
- PistolMk58
|
- PistolMk58
|
||||||
- PistolOlivawCivil
|
- PistolMk58Wood
|
||||||
|
- PistolMolly
|
||||||
|
- PistolOlivaw
|
||||||
|
- PistolPaco
|
||||||
chance: 0.75
|
chance: 0.75
|
||||||
gameRules:
|
gameRules:
|
||||||
- RuleSuspicion
|
- RuleSuspicion
|
||||||
@@ -94,6 +95,171 @@
|
|||||||
- Spear
|
- Spear
|
||||||
- ToolboxEmergency
|
- ToolboxEmergency
|
||||||
- CrowbarRed
|
- CrowbarRed
|
||||||
|
- Stunbaton
|
||||||
|
chance: 0.75
|
||||||
|
gameRules:
|
||||||
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Suspicion Revolver Spawner
|
||||||
|
id: SuspicionRevolverSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_revolver
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_revolver
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- RevolverDeckard
|
||||||
|
- RevolverInspector
|
||||||
|
- RevolverMateba
|
||||||
|
chance: 0.75
|
||||||
|
gameRules:
|
||||||
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Suspicion Shotgun Spawner
|
||||||
|
id: SuspicionShotgunSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_shotgun
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_shotgun
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- ShotgunBojevic
|
||||||
|
- ShotgunDB
|
||||||
|
- ShotgunBull
|
||||||
|
- ShotgunGladstone
|
||||||
|
- ShotgunRegulator
|
||||||
|
- ShotgunPump
|
||||||
|
- ShotgunSawn
|
||||||
|
chance: 0.75
|
||||||
|
gameRules:
|
||||||
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Suspicion SMG Spawner
|
||||||
|
id: SuspicionSMGSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_smg
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_smg
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- SmgAtreides
|
||||||
|
- SmgC20r
|
||||||
|
- SmgDrozd
|
||||||
|
- SmgStraylight
|
||||||
|
- SmgWt550
|
||||||
|
- SmgZoric
|
||||||
|
chance: 0.75
|
||||||
|
gameRules:
|
||||||
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Suspicion Sniper Spawner
|
||||||
|
id: SuspicionSniperSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_sniper
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_sniper
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- SniperBoltGun
|
||||||
|
- SniperBoltGunWood
|
||||||
|
- SniperHeavy
|
||||||
|
chance: 0.75
|
||||||
|
gameRules:
|
||||||
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Suspicion Hitscan Spawner
|
||||||
|
id: SuspicionHitscanSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_hitscan
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_hitscan
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- RedLaser
|
||||||
|
- RedHeavyLaser
|
||||||
|
- XrayLaser
|
||||||
|
- LaserGun
|
||||||
|
- LaserCannon
|
||||||
|
- XrayCannon
|
||||||
|
- TaserGun
|
||||||
|
chance: 0.75
|
||||||
|
gameRules:
|
||||||
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Suspicion Launchers Spawner
|
||||||
|
id: SuspicionLaunchersSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_launcher
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_launcher
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- LauncherChinaLake
|
||||||
|
- LauncherRocket
|
||||||
|
chance: 0.75
|
||||||
|
gameRules:
|
||||||
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Suspicion Grenades Spawner
|
||||||
|
id: SuspicionGrenadesSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_grenade
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_grenade
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- ExGrenade
|
||||||
|
- GrenadeFlashBang
|
||||||
|
- SyndieMiniBomb
|
||||||
chance: 0.75
|
chance: 0.75
|
||||||
gameRules:
|
gameRules:
|
||||||
- RuleSuspicion
|
- RuleSuspicion
|
||||||
|
|||||||
42
Resources/Prototypes/Entities/Markers/timed_spawners.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
- type: entity
|
||||||
|
name: base timed spawner
|
||||||
|
id: BaseTimedSpawner
|
||||||
|
abstract: true
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: cross_blue
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: cross_blue
|
||||||
|
- type: Marker
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Collidable
|
||||||
|
- type: TimedSpawner
|
||||||
|
placement:
|
||||||
|
mode: AlignTileAny
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: AI Timed Spawner
|
||||||
|
id: AITimedSpawner
|
||||||
|
parent: BaseTimedSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_rifle
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/markers.rsi
|
||||||
|
state: spawner_rifle
|
||||||
|
- type: TimedSpawner
|
||||||
|
prototypes:
|
||||||
|
- HumanMob_Spirate
|
||||||
|
- HumanMob_Civilian
|
||||||
|
chance: 0.75
|
||||||
|
intervalSeconds: 60
|
||||||
|
minimumEntitiesSpawned: 1
|
||||||
|
maximumEntitiesSpawned: 5
|
||||||
@@ -134,12 +134,15 @@
|
|||||||
- type: HumanoidAppearance
|
- type: HumanoidAppearance
|
||||||
- type: Stunnable
|
- type: Stunnable
|
||||||
- type: AnimationPlayer
|
- type: AnimationPlayer
|
||||||
|
- type: UnarmedCombat
|
||||||
|
range: 0.8
|
||||||
|
arcwidth: 30
|
||||||
|
arc: fist
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
save: false
|
save: false
|
||||||
name: Urist McHands
|
name: Urist McHands
|
||||||
parent: BaseHumanMob_Content
|
parent: BaseHumanMob_Content
|
||||||
abstract: true
|
|
||||||
id: HumanMob_Content
|
id: HumanMob_Content
|
||||||
description: A miserable pile of secrets
|
description: A miserable pile of secrets
|
||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
@@ -161,6 +164,7 @@
|
|||||||
save: false
|
save: false
|
||||||
name: Urist McHands
|
name: Urist McHands
|
||||||
id: HumanMob_Dummy
|
id: HumanMob_Dummy
|
||||||
|
abstract: true
|
||||||
description: A dummy human meant to be used in character setup
|
description: A dummy human meant to be used in character setup
|
||||||
components:
|
components:
|
||||||
- type: Hands
|
- type: Hands
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
fireRate: 0.5
|
fireRate: 0.5
|
||||||
capacity: 1
|
capacity: 1
|
||||||
soundEmpty: /Audio/Guns/Empty/empty.ogg
|
soundEmpty: /Audio/Guns/Empty/empty.ogg
|
||||||
soundGunshot: /Audio/Guns/Gunshots/bang.ogg
|
soundGunshot: /Audio/Guns/Gunshots/rpgfire.ogg
|
||||||
soundInsert: /Audio/Guns/MagIn/batrifle_magin.ogg
|
soundInsert: /Audio/Guns/MagIn/batrifle_magin.ogg
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
visuals:
|
visuals:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
hard: false
|
hard: false
|
||||||
shapes:
|
shapes:
|
||||||
- !type:PhysShapeAabb
|
- !type:PhysShapeAabb
|
||||||
bounds: "-0.2,-0.2,0.2,0.2"
|
bounds: "-0.1,-0.1,0.1,0.1"
|
||||||
layer: [Clickable]
|
layer: [Clickable]
|
||||||
mask:
|
mask:
|
||||||
- Impassable
|
- Impassable
|
||||||
@@ -130,10 +130,14 @@
|
|||||||
- type: Projectile
|
- type: Projectile
|
||||||
deleteOnCollide: false
|
deleteOnCollide: false
|
||||||
- type: Explosive
|
- type: Explosive
|
||||||
devastationRange: 3
|
devastationRange: 1
|
||||||
heavyImpactRange: 5
|
heavyImpactRange: 2
|
||||||
lightImpactRange: 7
|
lightImpactRange: 4
|
||||||
flashRange: 10
|
flashRange: 10
|
||||||
|
- type: PointLight
|
||||||
|
radius: 3.5
|
||||||
|
color: orange
|
||||||
|
energy: 0.5
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: BulletGrenadeBaton
|
id: BulletGrenadeBaton
|
||||||
|
|||||||
BIN
Resources/Textures/Objects/Guns/Launchers/rocket.rsi/mag-1.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
@@ -19,6 +19,14 @@
|
|||||||
"name": "mag-0",
|
"name": "mag-0",
|
||||||
"directions": 1
|
"directions": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "rocket0-inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rocket0-inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "inhand-left",
|
"name": "inhand-left",
|
||||||
"directions": 4
|
"directions": 4
|
||||||
@@ -28,4 +36,4 @@
|
|||||||
"directions": 4
|
"directions": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 814 B |
|
After Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 266 B After Width: | Height: | Size: 329 B |
@@ -10,6 +10,10 @@
|
|||||||
{
|
{
|
||||||
"name": "frag",
|
"name": "frag",
|
||||||
"directions": 1
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "smallfrag",
|
||||||
|
"directions": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 266 B |
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 435 B After Width: | Height: | Size: 840 B |
|
Before Width: | Height: | Size: 551 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 942 B |
|
Before Width: | Height: | Size: 492 B After Width: | Height: | Size: 927 B |
|
Before Width: | Height: | Size: 540 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 923 B |
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 902 B |
|
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 642 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 921 B |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 1006 B |
|
Before Width: | Height: | Size: 483 B After Width: | Height: | Size: 926 B |
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 736 B |
|
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 965 B |
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 1006 B |
|
Before Width: | Height: | Size: 501 B After Width: | Height: | Size: 945 B |
|
Before Width: | Height: | Size: 518 B After Width: | Height: | Size: 1020 B |
|
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 905 B |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 764 B |
|
Before Width: | Height: | Size: 542 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 828 B |
|
Before Width: | Height: | Size: 389 B After Width: | Height: | Size: 720 B |
|
Before Width: | Height: | Size: 527 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 1.0 KiB |