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 BenchmarkDotNet.Attributes;
using Moq;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Log;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Log;
namespace Content.Benchmarks
{
@@ -14,7 +18,17 @@ namespace Content.Benchmarks
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]
public void Setup()
@@ -23,6 +37,13 @@ namespace Content.Benchmarks
IoCManager.InitThread();
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>();
dummyReg.SetupGet(p => p.Name).Returns("Dummy");
@@ -34,17 +55,19 @@ namespace Content.Benchmarks
var componentFactory = new Mock<IComponentFactory>();
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
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.BuildGraph();
_componentManager = IoCManager.Resolve<IComponentManager>();
_componentManager.Initialize();
// Initialize N entities with one component.
for (var i = 0; i < N; i++)
{
var entity = new Entity();
entity.SetManagers(entityManager);
entity.SetUid(new EntityUid(i + 1));
_entities.Add(entity);
@@ -65,6 +88,16 @@ namespace Content.Benchmarks
return count;
}
[Benchmark]
public int Noop()
{
var count = 0;
_componentManager.TryGetComponent(default, out DummyComponent _);
return count;
}
private class DummyComponent : Component
{
public override string Name => "Dummy";

View File

@@ -6,7 +6,8 @@ namespace Content.Benchmarks
{
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;
#endif
#else
return true;
#endif
}
}
}

View File

