Merge remote-tracking branch 'upstream/master' into weapon_anims

# Conflicts:
#	Resources/Prototypes/MeleeWeaponAnimations/default.yml
This commit is contained in:
Metal Gear Sloth
2020-06-24 20:42:16 +10:00
124 changed files with 1580 additions and 357 deletions

View File

@@ -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";

View File

@@ -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();
} }
} }
} }

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,7 @@ using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.State; using Robust.Client.Interfaces.State;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -41,6 +42,7 @@ namespace Content.Client
[Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner; [Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner;
[Dependency] private readonly IGameController _gameController; [Dependency] private readonly IGameController _gameController;
[Dependency] private readonly IStateManager _stateManager; [Dependency] private readonly IStateManager _stateManager;
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649 #pragma warning restore 649
public override void Init() public override void Init()
@@ -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()

View File

@@ -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;
} }
} }

View File

@@ -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));

View File

@@ -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);
} }

View File

@@ -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();
} }

View File

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

View File

@@ -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;
} }
} }
}); });

View File

@@ -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>

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems; 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,

View File

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

View File

@@ -126,6 +126,9 @@ namespace Content.Server.AI.Utility.AiLogic
{ {
damageableComponent.DamageThresholdPassed -= DeathHandle; damageableComponent.DamageThresholdPassed -= DeathHandle;
} }
var currentOp = CurrentAction?.ActionOperators.Peek();
currentOp?.Shutdown(Outcome.Failed);
} }
private void DeathHandle(object sender, DamageThresholdPassedEventArgs eventArgs) private void DeathHandle(object sender, DamageThresholdPassedEventArgs eventArgs)

View File

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

View File

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

View File

@@ -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;

View File

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

View File

@@ -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;

View File

@@ -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)";

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
} }

View File

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

View File

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

View File

@@ -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;
} }

View File

@@ -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)

View File

@@ -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;
} }

View File

@@ -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";

View File

@@ -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;

View File

@@ -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";

View File

@@ -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)";

View File

@@ -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";
}
}

View File

@@ -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
} }
} }
} }
} }

View File

@@ -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
} }
} }
} }
} }

View File

@@ -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)
{ {

View File

@@ -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));

View File

@@ -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;
} }

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,7 +0,0 @@
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates
{
public interface IPathfindingGraphUpdate
{
}
}

View File

@@ -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; }
}
}

View File

@@ -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)
{ {

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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);
} }

View File

@@ -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;
} }

View File

@@ -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();
}
} }
} }
} }

View File

@@ -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);

View File

@@ -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 {}
}

View File

@@ -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);
} }
} }

Binary file not shown.

View 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

View 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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -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
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 329 B

View File

@@ -10,6 +10,10 @@
{ {
"name": "frag", "name": "frag",
"directions": 1 "directions": 1
},
{
"name": "smallfrag",
"directions": 1
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 B

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 B

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 B

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

After

Width:  |  Height:  |  Size: 1.0 KiB

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