@@ -55,8 +55,9 @@ namespace Content.Client.Commands
}
return !anyAction;
#endif
#else
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.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
@@ -41,6 +42,7 @@ namespace Content.Client
[Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner;
[Dependency] private readonly IGameController _gameController;
[Dependency] private readonly IStateManager _stateManager;
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649
public override void Init()
@@ -223,6 +225,8 @@ namespace Content.Client
{
IoCManager.Resolve<IMapManager>().CreateNewMapEntity(MapId.Nullspace);
};
_configurationManager.RegisterCVar("outline.enabled", true);
}
/// <summary>
@@ -248,9 +252,12 @@ namespace Content.Client
/// Remove the character interface master from this entity now that we have detached ourselves from it
/// </summary>
public static void DetachPlayerFromEntity(EntityDetachedEventArgs eventArgs)
{
if (!eventArgs.OldEntity.Deleted)
{
eventArgs.OldEntity.RemoveComponent<CharacterInterface>();
}
}
public override void PostInit()
{

View File

@@ -3,6 +3,7 @@ using Content.Client.Animations;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Robust.Client.Animations;
using Robust.Client.Graphics;
@@ -112,10 +113,10 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
switch (message)
{
/*
case BmwComponentAutoEjectedMessage _:
case MagazineAutoEjectMessage _:
_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.
var grid = _mapManager.GetGrid(ev.LastPosition.Value.grid);
if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
{
var pos = ev.LastPosition.Value.pos;
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)
{
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);
UpdateTile(grid, indices);
}

View File

@@ -55,6 +55,7 @@ namespace Content.Client.GameTicking
StartTime = message.StartTime;
IsGameStarted = message.IsRoundStarted;
AreWeReady = message.YouAreReady;
Paused = message.Paused;
LobbyStatusUpdated?.Invoke();
}

View File

@@ -12,6 +12,7 @@ using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Timing;
@@ -36,6 +37,8 @@ namespace Content.Client.State
[Dependency] private readonly IGameTiming _timing;
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager;
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649
private IEntity _lastHoveredEntity;
@@ -72,6 +75,15 @@ namespace Content.Client.State
}
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 != null && entityToClick.TryGetComponent(out outline))

View File

@@ -69,7 +69,8 @@ namespace Content.IntegrationTests.Tests
catch (Exception e)
{
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.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Robust.Server.Interfaces.Maps;
@@ -37,19 +38,34 @@ namespace Content.IntegrationTests.Tests
string one;
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))
{
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))
{
two = reader.ReadToEnd();
}
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>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems;
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 endGrid = _mapManager.GetGrid(TargetGrid.GridID).GetTileRef(TargetGrid);;
// _routeCancelToken = new CancellationTokenSource();
var access = AccessReader.FindAccessTags(Owner);
RouteJob = _pathfinder.RequestPath(new PathfindingArgs(
Owner.Uid,
access,
collisionMask,
startGrid,
endGrid,

View File

@@ -29,11 +29,17 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
public override void SetupOperators(Blackboard context)
{
var moveOperator = new MoveToEntityOperator(Owner, _entity);
var equipped = context.GetState<EquippedEntityState>().GetValue();
MoveToEntityOperator moveOperator;
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[]

View File

@@ -126,6 +126,9 @@ namespace Content.Server.AI.Utility.AiLogic
{
damageableComponent.DamageThresholdPassed -= DeathHandle;
}
var currentOp = CurrentAction?.ActionOperators.Peek();
currentOp?.Shutdown(Outcome.Failed);
}
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]
private static ICollection<string> FindAccessTags(IEntity entity)
public static ICollection<string> FindAccessTags(IEntity entity)
{
if (entity.TryGetComponent(out IAccess accessComponent))
{

View File

@@ -219,12 +219,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
{
protected override void GetData(IEntity user, SolutionComponent component, VerbData data)
{
if (user.TryGetComponent<HandsComponent>(out var hands))
{
if (hands.GetActiveHand != null)
{
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
if (!ActionBlockerSystem.CanInteract(user) ||
!user.TryGetComponent<HandsComponent>(out var hands) ||
hands.GetActiveHand == null ||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if ((solution.Capabilities & SolutionCaps.PourOut) != 0 &&
(component.Capabilities & SolutionCaps.PourIn) != 0)
{
@@ -234,9 +237,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
data.Text= $"Transfer liquid from [{heldEntityName}] to [{myName}].";
return;
}
}
}
}
data.Visibility = VerbVisibility.Invisible;
}
@@ -318,12 +318,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
{
protected override void GetData(IEntity user, SolutionComponent component, VerbData data)
{
if (user.TryGetComponent<HandsComponent>(out var hands))
{
if (hands.GetActiveHand != null)
{
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
if (!ActionBlockerSystem.CanInteract(user) ||
!user.TryGetComponent<HandsComponent>(out var hands) ||
hands.GetActiveHand == null ||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if ((solution.Capabilities & SolutionCaps.PourIn) != 0 &&
(component.Capabilities & SolutionCaps.PourOut) != 0)
{
@@ -333,9 +336,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
data.Text = $"Transfer liquid from [{myName}] to [{heldEntityName}].";
return;
}
}
}
}
data.Visibility = VerbVisibility.Invisible;
}

View File

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

View File

@@ -1,5 +1,6 @@
using System;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Content.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)
{
if (!component.Owner.TryGetComponent(out SolutionComponent solutionComponent))
if (!ActionBlockerSystem.CanInteract(user) ||
!component.Owner.TryGetComponent(out SolutionComponent solutionComponent))
{
data.Visibility = VerbVisibility.Invisible;
return;

View File

@@ -240,6 +240,12 @@ namespace Content.Server.GameObjects.Components.Interactable
{
protected override void GetData(IEntity user, HandheldLightComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component.Cell == null)
{
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)
{
component.OpenVerbGetData(user, component, data);
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
component.OpenVerbGetData(user, component, data);
}
/// <inheritdoc />
@@ -351,6 +356,12 @@ namespace Content.Server.GameObjects.Components
protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (IsWeldedShut)
{
data.Visibility = VerbVisibility.Disabled;

View File

@@ -116,7 +116,9 @@ namespace Content.Server.GameObjects
{
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;
return;

View File

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

View File

@@ -60,7 +60,7 @@ namespace Content.Server.GameObjects
base.ExposeData(serializer);
serializer.DataField(ref StorageCapacityMax, "Capacity", 10000);
serializer.DataField(ref StorageUsed, "used", 0);
//serializer.DataField(ref StorageUsed, "used", 0);
}
/// <summary>
@@ -348,7 +348,7 @@ namespace Content.Server.GameObjects
foreach (var entity in storage.ContainedEntities)
{
var item = entity.GetComponent<ItemComponent>();
var item = entity.GetComponent<StoreableComponent>();
StorageUsed += item.ObjectSize;
}

View File

@@ -2,18 +2,15 @@ using System;
using System.Collections.Generic;
using Content.Server.GameTicking;
using Content.Server.Interfaces.GameTicking;
using NFluidsynth;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using SQLitePCL;
using Logger = Robust.Shared.Log.Logger;
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)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = "Enter";
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)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = "Eject";
data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible;
}

View File

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

View File

@@ -250,6 +250,12 @@ namespace Content.Server.GameObjects.Components.PDA
{
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.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)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
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)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component._container.ContainedEntity == null)
{
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)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
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)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component._container.ContainedEntity == null)
{
data.Visibility = VerbVisibility.Disabled;

View File

@@ -1,3 +1,4 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.GameObjects;
using Robust.Server.GameObjects;
@@ -37,6 +38,12 @@ namespace Content.Server.GameObjects.Components
{
protected override void GetData(IEntity user, RotatableComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.CategoryData = VerbCategories.Rotate;
data.Text = "Rotate clockwise";
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)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.CategoryData = VerbCategories.Rotate;
data.Text = "Rotate counter-clockwise";
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(VVAccess.ReadWrite)]
private float _paralyzeChance = 0.25f;
private float _paralyzeChanceNoSlowdown = 0.35f;
[ViewVariables(VVAccess.ReadWrite)]
private float _paralyzeChanceWithSlowdown = 0.85f;
[ViewVariables(VVAccess.ReadWrite)]
private float _paralyzeTime = 10f;
@@ -75,7 +78,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
{
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 _slowdownTime, "slowdownTime", 5f);
}
@@ -92,7 +96,13 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
{
if (!entity.TryGetComponent(out StunnableComponent stunnable)) continue;
if(_robustRandom.Prob(_paralyzeChance))
if(!stunnable.SlowedDown)
if(_robustRandom.Prob(_paralyzeChanceNoSlowdown))
stunnable.Paralyze(_paralyzeTime);
else
stunnable.Slowdown(_slowdownTime);
else
if(_robustRandom.Prob(_paralyzeChanceWithSlowdown))
stunnable.Paralyze(_paralyzeTime);
else
stunnable.Slowdown(_slowdownTime);
@@ -252,6 +262,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
{
protected override void GetData(IEntity user, StunbatonComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component.Cell == null)
{
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

@@ -207,6 +207,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
{
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.Visibility = component.AmmoLeft > 0 ? VerbVisibility.Visible : VerbVisibility.Disabled;
}

View File

@@ -294,6 +294,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
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.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
}
@@ -309,6 +315,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
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.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled;
}

View File

@@ -42,8 +42,15 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
base.ExposeData(serializer);
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
if (serializer.Reading)
{
var capacity = serializer.ReadDataField("capacity", 6);
_ammoSlots = new IEntity[capacity];
}
// TODO: Writing?
// Sounds
serializer.DataField(ref _soundEject, "soundEject", "/Audio/Guns/MagOut/revolver_magout.ogg");
@@ -214,6 +221,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
protected override void GetData(IEntity user, RevolverBarrelComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("Spin");
if (component.Capacity <= 1)
{

View File

@@ -170,7 +170,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
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)
{
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.EntitySystems;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Content.Shared.Interfaces;
using Robust.Server.GameObjects;
@@ -217,6 +218,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
}
_magazineContainer.Remove(magazine);
SendNetworkMessage(new MagazineAutoEjectMessage());
}
if (nextRound == null && !BoltOpen)
@@ -389,6 +391,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
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");
if (component.MagNeedsOpenBolt)
{
@@ -412,6 +420,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
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.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
}
@@ -427,6 +441,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
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.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 (!Utils.TryEndNode(ref _endNode, _pathfindingArgs))
if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
{
return null;
}
@@ -88,9 +88,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
}
// 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;
}
@@ -107,7 +107,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
// pFactor is tie-breaker where the fscore is otherwise equal.
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
// 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));
}
}
@@ -117,7 +117,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
return null;
}
var route = Utils.ReconstructPath(cameFrom, currentNode);
var route = PathfindingHelpers.ReconstructPath(cameFrom, currentNode);
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 (!Utils.TryEndNode(ref _endNode, _pathfindingArgs))
if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
{
return null;
}
@@ -89,7 +89,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
jumpNodes.Add(jumpNode);
#endif
// 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)
{
@@ -108,7 +108,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
// pFactor is tie-breaker where the fscore is otherwise equal.
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
// 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));
}
}
@@ -119,7 +119,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
return null;
}
var route = Utils.ReconstructJumpPath(cameFrom, currentNode);
var route = PathfindingHelpers.ReconstructJumpPath(cameFrom, currentNode);
if (route.Count == 1)
{
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
// Nodes should be 2-way anyway.
if (nextNode == null ||
Utils.GetTileCost(_pathfindingArgs, currentNode, nextNode) == null)
PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode) == null)
{
return null;
}
@@ -312,14 +312,14 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
throw new ArgumentOutOfRangeException();
}
if ((closedNeighborOne == null || Utils.GetTileCost(_pathfindingArgs, currentNode, closedNeighborOne) == null)
&& openNeighborOne != null && Utils.GetTileCost(_pathfindingArgs, currentNode, openNeighborOne) != null)
if ((closedNeighborOne == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborOne) == null)
&& openNeighborOne != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborOne) != null)
{
return true;
}
if ((closedNeighborTwo == null || Utils.GetTileCost(_pathfindingArgs, currentNode, closedNeighborTwo) == null)
&& openNeighborTwo != null && Utils.GetTileCost(_pathfindingArgs, currentNode, openNeighborTwo) != null)
if ((closedNeighborTwo == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborTwo) == null)
&& openNeighborTwo != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborTwo) != null)
{
return true;
}
@@ -371,14 +371,14 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
throw new ArgumentOutOfRangeException();
}
if ((closedNeighborOne == null || !Utils.Traversable(_pathfindingArgs.CollisionMask, closedNeighborOne.CollisionMask)) &&
(openNeighborOne != null && Utils.Traversable(_pathfindingArgs.CollisionMask, openNeighborOne.CollisionMask)))
if ((closedNeighborOne == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborOne)) &&
(openNeighborOne != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborOne)))
{
return true;
}
if ((closedNeighborTwo == null || !Utils.Traversable(_pathfindingArgs.CollisionMask, closedNeighborTwo.CollisionMask)) &&
(openNeighborTwo != null && Utils.Traversable(_pathfindingArgs.CollisionMask, openNeighborTwo.CollisionMask)))
if ((closedNeighborTwo == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborTwo)) &&
(openNeighborTwo != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborTwo)))
{
return true;
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
@@ -6,6 +7,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
public struct PathfindingArgs
{
public EntityUid Uid { get; }
public ICollection<string> Access { get; }
public int CollisionMask { get; }
public TileRef Start { get; }
public TileRef End { get; }
@@ -20,6 +22,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
public PathfindingArgs(
EntityUid entityUid,
ICollection<string> access,
int collisionMask,
TileRef start,
TileRef end,
@@ -29,6 +32,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
bool allowSpace = false)
{
Uid = entityUid;
Access = access;
CollisionMask = collisionMask;
Start = start;
End = end;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
using Content.Server.GameObjects.EntitySystems.Pathfinding;
using Robust.Shared.Interfaces.GameObjects;
@@ -10,19 +11,19 @@ using Robust.Shared.Maths;
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
{
public static class Utils
public static class PathfindingHelpers
{
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)
{
// TODO: Should make this account for proximities,
// 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;
return true;
@@ -36,7 +37,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
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.
// 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:
if (northNeighbor == null || eastNeighbor == null) return false;
if (!Traversable(collisionMask, northNeighbor.CollisionMask) ||
!Traversable(collisionMask, eastNeighbor.CollisionMask))
if (!Traversable(collisionMask, access, northNeighbor) ||
!Traversable(collisionMask, access, eastNeighbor))
{
return false;
}
break;
case Direction.NorthWest:
if (northNeighbor == null || westNeighbor == null) return false;
if (!Traversable(collisionMask, northNeighbor.CollisionMask) ||
!Traversable(collisionMask, westNeighbor.CollisionMask))
if (!Traversable(collisionMask, access, northNeighbor) ||
!Traversable(collisionMask, access, westNeighbor))
{
return false;
}
break;
case Direction.SouthWest:
if (southNeighbor == null || westNeighbor == null) return false;
if (!Traversable(collisionMask, southNeighbor.CollisionMask) ||
!Traversable(collisionMask, westNeighbor.CollisionMask))
if (!Traversable(collisionMask, access, southNeighbor) ||
!Traversable(collisionMask, access, westNeighbor))
{
return false;
}
break;
case Direction.SouthEast:
if (southNeighbor == null || eastNeighbor == null) return false;
if (!Traversable(collisionMask, southNeighbor.CollisionMask) ||
!Traversable(collisionMask, eastNeighbor.CollisionMask))
if (!Traversable(collisionMask, access, southNeighbor) ||
!Traversable(collisionMask, access, eastNeighbor))
{
return false;
}
@@ -86,9 +87,22 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
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)
@@ -195,6 +209,20 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
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)
{
return Math.Abs(currentNode.TileRef.X - endNode.TileRef.X) + Math.Abs(currentNode.TileRef.Y - endNode.TileRef.Y);
@@ -202,7 +230,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
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;
}

View File

@@ -1,6 +1,12 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Doors;
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.Maths;
@@ -8,27 +14,34 @@ namespace Content.Server.GameObjects.EntitySystems.Pathfinding
{
public class PathfindingNode
{
// TODO: Add access ID here
public PathfindingChunk ParentChunk => _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;
private Dictionary<Direction, PathfindingNode> _neighbors = new Dictionary<Direction, PathfindingNode>();
public PathfindingNode(PathfindingChunk parent, TileRef tileRef, List<int> collisionLayers = null)
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 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;
TileRef = tileRef;
if (collisionLayers == null)
{
CollisionLayers = new List<int>();
}
else
{
CollisionLayers = collisionLayers;
}
GenerateMask();
}
@@ -105,25 +118,70 @@ namespace Content.Server.GameObjects.EntitySystems.Pathfinding
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);
GenerateMask();
// If we're a door
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;
}
public void RemoveCollisionLayer(int layer)
if (entity.TryGetComponent(out CollidableComponent collidableComponent))
{
CollisionLayers.Remove(layer);
if (entity.TryGetComponent(out PhysicsComponent physicsComponent) && !physicsComponent.Anchored)
{
_physicsUids.Add(entity.Uid);
}
else
{
_blockedCollidables.TryAdd(entity.Uid, collidableComponent.CollisionLayer);
GenerateMask();
}
}
}
public void RemoveEntity(IEntity entity)
{
if (_accessReaders.ContainsKey(entity.Uid))
{
_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()
{
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.Collections.Generic;
using System.Threading;
using Content.Server.GameObjects.Components.Doors;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.GraphUpdates;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
using Content.Server.GameObjects.EntitySystems.JobQueues;
using Content.Server.GameObjects.EntitySystems.JobQueues.Queues;
using Content.Server.GameObjects.EntitySystems.Pathfinding;
using Content.Shared.Physics;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
@@ -14,6 +14,7 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
{
@@ -29,18 +30,30 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
public class PathfindingSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entitymanager;
[Dependency] private readonly IMapManager _mapManager;
#pragma warning restore 649
public IReadOnlyDictionary<GridId, Dictionary<MapIndices, PathfindingChunk>> Graph => _graph;
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();
// 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
private readonly Dictionary<IEntity, TileRef> _lastKnownPositions = new Dictionary<IEntity, TileRef>();
public const int TrackedCollisionLayers = (int)
(CollisionGroup.Impassable |
CollisionGroup.MobImpassable |
CollisionGroup.SmallImpassable |
CollisionGroup.VaultImpassable);
/// <summary>
/// Ask for the pathfinder to gimme somethin
/// </summary>
@@ -68,51 +81,66 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
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();
switch (update)
var entity = _entitymanager.GetEntity(update.Owner);
if (update.CanCollide)
{
case CollidableMove move:
HandleCollidableMove(move);
break;
case CollisionChange change:
if (change.Value)
{
HandleCollidableAdd(change.Owner);
HandleCollidableAdd(entity);
}
else
{
HandleCollidableRemove(change.Owner);
HandleAccessRemove(entity);
}
break;
case GridRemoval removal:
HandleGridRemoval(removal);
break;
case TileUpdate tile:
totalUpdates++;
}
_collidableUpdateQueue.Clear();
foreach (var update in _accessReaderUpdateQueue)
{
var entity = _entitymanager.GetEntity(update.Uid);
if (update.Enabled)
{
HandleAccessAdd(entity);
}
else
{
HandleAccessRemove(entity);
}
totalUpdates++;
}
_accessReaderUpdateQueue.Clear();
foreach (var tile in _tileUpdateQueue)
{
HandleTileUpdate(tile);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
totalUpdates++;
}
private void HandleGridRemoval(GridRemoval removal)
_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)
{
if (!_graph.ContainsKey(removal.GridId))
{
throw new InvalidOperationException();
moveUpdateCount = _moveUpdateQueue.Count - 100;
}
_graph.Remove(removal.GridId);
moveUpdateCount = Math.Min(moveUpdateCount, _moveUpdateQueue.Count);
for (var i = 0; i < moveUpdateCount; i++)
{
HandleCollidableMove(_moveUpdateQueue.Dequeue());
}
private void HandleTileUpdate(TileUpdate tile)
{
var chunk = GetChunk(tile.Tile);
chunk.UpdateNode(tile.Tile);
DebugTools.Assert(_moveUpdateQueue.Count < 1000);
}
public PathfindingChunk GetChunk(TileRef tile)
@@ -132,7 +160,6 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
}
var newChunk = CreateChunk(tile.GridIndex, mapIndices);
return newChunk;
}
@@ -179,13 +206,13 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
public override void Initialize()
{
IoCManager.InjectDependencies(this);
SubscribeLocalEvent<CollisionChangeEvent>(QueueCollisionEnabledEvent);
SubscribeLocalEvent<MoveEvent>(QueueCollidableMove);
SubscribeLocalEvent<AccessReaderChangeMessage>(QueueAccessChangeEvent);
// Handle all the base grid changes
// Anything that affects traversal (i.e. collision layer) is handled separately.
_mapManager.OnGridRemoved += QueueGridRemoval;
_mapManager.OnGridRemoved += HandleGridRemoval;
_mapManager.GridChanged += QueueGridChange;
_mapManager.TileChanged += QueueTileChange;
}
@@ -193,32 +220,85 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
public override void Shutdown()
{
base.Shutdown();
_mapManager.OnGridRemoved -= QueueGridRemoval;
UnsubscribeLocalEvent<CollisionChangeEvent>();
UnsubscribeLocalEvent<MoveEvent>();
UnsubscribeLocalEvent<AccessReaderChangeMessage>();
_mapManager.OnGridRemoved -= HandleGridRemoval;
_mapManager.GridChanged -= QueueGridChange;
_mapManager.TileChanged -= QueueTileChange;
}
public void ResettingCleanup()
private void HandleTileUpdate(TileRef tile)
{
_queuedGraphUpdates.Clear();
var node = GetNode(tile);
node.UpdateTile(tile);
}
private void QueueGridRemoval(GridId gridId)
public void ResettingCleanup()
{
_queuedGraphUpdates.Enqueue(new GridRemoval(gridId));
_graph.Clear();
_collidableUpdateQueue.Clear();
_moveUpdateQueue.Clear();
_accessReaderUpdateQueue.Clear();
_tileUpdateQueue.Clear();
_lastKnownPositions.Clear();
}
private void HandleGridRemoval(GridId gridId)
{
if (_graph.ContainsKey(gridId))
{
_graph.Remove(gridId);
}
}
private void QueueGridChange(object sender, GridChangedEventArgs eventArgs)
{
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)
{
_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
@@ -228,25 +308,22 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
/// <param name="entity"></param>
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 ||
entity.Deleted ||
entity.HasComponent<ServerDoorComponent>() ||
entity.HasComponent<AirlockComponent>() ||
_lastKnownPositions.ContainsKey(entity))
_lastKnownPositions.ContainsKey(entity) ||
!entity.TryGetComponent(out CollidableComponent collidableComponent) ||
!collidableComponent.CanCollide ||
(TrackedCollisionLayers & collidableComponent.CollisionLayer) == 0)
{
return;
}
var grid = _mapManager.GetGrid(entity.Transform.GridID);
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
var collisionLayer = entity.GetComponent<CollidableComponent>().CollisionLayer;
var chunk = GetChunk(tileRef);
var node = chunk.GetNode(tileRef);
node.AddCollisionLayer(collisionLayer);
node.AddEntity(entity);
_lastKnownPositions.Add(entity, tileRef);
}
@@ -258,46 +335,37 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
{
if (entity.Prototype == null ||
entity.Deleted ||
entity.HasComponent<ServerDoorComponent>() ||
entity.HasComponent<AirlockComponent>() ||
!_lastKnownPositions.ContainsKey(entity))
!_lastKnownPositions.ContainsKey(entity) ||
!entity.TryGetComponent(out CollidableComponent collidableComponent) ||
!collidableComponent.CanCollide ||
(TrackedCollisionLayers & collidableComponent.CollisionLayer) == 0)
{
return;
}
_lastKnownPositions.Remove(entity);
var grid = _mapManager.GetGrid(entity.Transform.GridID);
var tileRef = grid.GetTileRef(entity.Transform.GridPosition);
if (!entity.TryGetComponent(out CollidableComponent collidableComponent))
{
return;
}
var collisionLayer = collidableComponent.CollisionLayer;
var chunk = GetChunk(tileRef);
var node = chunk.GetNode(tileRef);
node.RemoveCollisionLayer(collisionLayer);
node.RemoveEntity(entity);
_lastKnownPositions.Remove(entity);
}
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;
}
// 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.
var moveEvent = move.MoveEvent;
if (moveEvent.Sender.Deleted)
{
HandleCollidableRemove(moveEvent.Sender);
@@ -314,14 +382,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
_lastKnownPositions[moveEvent.Sender] = newTile;
if (!moveEvent.Sender.TryGetComponent(out CollidableComponent collidableComponent))
if (!moveEvent.Sender.HasComponent<CollidableComponent>())
{
HandleCollidableRemove(moveEvent.Sender);
return;
}
var collisionLayer = collidableComponent.CollisionLayer;
var gridIds = new HashSet<GridId>(2) {oldTile.GridIndex, newTile.GridIndex};
foreach (var gridId in gridIds)
@@ -330,33 +396,53 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
{
var oldChunk = GetChunk(oldTile);
var oldNode = oldChunk.GetNode(oldTile);
oldNode.RemoveCollisionLayer(collisionLayer);
oldNode.RemoveEntity(moveEvent.Sender);
}
if (newTile.GridIndex == gridId)
{
var newChunk = GetChunk(newTile);
var newNode = newChunk.GetNode(newTile);
newNode.RemoveCollisionLayer(collisionLayer);
newNode.AddEntity(moveEvent.Sender);
}
}
}
private void QueueCollisionEnabledEvent(CollisionChangeEvent collisionEvent)
{
// TODO: Handle containers
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;
}
_collidableUpdateQueue.Enqueue(collisionEvent);
}
#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 Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Timing;
@@ -955,22 +955,34 @@ namespace Content.Server.GameObjects.EntitySystems
return;
}
// Verify player has a hand, and find what object he is currently holding in his active hand
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)
if (!ActionBlockerSystem.CanAttack(player))
{
return;
}
var eventArgs = new AttackEventArgs(player, coordinates);
// 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);
}

View File

@@ -437,6 +437,8 @@ namespace Content.Server.GameTicking
lobbyCountdownMessage.Paused = Paused;
_netManager.ServerSendToAll(lobbyCountdownMessage);
_chatManager.DispatchServerAnnouncement($"Round start has been delayed for {time.TotalSeconds} seconds.");
return true;
}
@@ -463,6 +465,10 @@ namespace Content.Server.GameTicking
lobbyCountdownMessage.Paused = Paused;
_netManager.ServerSendToAll(lobbyCountdownMessage);
_chatManager.DispatchServerAnnouncement(Paused
? "Round start has been paused."
: "Round start countdown is now resumed.");
return true;
}
@@ -838,6 +844,7 @@ namespace Content.Server.GameTicking
msg.IsRoundStarted = RunLevel != GameRunLevel.PreRoundLobby;
msg.StartTime = _roundStartTimeUtc;
msg.YouAreReady = ready;
msg.Paused = Paused;
return msg;
}

View File

@@ -1,4 +1,5 @@
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.GameObjects;
using Robust.Server.Console;
@@ -62,6 +63,10 @@ namespace Content.Server.GlobalVerbs
{
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)
{
if (!serializer.Reading)
return;
Filename = serializer.ReadDataField("filename", "");
Delay = serializer.ReadDataField("delay", 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; }
// UTC.
public DateTime StartTime { get; set; }
public bool Paused { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
@@ -78,6 +79,7 @@ namespace Content.Shared
YouAreReady = buffer.ReadBoolean();
StartTime = new DateTime(buffer.ReadInt64(), DateTimeKind.Utc);
Paused = buffer.ReadBoolean();
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
@@ -91,6 +93,7 @@ namespace Content.Shared
buffer.Write(YouAreReady);
buffer.Write(StartTime.Ticks);
buffer.Write(Paused);
}
}

Binary file not shown.

View File

@@ -38,10 +38,9 @@
- RifleBlackAk
- RifleCarbine
- RifleDallas
- RifleIhHeavy
- RifleSolEot
- RifleSolPara
- RifleSts
- RifleSTS
- RifleVintorez
- RifleWintermute
chance: 0.75
gameRules:
- RuleSuspicion
@@ -62,14 +61,16 @@
- type: ConditionalSpawner
prototypes:
- PistolClarissa
- PistolDeagle
- PistolDeckard
- PistolColt
- PistolGiskard
- PistolGyro
- PistolHMPistol
- PistolLamia
- PistolMakarov
- PistolMandella
- PistolMk58
- PistolOlivawCivil
- PistolMk58Wood
- PistolMolly
- PistolOlivaw
- PistolPaco
chance: 0.75
gameRules:
- RuleSuspicion
@@ -94,6 +95,171 @@
- Spear
- ToolboxEmergency
- 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
gameRules:
- 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: Stunnable
- type: AnimationPlayer
- type: UnarmedCombat
range: 0.8
arcwidth: 30
arc: fist
- type: entity
save: false
name: Urist McHands
parent: BaseHumanMob_Content
abstract: true
id: HumanMob_Content
description: A miserable pile of secrets
drawdepth: Mobs
@@ -161,6 +164,7 @@
save: false
name: Urist McHands
id: HumanMob_Dummy
abstract: true
description: A dummy human meant to be used in character setup
components:
- type: Hands

View File

@@ -73,7 +73,7 @@
fireRate: 0.5
capacity: 1
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
- type: Appearance
visuals:

View File

@@ -18,7 +18,7 @@
hard: false
shapes:
- !type:PhysShapeAabb
bounds: "-0.2,-0.2,0.2,0.2"
bounds: "-0.1,-0.1,0.1,0.1"
layer: [Clickable]
mask:
- Impassable
@@ -130,10 +130,14 @@
- type: Projectile
deleteOnCollide: false
- type: Explosive
devastationRange: 3
heavyImpactRange: 5
lightImpactRange: 7
devastationRange: 1
heavyImpactRange: 2
lightImpactRange: 4
flashRange: 10
- type: PointLight
radius: 3.5
color: orange
energy: 0.5
- type: entity
id: BulletGrenadeBaton

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -19,6 +19,14 @@
"name": "mag-0",
"directions": 1
},
{
"name": "rocket0-inhand-left",
"directions": 4
},
{
"name": "rocket0-inhand-right",
"directions": 4
},
{
"name": "inhand-left",
"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",
"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