Merge branch 'master' into 2020-08-19-firelocks

This commit is contained in:
Víctor Aguilera Puerto
2020-08-21 16:51:50 +02:00
212 changed files with 3718 additions and 462 deletions

View File

@@ -112,19 +112,14 @@ namespace Content.Server.AI.Utility.Actions
UpdateBlackboard(context);
var considerations = GetConsiderations(context);
DebugTools.Assert(considerations.Count > 0);
// I used the IAUS video although I did have some confusion on how to structure it overall
// as some of the slides seemed contradictory
// Ideally we should early-out each action as cheaply as possible if it's not valid
// We also need some way to tell if the action isn't going to
// have a better score than the current action (if applicable) and early-out that way as well.
// 23:00 Building a better centaur
// Overall structure is based on Building a better centaur
// Ideally we should early-out each action as cheaply as possible if it's not valid, thus
// the finalScore can only go down over time.
var finalScore = 1.0f;
var minThreshold = min / Bonus;
context.GetState<ConsiderationState>().SetValue(considerations.Count);
// See 10:09 for this and the adjustments
foreach (var consideration in considerations)
{

View File

@@ -13,18 +13,27 @@ namespace Content.Server.AI.Utility.Considerations
private float GetAdjustedScore(Blackboard context)
{
var score = GetScore(context);
/*
* Now using the geometric mean
* for n scores you take the n-th root of the scores multiplied
* e.g. a, b, c scores you take Math.Pow(a * b * c, 1/3)
* To get the ACTUAL geometric mean at any one stage you'd need to divide by the running consideration count
* however, the downside to this is it will fluctuate up and down over time.
* For our purposes if we go below the minimum threshold we want to cut it off, thus we take a
* "running geometric mean" which can only ever go down (and by the final value will equal the actual geometric mean).
*/
// Previously we used a makeupvalue method although the geometric mean is less punishing for more considerations
var considerationsCount = context.GetState<ConsiderationState>().GetValue();
var modificationFactor = 1.0f - 1.0f / considerationsCount;
var makeUpValue = (1.0f - score) * modificationFactor;
var adjustedScore = score + makeUpValue * score;
return FloatMath.Clamp(adjustedScore, 0.0f, 1.0f);
var adjustedScore = MathF.Pow(score, 1 / (float) considerationsCount);
return MathHelper.Clamp(adjustedScore, 0.0f, 1.0f);
}
[Pure]
private static float BoolCurve(float x)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
return x == 1.0f ? 1.0f : 0.0f;
return x > 0.0f ? 1.0f : 0.0f;
}
public Func<float> BoolCurve(Blackboard context)
@@ -42,7 +51,7 @@ namespace Content.Server.AI.Utility.Considerations
private static float InverseBoolCurve(float x)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
return x == 1.0f ? 0.0f : 1.0f;
return x == 0.0f ? 1.0f : 0.0f;
}
public Func<float> InverseBoolCurve(Blackboard context)
@@ -59,7 +68,7 @@ namespace Content.Server.AI.Utility.Considerations
[Pure]
private static float LogisticCurve(float x, float slope, float exponent, float yOffset, float xOffset)
{
return FloatMath.Clamp(
return MathHelper.Clamp(
exponent * (1 / (1 + (float) Math.Pow(Math.Log(1000) * slope, -1 * x + xOffset))) + yOffset, 0.0f, 1.0f);
}
@@ -77,7 +86,7 @@ namespace Content.Server.AI.Utility.Considerations
[Pure]
private static float QuadraticCurve(float x, float slope, float exponent, float yOffset, float xOffset)
{
return FloatMath.Clamp(slope * (float) Math.Pow(x - xOffset, exponent) + yOffset, 0.0f, 1.0f);
return MathHelper.Clamp(slope * (float) Math.Pow(x - xOffset, exponent) + yOffset, 0.0f, 1.0f);
}
public Func<float> QuadraticCurve(Blackboard context, float slope, float exponent, float yOffset, float xOffset)

View File

@@ -16,6 +16,9 @@ namespace Content.Server.AI.WorldState.States.Inventory
{
foreach (var item in handsComponent.GetAllHeldItems())
{
if (item.Owner.Deleted)
continue;
yield return item.Owner;
}
}

View File

@@ -0,0 +1,40 @@
#nullable enable
using Content.Server.GameTicking;
using Content.Server.Interfaces.GameTicking;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.IoC;
namespace Content.Server.Administration
{
public class ReadyAll : IClientCommand
{
public string Command => "readyall";
public string Description => "Readies up all players in the lobby.";
public string Help => $"{Command} | ̣{Command} <ready>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
var ready = true;
if (args.Length > 0)
{
ready = bool.Parse(args[0]);
}
var gameTicker = IoCManager.Resolve<IGameTicker>();
var playerManager = IoCManager.Resolve<IPlayerManager>();
if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
{
shell.SendText(player, "This command can only be ran while in the lobby!");
return;
}
foreach (var p in playerManager.GetAllPlayers())
{
gameTicker.ToggleReady(p, ready);
}
}
}
}

View File

@@ -53,14 +53,14 @@ namespace Content.Server.Atmos
{
if (throwTarget != GridCoordinates.InvalidGrid)
{
var moveForce = maxForce * FloatMath.Clamp(moveProb, 0, 100) / 150f;
var moveForce = maxForce * MathHelper.Clamp(moveProb, 0, 100) / 150f;
var pos = ((throwTarget.Position - transform.GridPosition.Position).Normalized + direction.ToVec()).Normalized;
LinearVelocity = pos * moveForce;
}
else
{
var moveForce = MathF.Min(maxForce * FloatMath.Clamp(moveProb, 0, 100) / 2500f, 20f);
var moveForce = MathF.Min(maxForce * MathHelper.Clamp(moveProb, 0, 100) / 2500f, 20f);
LinearVelocity = direction.ToVec() * moveForce;
}

View File

@@ -172,7 +172,7 @@ namespace Content.Server.Atmos
{
if(_soundCooldown == 0)
EntitySystem.Get<AudioSystem>().PlayAtCoords("/Audio/Effects/space_wind.ogg",
GridIndices.ToGridCoordinates(_mapManager, GridIndex), AudioHelpers.WithVariation(0.125f).WithVolume(FloatMath.Clamp(PressureDifference / 10, 10, 100)));
GridIndices.ToGridCoordinates(_mapManager, GridIndex), AudioHelpers.WithVariation(0.125f).WithVolume(MathHelper.Clamp(PressureDifference / 10, 10, 100)));
}
@@ -1043,7 +1043,7 @@ namespace Content.Server.Atmos
private void HandleDecompressionFloorRip(float sum)
{
var chance = FloatMath.Clamp(sum / 500, 0.005f, 0.5f);
var chance = MathHelper.Clamp(sum / 500, 0.005f, 0.5f);
if (sum > 20 && _robustRandom.Prob(chance))
_gridAtmosphereComponent.PryTile(GridIndices);
}

View File

@@ -32,7 +32,7 @@ namespace Content.Server.Body
return;
}
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent body))
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
{
var random = IoCManager.Resolve<IRobustRandom>();
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
@@ -72,7 +72,7 @@ namespace Content.Server.Body
return;
}
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent body))
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
{
var random = IoCManager.Resolve<IRobustRandom>();
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
@@ -119,7 +119,7 @@ namespace Content.Server.Body
return;
}
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent body))
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
{
var random = IoCManager.Resolve<IRobustRandom>();
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";

View File

@@ -19,7 +19,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
base.PreMetabolism(frameTime);
if (Mechanism.Body == null ||
!Mechanism.Body.Owner.TryGetComponent(out BloodstreamComponent bloodstream))
!Mechanism.Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{
return;
}

View File

@@ -16,7 +16,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
base.PreMetabolism(frameTime);
if (Mechanism.Body == null ||
!Mechanism.Body.Owner.TryGetComponent(out LungComponent lung))
!Mechanism.Body.Owner.TryGetComponent(out LungComponent? lung))
{
return;
}

View File

@@ -18,7 +18,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
base.PreMetabolism(frameTime);
if (Mechanism.Body == null ||
!Mechanism.Body.Owner.TryGetComponent(out StomachComponent stomach))
!Mechanism.Body.Owner.TryGetComponent(out StomachComponent? stomach))
{
return;
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
@@ -32,9 +32,11 @@ namespace Content.Server.Chat
if (args.Length < 1)
return;
var chat = IoCManager.Resolve<IChatManager>();
var message = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(message))
return;
var message = string.Join(" ", args);
var chat = IoCManager.Resolve<IChatManager>();
if (player.AttachedEntity.HasComponent<GhostComponent>())
chat.SendDeadChat(player, message);
@@ -61,9 +63,11 @@ namespace Content.Server.Chat
if (args.Length < 1)
return;
var chat = IoCManager.Resolve<IChatManager>();
var action = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(action))
return;
var action = string.Join(" ", args);
var chat = IoCManager.Resolve<IChatManager>();
var mindComponent = player.ContentData().Mind;
chat.EntityMe(mindComponent.OwnedEntity, action);
@@ -78,8 +82,15 @@ namespace Content.Server.Chat
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
if (args.Length < 1)
return;
var message = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(message))
return;
var chat = IoCManager.Resolve<IChatManager>();
chat.SendOOC(player, string.Join(" ", args));
chat.SendOOC(player, message);
}
}
@@ -91,8 +102,15 @@ namespace Content.Server.Chat
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
if (args.Length < 1)
return;
var message = string.Join(" ", args).Trim();
if (string.IsNullOrEmpty(message))
return;
var chat = IoCManager.Resolve<IChatManager>();
chat.SendAdminChat(player, string.Join(" ", args));
chat.SendAdminChat(player, message);
}
}

View File

@@ -71,16 +71,16 @@ namespace Content.Server.GameObjects.Components.Access
public static ICollection<string> FindAccessTags(IEntity entity)
{
if (entity.TryGetComponent(out IAccess accessComponent))
if (entity.TryGetComponent(out IAccess? accessComponent))
{
return accessComponent.Tags;
}
if (entity.TryGetComponent(out IHandsComponent handsComponent))
if (entity.TryGetComponent(out IHandsComponent? handsComponent))
{
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
if (activeHandEntity != null &&
activeHandEntity.TryGetComponent(out IAccess handAccessComponent))
activeHandEntity.TryGetComponent(out IAccess? handAccessComponent))
{
return handAccessComponent.Tags;
}
@@ -90,11 +90,11 @@ namespace Content.Server.GameObjects.Components.Access
return Array.Empty<string>();
}
if (entity.TryGetComponent(out InventoryComponent inventoryComponent))
if (entity.TryGetComponent(out InventoryComponent? inventoryComponent))
{
if (inventoryComponent.HasSlot(EquipmentSlotDefines.Slots.IDCARD) &&
inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent item) &&
item.Owner.TryGetComponent(out IAccess idAccessComponent)
item.Owner.TryGetComponent(out IAccess? idAccessComponent)
)
{
return idAccessComponent.Tags;

View File

@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.Components
if (!force)
{
if (utilizing == null ||
!utilizing.TryGetComponent(out ToolComponent tool) ||
!utilizing.TryGetComponent(out ToolComponent? tool) ||
!(await tool.UseTool(user, Owner, 0.5f, ToolQuality.Anchoring)))
{
return false;
@@ -93,7 +93,7 @@ namespace Content.Server.GameObjects.Components
/// <returns>true if toggled, false otherwise</returns>
private async Task<bool> TryToggleAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
{
if (!Owner.TryGetComponent(out ICollidableComponent collidable))
if (!Owner.TryGetComponent(out ICollidableComponent? collidable))
{
return false;
}

View File

@@ -116,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{
_pressureDanger = GasAnalyzerDanger.Nominal;
}
Dirty();
_timeSinceSync = 0f;
}
@@ -131,11 +131,11 @@ namespace Content.Server.GameObjects.Components.Atmos
if (session.AttachedEntity == null)
return;
if (!session.AttachedEntity.TryGetComponent(out IHandsComponent handsComponent))
if (!session.AttachedEntity.TryGetComponent(out IHandsComponent? handsComponent))
return;
var activeHandEntity = handsComponent?.GetActiveHand?.Owner;
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent gasAnalyzer))
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
{
return;
}
@@ -147,7 +147,7 @@ namespace Content.Server.GameObjects.Components.Atmos
// Check if position is out of range => don't update
if (!_position.Value.InRange(_mapManager, pos, SharedInteractionSystem.InteractionRange))
return;
pos = _position.Value;
}
@@ -195,7 +195,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return;
}
if (!player.TryGetComponent(out IHandsComponent handsComponent))
if (!player.TryGetComponent(out IHandsComponent? handsComponent))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player,
Loc.GetString("You have no hands."));
@@ -203,7 +203,7 @@ namespace Content.Server.GameObjects.Components.Atmos
}
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent gasAnalyzer))
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
{
_notifyManager.PopupMessage(serverMsg.Session.AttachedEntity,
serverMsg.Session.AttachedEntity,
@@ -225,7 +225,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return;
}
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
OpenInterface(actor.playerSession, eventArgs.ClickLocation);
//TODO: show other sprite when ui open?
@@ -236,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Atmos
void IDropped.Dropped(DroppedEventArgs eventArgs)
{
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
CloseInterface(actor.playerSession);
//TODO: if other sprite is shown, change again
@@ -245,7 +245,7 @@ namespace Content.Server.GameObjects.Components.Atmos
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
{
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
OpenInterface(actor.playerSession);
//TODO: show other sprite when ui open?

View File

@@ -272,7 +272,7 @@ namespace Content.Server.GameObjects.Components.Body
private void CalculateSpeed()
{
if (!Owner.TryGetComponent(out MovementSpeedModifierComponent playerMover))
if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover))
{
return;
}

View File

@@ -112,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Buckle
/// </summary>
private void BuckleStatus()
{
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
{
status.ChangeStatusEffectIcon(StatusEffect.Buckled,
Buckled
@@ -291,7 +291,7 @@ namespace Content.Server.GameObjects.Components.Buckle
return false;
}
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(BuckleVisuals.Buckled, true);
}
@@ -359,12 +359,12 @@ namespace Content.Server.GameObjects.Components.Buckle
Owner.Transform.WorldRotation = oldBuckledTo.Owner.Transform.WorldRotation;
}
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(BuckleVisuals.Buckled, false);
}
if (Owner.TryGetComponent(out StunnableComponent stunnable) && stunnable.KnockedDown)
if (Owner.TryGetComponent(out StunnableComponent? stunnable) && stunnable.KnockedDown)
{
StandingStateHelper.Down(Owner);
}
@@ -373,14 +373,14 @@ namespace Content.Server.GameObjects.Components.Buckle
StandingStateHelper.Standing(Owner);
}
if (Owner.TryGetComponent(out MobStateManagerComponent stateManager))
if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager))
{
stateManager.CurrentMobState.EnterState(Owner);
}
BuckleStatus();
if (oldBuckledTo.Owner.TryGetComponent(out StrapComponent strap))
if (oldBuckledTo.Owner.TryGetComponent(out StrapComponent? strap))
{
strap.Remove(this);
_entitySystem.GetEntitySystem<AudioSystem>()
@@ -535,7 +535,7 @@ namespace Content.Server.GameObjects.Components.Buckle
_entityManager.EventBus.UnsubscribeEvents(this);
if (BuckledTo != null &&
BuckledTo.Owner.TryGetComponent(out StrapComponent strap))
BuckledTo.Owner.TryGetComponent(out StrapComponent? strap))
{
strap.Remove(this);
}
@@ -552,7 +552,7 @@ namespace Content.Server.GameObjects.Components.Buckle
if (BuckledTo != null &&
Owner.Transform.WorldRotation.GetCardinalDir() == Direction.North &&
BuckledTo.Owner.TryGetComponent(out SpriteComponent strapSprite))
BuckledTo.Owner.TryGetComponent(out SpriteComponent? strapSprite))
{
drawDepth = strapSprite.DrawDepth - 1;
}

View File

@@ -159,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Cargo
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}

View File

@@ -60,7 +60,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
{
_state = value;
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -93,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
return false;
}
if (Owner.TryGetComponent(out PowerReceiverComponent receiver) &&
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) &&
!receiver.Powered)
{
return false;
@@ -114,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
return false;
}
if (!entity.TryGetComponent(out ICollidableComponent collidable) ||
if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
collidable.Anchored)
{
return false;
@@ -155,7 +155,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
if (entity.TryGetComponent(out ICollidableComponent collidable))
if (entity.TryGetComponent(out ICollidableComponent? collidable))
{
var controller = collidable.EnsureController<ConveyedController>();
controller.Move(direction, _speed * frameTime);
@@ -225,7 +225,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component))
{
continue;
}
@@ -247,13 +247,13 @@ namespace Content.Server.GameObjects.Components.Conveyor
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent conveyorSwitch))
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent? conveyorSwitch))
{
conveyorSwitch.Connect(this, eventArgs.User);
return true;
}
if (eventArgs.Using.TryGetComponent(out ToolComponent tool))
if (eventArgs.Using.TryGetComponent(out ToolComponent? tool))
{
return await ToolUsed(eventArgs.User, tool);
}

View File

@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
{
_state = value;
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(ConveyorVisuals.State, value);
}
@@ -145,7 +145,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
if (!conveyor.TryGetComponent(out ConveyorComponent component))
if (!conveyor.TryGetComponent(out ConveyorComponent? component))
{
continue;
}
@@ -172,7 +172,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component))
{
continue;
}
@@ -196,13 +196,13 @@ namespace Content.Server.GameObjects.Components.Conveyor
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (eventArgs.Using.TryGetComponent(out ConveyorComponent conveyor))
if (eventArgs.Using.TryGetComponent(out ConveyorComponent? conveyor))
{
Connect(conveyor, eventArgs.User);
return true;
}
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent otherSwitch))
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent? otherSwitch))
{
SyncWith(otherSwitch, eventArgs.User);
return true;

View File

@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.Components.Disposal
return;
}
if (!entity.TryGetComponent(out IDisposalTubeComponent tube))
if (!entity.TryGetComponent(out IDisposalTubeComponent? tube))
{
shell.SendText(player, Loc.GetString("Entity with uid {0} doesn't have a {1} component", id, nameof(IDisposalTubeComponent)));
return;

View File

@@ -1,10 +1,12 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Shared.GameObjects.Components.Body;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
@@ -41,6 +43,12 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
public IDisposalTubeComponent? NextTube { get; set; }
/// <summary>
/// A list of tags attached to the content, used for sorting
/// </summary>
[ViewVariables]
public HashSet<string> Tags { get; set; } = new HashSet<string>();
private bool CanInsert(IEntity entity)
{
if (!_contents.CanInsert(entity))
@@ -48,6 +56,12 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
!collidable.CanCollide)
{
return false;
}
return entity.HasComponent<ItemComponent>() ||
entity.HasComponent<IBodyManagerComponent>();
}
@@ -59,6 +73,11 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
if (entity.TryGetComponent(out ICollidableComponent? collidable))
{
collidable.CanCollide = false;
}
return true;
}
@@ -86,6 +105,11 @@ namespace Content.Server.GameObjects.Components.Disposal
foreach (var entity in _contents.ContainedEntities.ToArray())
{
if (entity.TryGetComponent(out ICollidableComponent? collidable))
{
collidable.CanCollide = true;
}
_contents.ForceRemove(entity);
if (entity.Transform.Parent == Owner.Transform)

View File

@@ -0,0 +1,179 @@
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalRouterComponent : DisposalJunctionComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
public override string Name => "DisposalRouter";
[ViewVariables]
private BoundUserInterface _userInterface;
[ViewVariables]
private HashSet<string> _tags;
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
collidable.Anchored;
public override Direction NextDirection(DisposalHolderComponent holder)
{
var directions = ConnectableDirections();
if (holder.Tags.Overlaps(_tags))
{
return directions[1];
}
return Owner.Transform.LocalRotation.GetDir();
}
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(DisposalRouterUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
_tags = new HashSet<string>();
UpdateUserInterface();
}
/// <summary>
/// Handles ui messages from the client. For things such as button presses
/// which interact with the world and require server action.
/// </summary>
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
var msg = (UiActionMessage) obj.Message;
if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
return;
//Check for correct message and ignore maleformed strings
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tags))
{
_tags.Clear();
foreach (var tag in msg.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries))
{
_tags.Add(tag.Trim());
ClickSound();
}
}
}
/// <summary>
/// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the configuration interface, and false if it cannot.</returns>
private bool PlayerCanUseDisposalTagger(IEntity playerEntity)
{
//Need player entity to check if they are still able to use the configuration interface
if (playerEntity == null)
return false;
if (!Anchored)
return false;
//Check if player can interact in their current state
if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
return false;
return true;
}
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>
/// <returns>Returns a <see cref="SharedDisposalRouterComponent.DisposalRouterBoundUserInterfaceState"/></returns>
private DisposalRouterUserInterfaceState GetUserInterfaceState()
{
if(_tags == null || _tags.Count <= 0)
{
return new DisposalRouterUserInterfaceState("");
}
var taglist = new System.Text.StringBuilder();
foreach (var tag in _tags)
{
taglist.Append(tag);
taglist.Append(", ");
}
taglist.Remove(taglist.Length - 2, 2);
return new DisposalRouterUserInterfaceState(taglist.ToString());
}
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
}
private void ClickSound()
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
}
/// <summary>
/// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible.
/// </summary>
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
void IActivate.Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
return;
}
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
UpdateUserInterface();
_userInterface.Open(actor.playerSession);
}
}
public override void OnRemove()
{
_userInterface.CloseAll();
base.OnRemove();
}
}
}

View File

@@ -0,0 +1,150 @@
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalTaggerComponent : DisposalTransitComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
public override string Name => "DisposalTagger";
[ViewVariables]
private BoundUserInterface _userInterface;
[ViewVariables(VVAccess.ReadWrite)]
private string _tag = "";
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
collidable.Anchored;
public override Direction NextDirection(DisposalHolderComponent holder)
{
holder.Tags.Add(_tag);
return base.NextDirection(holder);
}
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(DisposalTaggerUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
UpdateUserInterface();
}
/// <summary>
/// Handles ui messages from the client. For things such as button presses
/// which interact with the world and require server action.
/// </summary>
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
var msg = (UiActionMessage) obj.Message;
if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
return;
//Check for correct message and ignore maleformed strings
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tag))
{
_tag = msg.Tag;
ClickSound();
}
}
/// <summary>
/// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the configuration interface, and false if it cannot.</returns>
private bool PlayerCanUseDisposalTagger(IEntity playerEntity)
{
//Need player entity to check if they are still able to use the configuration interface
if (playerEntity == null)
return false;
if (!Anchored)
return false;
//Check if player can interact in their current state
if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
return false;
return true;
}
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>
/// <returns>Returns a <see cref="SharedDisposalTaggerComponent.DisposalTaggerBoundUserInterfaceState"/></returns>
private DisposalTaggerUserInterfaceState GetUserInterfaceState()
{
return new DisposalTaggerUserInterfaceState(_tag);
}
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
}
private void ClickSound()
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
}
/// <summary>
/// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible.
/// </summary>
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
void IActivate.Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
return;
}
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
UpdateUserInterface();
_userInterface.Open(actor.playerSession);
}
}
public override void OnRemove()
{
base.OnRemove();
_userInterface.CloseAll();
}
}
}

View File

@@ -44,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
private bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
!Owner.TryGetComponent(out CollidableComponent? collidable) ||
collidable.Anchored;
/// <summary>
@@ -71,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Disposal
var snapGrid = Owner.GetComponent<SnapGridComponent>();
var tube = snapGrid
.GetInDir(nextDirection)
.Select(x => x.TryGetComponent(out IDisposalTubeComponent c) ? c : null)
.Select(x => x.TryGetComponent(out IDisposalTubeComponent? c) ? c : null)
.FirstOrDefault(x => x != null && x != this);
if (tube == null)
@@ -153,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Disposal
foreach (var entity in Contents.ContainedEntities.ToArray())
{
if (!entity.TryGetComponent(out DisposalHolderComponent holder))
if (!entity.TryGetComponent(out DisposalHolderComponent? holder))
{
continue;
}
@@ -171,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateVisualState()
{
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -187,7 +187,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void AnchoredChanged()
{
if (!Owner.TryGetComponent(out CollidableComponent collidable))
if (!Owner.TryGetComponent(out CollidableComponent? collidable))
{
return;
}

View File

@@ -86,12 +86,12 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
public bool Powered =>
!Owner.TryGetComponent(out PowerReceiverComponent receiver) ||
!Owner.TryGetComponent(out PowerReceiverComponent? receiver) ||
receiver.Powered;
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
!Owner.TryGetComponent(out CollidableComponent? collidable) ||
collidable.Anchored;
[ViewVariables]
@@ -122,6 +122,12 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
!collidable.CanCollide)
{
return false;
}
if (!entity.HasComponent<ItemComponent>() &&
!entity.HasComponent<IBodyManagerComponent>())
{
@@ -153,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Disposal
{
TryQueueEngage();
if (entity.TryGetComponent(out IActorComponent actor))
if (entity.TryGetComponent(out IActorComponent? actor))
{
_userInterface.Close(actor.playerSession);
}
@@ -175,7 +181,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private bool TryDrop(IEntity user, IEntity entity)
{
if (!user.TryGetComponent(out HandsComponent hands))
if (!user.TryGetComponent(out HandsComponent? hands))
{
return false;
}
@@ -267,7 +273,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void TogglePower()
{
if (!Owner.TryGetComponent(out PowerReceiverComponent receiver))
if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
return;
}
@@ -346,7 +352,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateVisualState(bool flush)
{
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -482,7 +488,7 @@ namespace Content.Server.GameObjects.Components.Disposal
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged += UpdateVisualState;
if (Owner.TryGetComponent(out PowerReceiverComponent receiver))
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += PowerStateChanged;
}
@@ -492,12 +498,12 @@ namespace Content.Server.GameObjects.Components.Disposal
public override void OnRemove()
{
if (Owner.TryGetComponent(out ICollidableComponent collidable))
if (Owner.TryGetComponent(out ICollidableComponent? collidable))
{
collidable.AnchoredChanged -= UpdateVisualState;
}
if (Owner.TryGetComponent(out PowerReceiverComponent receiver))
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged -= PowerStateChanged;
}
@@ -524,7 +530,7 @@ namespace Content.Server.GameObjects.Components.Disposal
switch (message)
{
case RelayMovementEntityMessage msg:
if (!msg.Entity.TryGetComponent(out HandsComponent hands) ||
if (!msg.Entity.TryGetComponent(out HandsComponent? hands) ||
hands.Count == 0 ||
_gameTiming.CurTime < _lastExitAttempt + ExitAttemptDelay)
{
@@ -553,7 +559,7 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return false;
}

View File

@@ -60,7 +60,7 @@ namespace Content.Server.GameObjects.Components
{
connectedClient = null;
if (!Owner.TryGetComponent(out IActorComponent actorComponent))
if (!Owner.TryGetComponent(out IActorComponent? actorComponent))
{
return false;
}

View File

@@ -418,7 +418,7 @@ namespace Content.Server.GameObjects.Components.Doors
return true;
}
if (!await tool.UseTool(eventArgs.User, Owner, 3f, ToolQuality.Prying, AirlockCheck)) return false;
if (!await tool.UseTool(eventArgs.User, Owner, 0.2f, ToolQuality.Prying, AirlockCheck)) return false;
if (State == DoorState.Closed)
Open();

View File

@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Fluids
foreach (var spillEntity in entityManager.GetEntitiesAt(spillTileMapGrid.ParentMapId, spillGridCoords.Position))
{
if (!spillEntity.TryGetComponent(out PuddleComponent puddleComponent))
if (!spillEntity.TryGetComponent(out PuddleComponent? puddleComponent))
{
continue;
}

View File

@@ -711,7 +711,7 @@ namespace Content.Server.GameObjects.Components.GUI
Dirty();
if (!message.Entity.TryGetComponent(out ICollidableComponent collidable))
if (!message.Entity.TryGetComponent(out ICollidableComponent? collidable))
{
return;
}
@@ -724,13 +724,13 @@ namespace Content.Server.GameObjects.Components.GUI
private void AddPullingStatuses(IEntity pulled)
{
if (pulled.TryGetComponent(out ServerStatusEffectsComponent pulledStatus))
if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
{
pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled,
"/Textures/Interface/StatusEffects/Pull/pulled.png");
}
if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus))
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
{
ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling,
"/Textures/Interface/StatusEffects/Pull/pulling.png");
@@ -739,12 +739,12 @@ namespace Content.Server.GameObjects.Components.GUI
private void RemovePullingStatuses(IEntity pulled)
{
if (pulled.TryGetComponent(out ServerStatusEffectsComponent pulledStatus))
if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
{
pulledStatus.RemoveStatusEffect(StatusEffect.Pulled);
}
if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus))
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
{
ownerStatus.RemoveStatusEffect(StatusEffect.Pulling);
}

View File

@@ -10,6 +10,7 @@ using Content.Server.Interfaces.GameObjects;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
@@ -115,8 +116,14 @@ namespace Content.Server.GameObjects.Components.GUI
public override void OnRemove()
{
var slots = _slotContainers.Keys.ToList();
foreach (var slot in slots)
{
if (TryGetSlotItem(slot, out ItemComponent item))
{
item.Owner.Delete();
}
RemoveSlot(slot);
}
@@ -267,17 +274,17 @@ namespace Content.Server.GameObjects.Components.GUI
}
var inventorySlot = _slotContainers[slot];
var item = inventorySlot.ContainedEntity.GetComponent<ItemComponent>();
if (!inventorySlot.Remove(inventorySlot.ContainedEntity))
var entity = inventorySlot.ContainedEntity;
var item = entity.GetComponent<ItemComponent>();
if (!inventorySlot.Remove(entity))
{
return false;
}
// TODO: The item should be dropped to the container our owner is in, if any.
var itemTransform = item.Owner.GetComponent<ITransformComponent>();
itemTransform.GridPosition = Owner.GetComponent<ITransformComponent>().GridPosition;
ContainerHelpers.AttachParentToContainerOrGrid(entity.Transform);
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedInteraction(Owner, item.Owner, slot);
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedInteraction(Owner, entity, slot);
OnItemChanged?.Invoke();
@@ -286,6 +293,29 @@ namespace Content.Server.GameObjects.Components.GUI
return true;
}
public void ForceUnequip(Slots slot)
{
var inventorySlot = _slotContainers[slot];
var entity = inventorySlot.ContainedEntity;
if (entity == null)
{
return;
}
var item = entity.GetComponent<ItemComponent>();
inventorySlot.ForceRemove(entity);
var itemTransform = entity.Transform;
ContainerHelpers.AttachParentToContainerOrGrid(itemTransform);
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedInteraction(Owner, item.Owner, slot);
OnItemChanged?.Invoke();
Dirty();
}
/// <summary>
/// Checks whether an item can be dropped from the specified slot.
/// </summary>
@@ -340,13 +370,11 @@ namespace Content.Server.GameObjects.Components.GUI
throw new InvalidOperationException($"Slow '{slot}' does not exist.");
}
if (GetSlotItem(slot) != null && !Unequip(slot))
{
// TODO: Handle this potential failiure better.
throw new InvalidOperationException(
"Unable to remove slot as the contained clothing could not be dropped");
}
ForceUnequip(slot);
var container = _slotContainers[slot];
container.Shutdown();
_slotContainers.Remove(slot);
OnItemChanged?.Invoke();

View File

@@ -106,7 +106,7 @@ namespace Content.Server.GameObjects.Components.Interactable
foreach (var entity in entities)
{
if (entity.TryGetComponent(out AnchorableComponent anchorable))
if (entity.TryGetComponent(out AnchorableComponent? anchorable))
{
anchorable.TryAnchor(player.AttachedEntity, force: true);
}
@@ -151,7 +151,7 @@ namespace Content.Server.GameObjects.Components.Interactable
foreach (var entity in entities)
{
if (entity.TryGetComponent(out AnchorableComponent anchorable))
if (entity.TryGetComponent(out AnchorableComponent? anchorable))
{
anchorable.TryUnAnchor(player.AttachedEntity, force: true);
}

View File

@@ -101,13 +101,13 @@ namespace Content.Server.GameObjects.Components.Items.Storage
{
EnsureInitialCalculated();
if (entity.TryGetComponent(out ServerStorageComponent storage) &&
if (entity.TryGetComponent(out ServerStorageComponent? storage) &&
storage._storageCapacityMax >= _storageCapacityMax)
{
return false;
}
if (entity.TryGetComponent(out StorableComponent store) &&
if (entity.TryGetComponent(out StorableComponent? store) &&
store.ObjectSize > _storageCapacityMax - _storageUsed)
{
return false;
@@ -164,7 +164,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) had entity (UID {message.Entity.Uid}) removed from it.");
if (!message.Entity.TryGetComponent(out StorableComponent storable))
if (!message.Entity.TryGetComponent(out StorableComponent? storable))
{
Logger.WarningS(LoggerName, $"Removed entity {message.Entity.Uid} without a StorableComponent from storage {Owner.Uid} at {Owner.Transform.MapPosition}");
@@ -186,7 +186,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
{
EnsureInitialCalculated();
if (!player.TryGetComponent(out IHandsComponent hands) ||
if (!player.TryGetComponent(out IHandsComponent? hands) ||
hands.GetActiveHand == null)
{
return false;
@@ -317,7 +317,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
private void UpdateDoorState()
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(StorageVisuals.Open, SubscribedSessions.Count != 0);
}
@@ -382,7 +382,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
var item = entity.GetComponent<ItemComponent>();
if (item == null ||
!player.TryGetComponent(out HandsComponent hands))
!player.TryGetComponent(out HandsComponent? hands))
{
break;
}
@@ -496,7 +496,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
foreach (var entity in storedEntities)
{
var exActs = entity.GetAllComponents<IExAct>();
var exActs = entity.GetAllComponents<IExAct>().ToArray();
foreach (var exAct in exActs)
{
exAct.OnExplosion(eventArgs);
@@ -506,7 +506,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent placeable) &&
return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent? placeable) &&
placeable.IsPlaceable;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Medical;
using Content.Shared.GameObjects.EntitySystems;
@@ -38,9 +39,14 @@ namespace Content.Server.GameObjects.Components.Medical
_appearance = Owner.GetComponent<AppearanceComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(MedicalScannerUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
//TODO: write this so that it checks for a change in power events and acts accordingly.
var newState = GetUserInterfaceState();
_userInterface.SetState(newState);
UpdateUserInterface();
}
@@ -48,7 +54,8 @@ namespace Content.Server.GameObjects.Components.Medical
new MedicalScannerBoundUserInterfaceState(
null,
new Dictionary<DamageClass, int>(),
new Dictionary<DamageType, int>());
new Dictionary<DamageType, int>(),
false);
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
{
@@ -68,7 +75,7 @@ namespace Content.Server.GameObjects.Components.Medical
var classes = new Dictionary<DamageClass, int>(damageable.DamageClasses);
var types = new Dictionary<DamageType, int>(damageable.DamageTypes);
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types);
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types, CloningSystem.HasUid(body.Uid));
}
private void UpdateUserInterface()
@@ -92,12 +99,18 @@ namespace Content.Server.GameObjects.Components.Medical
default: throw new ArgumentException(nameof(damageState));
}
}
private MedicalScannerStatus GetStatus()
{
var body = _bodyContainer.ContainedEntity;
return body == null
? MedicalScannerStatus.Open
: GetStatusFromDamageState(body.GetComponent<IDamageableComponent>().CurrentDamageState);
if (Powered)
{
var body = _bodyContainer.ContainedEntity;
return body == null
? MedicalScannerStatus.Open
: GetStatusFromDamageState(body.GetComponent<IDamageableComponent>().CurrentDamageState);
}
return MedicalScannerStatus.Off;
}
private void UpdateAppearance()
@@ -178,14 +191,28 @@ namespace Content.Server.GameObjects.Components.Medical
public void Update(float frameTime)
{
if (_bodyContainer.ContainedEntity == null)
{
// There's no need to update if there's no one inside
return;
}
UpdateUserInterface();
UpdateAppearance();
}
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (!(obj.Message is UiButtonPressedMessage message))
{
return;
}
switch (message.Button)
{
case UiButton.ScanDNA:
if (_bodyContainer.ContainedEntity != null)
{
CloningSystem.AddToScannedUids(_bodyContainer.ContainedEntity.Uid);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@@ -213,7 +213,7 @@ namespace Content.Server.GameObjects.Components.Metabolism
if (Suffocating &&
Owner.TryGetComponent(out IDamageableComponent damageable))
{
damageable.ChangeDamage(DamageClass.Airloss, _suffocationDamage, false);
// damageable.ChangeDamage(DamageClass.Airloss, _suffocationDamage, false);
}
}

View File

@@ -79,7 +79,7 @@ namespace Content.Server.GameObjects.Components.Mobs
var visiting = Mind?.VisitingEntity;
if (visiting != null)
{
if (visiting.TryGetComponent(out GhostComponent ghost))
if (visiting.TryGetComponent(out GhostComponent? ghost))
{
ghost.CanReturnToBody = false;
}

View File

@@ -0,0 +1,235 @@

using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Robust.Server.Interfaces.Player;
using Content.Server.Interfaces;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Interfaces;
using Content.Server.GameObjects.Components.Body;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Robust.Shared.Maths;
using System;
namespace Content.Server.GameObjects.Components.Movement
{
[RegisterComponent]
[ComponentReference(typeof(IClimbable))]
public class ClimbableComponent : SharedClimbableComponent, IDragDropOn
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
#pragma warning restore 649
/// <summary>
/// The range from which this entity can be climbed.
/// </summary>
[ViewVariables]
private float _range;
/// <summary>
/// The time it takes to climb onto the entity.
/// </summary>
[ViewVariables]
private float _climbDelay;
private ICollidableComponent _collidableComponent;
private DoAfterSystem _doAfterSystem;
public override void Initialize()
{
base.Initialize();
_collidableComponent = Owner.GetComponent<ICollidableComponent>();
_doAfterSystem = EntitySystem.Get<DoAfterSystem>();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _range, "range", SharedInteractionSystem.InteractionRange / 1.4f);
serializer.DataField(ref _climbDelay, "delay", 0.8f);
}
bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
{
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
return false;
}
if (eventArgs.User == eventArgs.Dropped) // user is dragging themselves onto a climbable
{
if (!eventArgs.User.HasComponent<ClimbingComponent>())
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are incapable of climbing!"));
return false;
}
var bodyManager = eventArgs.User.GetComponent<BodyManagerComponent>();
if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are unable to climb!"));
return false;
}
var userPosition = eventArgs.User.Transform.MapPosition;
var climbablePosition = eventArgs.Target.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User);
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
return false;
}
}
else // user is dragging some other entity onto a climbable
{
if (eventArgs.Target == null || !eventArgs.Dropped.HasComponent<ClimbingComponent>())
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
return false;
}
var userPosition = eventArgs.User.Transform.MapPosition;
var otherUserPosition = eventArgs.Dropped.Transform.MapPosition;
var climbablePosition = eventArgs.Target.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User || entity == eventArgs.Dropped);
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) ||
!interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
return false;
}
}
return true;
}
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
{
if (eventArgs.User == eventArgs.Dropped)
{
TryClimb(eventArgs.User);
}
else
{
TryMoveEntity(eventArgs.User, eventArgs.Dropped);
}
return true;
}
private async void TryMoveEntity(IEntity user, IEntity entityToMove)
{
var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, entityToMove)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true
};
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
if (result != DoAfterStatus.Cancelled && entityToMove.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
{
var direction = (Owner.Transform.WorldPosition - entityToMove.Transform.WorldPosition).Normalized;
var endPoint = Owner.Transform.WorldPosition;
var climbMode = entityToMove.GetComponent<ClimbingComponent>();
climbMode.IsClimbing = true;
if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
{
endPoint = new Vector2(entityToMove.Transform.WorldPosition.X, endPoint.Y);
}
else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
{
endPoint = new Vector2(endPoint.X, entityToMove.Transform.WorldPosition.Y);
}
climbMode.TryMoveTo(entityToMove.Transform.WorldPosition, endPoint);
// we may potentially need additional logic since we're forcing a player onto a climbable
// there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for
PopupMessageOtherClientsInRange(user, Loc.GetString("{0:theName} forces {1:theName} onto {2:theName}!", user, entityToMove, Owner), 15);
_notifyManager.PopupMessage(user, user, Loc.GetString("You force {0:theName} onto {1:theName}!", entityToMove, Owner));
}
}
private async void TryClimb(IEntity user)
{
var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, Owner)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true
};
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
if (result != DoAfterStatus.Cancelled && user.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
{
var direction = (Owner.Transform.WorldPosition - user.Transform.WorldPosition).Normalized;
var endPoint = Owner.Transform.WorldPosition;
var climbMode = user.GetComponent<ClimbingComponent>();
climbMode.IsClimbing = true;
if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
{
endPoint = new Vector2(user.Transform.WorldPosition.X, endPoint.Y);
}
else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
{
endPoint = new Vector2(endPoint.X, user.Transform.WorldPosition.Y);
}
climbMode.TryMoveTo(user.Transform.WorldPosition, endPoint);
PopupMessageOtherClientsInRange(user, Loc.GetString("{0:theName} jumps onto {1:theName}!", user, Owner), 15);
_notifyManager.PopupMessage(user, user, Loc.GetString("You jump onto {0:theName}!", Owner));
}
}
private void PopupMessageOtherClientsInRange(IEntity source, string message, int maxReceiveDistance)
{
var viewers = _playerManager.GetPlayersInRange(source.Transform.GridPosition, maxReceiveDistance);
foreach (var viewer in viewers)
{
var viewerEntity = viewer.AttachedEntity;
if (viewerEntity == null || source == viewerEntity)
{
continue;
}
source.PopupMessage(viewer.AttachedEntity, message);
}
}
}
}

View File

@@ -0,0 +1,76 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Content.Shared.Physics;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.EntitySystems;
namespace Content.Server.GameObjects.Components.Movement
{
[RegisterComponent]
public class ClimbingComponent : SharedClimbingComponent, IActionBlocker
{
private bool _isClimbing = false;
private ClimbController _climbController = default;
public override bool IsClimbing
{
get
{
return _isClimbing;
}
set
{
if (!value && Body != null)
{
Body.TryRemoveController<ClimbController>();
}
_isClimbing = value;
Dirty();
}
}
/// <summary>
/// Make the owner climb from one point to another
/// </summary>
public void TryMoveTo(Vector2 from, Vector2 to)
{
if (Body != null)
{
_climbController = Body.EnsureController<ClimbController>();
_climbController.TryMoveTo(from, to);
}
}
public void Update(float frameTime)
{
if (Body != null && IsClimbing)
{
if (_climbController != null && (_climbController.IsBlocked || !_climbController.IsActive))
{
if (Body.TryRemoveController<ClimbController>())
{
_climbController = null;
}
}
if (IsClimbing)
{
Body.WakeBody();
}
if (!IsOnClimbableThisFrame && IsClimbing && _climbController == null)
{
IsClimbing = false;
}
IsOnClimbableThisFrame = false;
}
}
public override ComponentState GetComponentState()
{
return new ClimbModeComponentState(_isClimbing);
}
}
}

View File

@@ -71,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Movement
_entityManager.TryGetEntity(grid.GridEntityId, out var gridEntity))
{
//TODO: Switch to shuttle component
if (!gridEntity.TryGetComponent(out ICollidableComponent collidable))
if (!gridEntity.TryGetComponent(out ICollidableComponent? collidable))
{
collidable = gridEntity.AddComponent<CollidableComponent>();
collidable.Mass = 1;
@@ -137,9 +137,9 @@ namespace Content.Server.GameObjects.Components.Movement
private void SetController(IEntity entity)
{
if (_controller != null ||
!entity.TryGetComponent(out MindComponent mind) ||
!entity.TryGetComponent(out MindComponent? mind) ||
mind.Mind == null ||
!Owner.TryGetComponent(out ServerStatusEffectsComponent status))
!Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
{
return;
}
@@ -179,17 +179,17 @@ namespace Content.Server.GameObjects.Components.Movement
/// <param name="entity">The entity to update</param>
private void UpdateRemovedEntity(IEntity entity)
{
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
{
status.RemoveStatusEffect(StatusEffect.Piloting);
}
if (entity.TryGetComponent(out MindComponent mind))
if (entity.TryGetComponent(out MindComponent? mind))
{
mind.Mind?.UnVisit();
}
if (entity.TryGetComponent(out BuckleComponent buckle))
if (entity.TryGetComponent(out BuckleComponent? buckle))
{
buckle.TryUnbuckle(entity, true);
}

View File

@@ -141,7 +141,7 @@ namespace Content.Server.GameObjects.Components.PDA
private void UpdatePDAAppearance()
{
_appearance?.SetData(PDAVisuals.ScreenLit, _lightOn);
_appearance?.SetData(PDAVisuals.FlashlightLit, _lightOn);
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
@@ -164,7 +164,7 @@ namespace Content.Server.GameObjects.Components.PDA
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
@@ -175,7 +175,7 @@ namespace Content.Server.GameObjects.Components.PDA
public bool UseEntity(UseEntityEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return false;
}

View File

@@ -64,7 +64,7 @@ namespace Content.Server.GameObjects.Components.Pointing
{
base.Startup();
if (Owner.TryGetComponent(out SpriteComponent sprite))
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}

View File

@@ -57,7 +57,7 @@ namespace Content.Server.GameObjects.Components.Pointing
private void UpdateAppearance()
{
if (_chasing == null ||
!Owner.TryGetComponent(out AppearanceComponent appearance))
!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -69,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Pointing
{
base.Startup();
if (Owner.TryGetComponent(out SpriteComponent sprite))
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}

View File

@@ -100,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Power
private void SetCurrentCharge(float newChargeAmount)
{
_currentCharge = FloatMath.Clamp(newChargeAmount, 0, MaxCharge);
_currentCharge = MathHelper.Clamp(newChargeAmount, 0, MaxCharge);
UpdateStorageState();
OnChargeChanged();
}

View File

@@ -24,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Rotatable
private void TryFlip(IEntity user)
{
if (Owner.TryGetComponent(out ICollidableComponent collidable) &&
if (Owner.TryGetComponent(out ICollidableComponent? collidable) &&
collidable.Anchored)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("It's stuck."));

View File

@@ -178,7 +178,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
var currentTime = _gameTiming.CurTime;
var timeSinceLastFire = (currentTime - _lastFire).TotalSeconds;
var newTheta = FloatMath.Clamp(_currentAngle.Theta + _angleIncrease - _angleDecay * timeSinceLastFire, _minAngle.Theta, _maxAngle.Theta);
var newTheta = MathHelper.Clamp(_currentAngle.Theta + _angleIncrease - _angleDecay * timeSinceLastFire, _minAngle.Theta, _maxAngle.Theta);
_currentAngle = new Angle(newTheta);
var random = (_robustRandom.NextDouble() - 0.5) * 2;

View File

@@ -373,7 +373,7 @@ namespace Content.Server.GameObjects.Components
return;
}
if (!player.TryGetComponent(out IHandsComponent handsComponent))
if (!player.TryGetComponent(out IHandsComponent? handsComponent))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player,
Loc.GetString("You have no hands."));

View File

@@ -647,7 +647,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
var additionalVector = (centerGrid.Position - entityGridCoords.Position);
var distance = additionalVector.Length;
// If we're too far no point, if we're close then cap it at the normalized vector
distance = FloatMath.Clamp(2.5f - distance, 0.0f, 1.0f);
distance = MathHelper.Clamp(2.5f - distance, 0.0f, 1.0f);
additionalVector = new Angle(90 * distance).RotateVec(additionalVector);
avoidanceVector += additionalVector;
// if we do need to avoid that means we'll have to lookahead for the next tile

View File

@@ -30,19 +30,19 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
[Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
/// <summary>
/// The tiles that have had their atmos data updated since last tick
/// </summary>
private Dictionary<GridId, HashSet<MapIndices>> _invalidTiles = new Dictionary<GridId, HashSet<MapIndices>>();
private Dictionary<IPlayerSession, PlayerGasOverlay> _knownPlayerChunks =
private Dictionary<IPlayerSession, PlayerGasOverlay> _knownPlayerChunks =
new Dictionary<IPlayerSession, PlayerGasOverlay>();
/// <summary>
/// Gas data stored in chunks to make PVS / bubbling easier.
/// </summary>
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _overlay =
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _overlay =
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
/// <summary>
@@ -52,7 +52,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
// Because the gas overlay updates aren't run every tick we need to avoid the pop-in that might occur with
// the regular PVS range.
private const float RangeOffset = 6.0f;
/// <summary>
/// Overlay update ticks per second.
/// </summary>
@@ -164,8 +164,8 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
var moles = tile.Air.Gases[i];
if (moles < gas.GasMolesVisible) continue;
var data = new GasData(i, (byte) (FloatMath.Clamp01(moles / gas.GasMolesVisibleMax) * 255));
var data = new GasData(i, (byte) (MathHelper.Clamp01(moles / gas.GasMolesVisibleMax) * 255));
tileData.Add(data);
}
@@ -175,7 +175,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return false;
}
return true;
}
@@ -187,10 +187,10 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
private List<GasOverlayChunk> GetChunksInRange(IEntity entity)
{
var inRange = new List<GasOverlayChunk>();
// This is the max in any direction that we can get a chunk (e.g. max 2 chunks away of data).
var (maxXDiff, maxYDiff) = ((int) (_updateRange / ChunkSize) + 1, (int) (_updateRange / ChunkSize) + 1);
var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition,
new Vector2(_updateRange, _updateRange));
@@ -202,7 +202,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
}
var entityTile = grid.GetTileRef(entity.Transform.GridPosition).GridIndices;
for (var x = -maxXDiff; x <= maxXDiff; x++)
{
for (var y = -maxYDiff; y <= maxYDiff; y++)
@@ -210,7 +210,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
var chunkIndices = GetGasChunkIndices(new MapIndices(entityTile.X + x * ChunkSize, entityTile.Y + y * ChunkSize));
if (!chunks.TryGetValue(chunkIndices, out var chunk)) continue;
// Now we'll check if it's in range and relevant for us
// (e.g. if we're on the very edge of a chunk we may need more chunks).
@@ -219,7 +219,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
yDiff > 0 && yDiff > _updateRange ||
xDiff < 0 && Math.Abs(xDiff + ChunkSize) > _updateRange ||
yDiff < 0 && Math.Abs(yDiff + ChunkSize) > _updateRange) continue;
inRange.Add(chunk);
}
}
@@ -237,25 +237,25 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return;
}
_updateRange = _configManager.GetCVar<float>("net.maxupdaterange") + RangeOffset;
// TODO: So in the worst case scenario we still have to send a LOT of tile data per tick if there's a fire.
// If we go with say 15 tile radius then we have up to 900 tiles to update per tick.
// In a saltern fire the worst you'll normally see is around 650 at the moment.
// Need a way to fake this more because sending almost 2,000 tile updates per second to even 50 players is... yikes
// I mean that's as big as it gets so larger maps will have the same but still, that's a lot of data.
// Some ways to do this are potentially: splitting fire and gas update data so they don't update at the same time
// (gives the illusion of more updates happening), e.g. if gas updates are 3 times a second and fires are 1.6 times a second or something.
// Could also look at updating tiles close to us more frequently (e.g. within 1 chunk every tick).
// Stuff just out of our viewport we need so when we move it doesn't pop in but it doesn't mean we need to update it every tick.
AccumulatedFrameTime -= _updateCooldown;
var gridAtmosComponents = new Dictionary<GridId, GridAtmosphereComponent>();
var updatedTiles = new Dictionary<GasOverlayChunk, HashSet<MapIndices>>();
// So up to this point we've been caching the updated tiles for multiple ticks.
// Now we'll go through and check whether the update actually matters for the overlay or not,
// and if not then we won't bother sending the data.
@@ -263,7 +263,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
var gridEntityId = _mapManager.GetGrid(gridId).GridEntityId;
if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent gam))
if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent? gam))
{
continue;
}
@@ -286,7 +286,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
tiles = new HashSet<MapIndices>();
updatedTiles[chunk] = tiles;
}
updatedTiles[chunk].Add(invalid);
chunk.Update(data, invalid);
}
@@ -306,13 +306,13 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
foreach (var (session, overlay) in _knownPlayerChunks)
{
if (session.AttachedEntity == null) continue;
// Get chunks in range and update if we've moved around or the chunks have new overlay data
var chunksInRange = GetChunksInRange(session.AttachedEntity);
var knownChunks = overlay.GetKnownChunks();
var chunksToRemove = new List<GasOverlayChunk>();
var chunksToAdd = new List<GasOverlayChunk>();
foreach (var chunk in chunksInRange)
{
if (!knownChunks.Contains(chunk))
@@ -328,7 +328,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
chunksToRemove.Add(chunk);
}
}
foreach (var chunk in chunksToAdd)
{
var message = overlay.AddChunk(currentTick, chunk);
@@ -342,7 +342,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
overlay.RemoveChunk(chunk);
}
var clientInvalids = new Dictionary<GridId, List<(MapIndices, GasOverlayData)>>();
// Check for any dirty chunks in range and bundle the data to send to the client.
@@ -355,7 +355,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
existingData = new List<(MapIndices, GasOverlayData)>();
clientInvalids[chunk.GridIndices] = existingData;
}
chunk.GetData(existingData, invalids);
}
@@ -370,10 +370,10 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
}
private sealed class PlayerGasOverlay
{
private readonly Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _data =
private readonly Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _data =
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
private readonly Dictionary<GasOverlayChunk, GameTick> _lastSent =
private readonly Dictionary<GasOverlayChunk, GameTick> _lastSent =
new Dictionary<GasOverlayChunk, GameTick>();
public GasOverlayMessage UpdateClient(GridId grid, List<(MapIndices, GasOverlayData)> data)
@@ -386,11 +386,11 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
_data.Clear();
_lastSent.Clear();
}
public List<GasOverlayChunk> GetKnownChunks()
{
var known = new List<GasOverlayChunk>();
foreach (var (_, chunks) in _data)
{
foreach (var (_, chunk) in chunks)
@@ -414,7 +414,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return null;
}
_lastSent[chunk] = currentTick;
var message = ChunkToMessage(chunk);
@@ -444,7 +444,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
// Chunk data should already be up to date.
// Only send relevant tiles to client.
var tileData = new List<(MapIndices, GasOverlayData)>();
for (var x = 0; x < ChunkSize; x++)
@@ -467,7 +467,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return null;
}
return new GasOverlayMessage(chunk.GridIndices, tileData);
}
}

View File

@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.EntitySystems
if (!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) return null;
return gridEnt.TryGetComponent(out IGridAtmosphereComponent atmos) ? atmos : null;
return gridEnt.TryGetComponent(out IGridAtmosphereComponent? atmos) ? atmos : null;
}
public override void Update(float frameTime)

View File

@@ -0,0 +1,18 @@
using Content.Server.GameObjects.Components.Movement;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
internal sealed class ClimbSystem : EntitySystem
{
public override void Update(float frameTime)
{
foreach (var comp in ComponentManager.EntityQuery<ClimbingComponent>())
{
comp.Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
internal sealed class CloningSystem : EntitySystem
{
public static List<EntityUid> scannedUids = new List<EntityUid>();
public static void AddToScannedUids(EntityUid uid)
{
if (!scannedUids.Contains(uid))
{
scannedUids.Add(uid);
}
}
public static bool HasUid(EntityUid uid)
{
return scannedUids.Contains(uid);
}
}
}

View File

@@ -54,7 +54,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
// For this we need to stay on the same hand slot and need the same item in that hand slot
// (or if there is no item there we need to keep it free).
if (eventArgs.NeedHand && eventArgs.User.TryGetComponent(out HandsComponent handsComponent))
if (eventArgs.NeedHand && eventArgs.User.TryGetComponent(out HandsComponent? handsComponent))
{
_activeHand = handsComponent.ActiveHand;
_activeItem = handsComponent.GetActiveHand;
@@ -126,7 +126,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
}
if (EventArgs.BreakOnStun &&
EventArgs.User.TryGetComponent(out StunnableComponent stunnableComponent) &&
EventArgs.User.TryGetComponent(out StunnableComponent? stunnableComponent) &&
stunnableComponent.Stunned)
{
return true;
@@ -134,7 +134,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
if (EventArgs.NeedHand)
{
if (!EventArgs.User.TryGetComponent(out HandsComponent handsComponent))
if (!EventArgs.User.TryGetComponent(out HandsComponent? handsComponent))
{
// If we had a hand but no longer have it that's still a paddlin'
if (_activeHand != null)

View File

@@ -7,6 +7,7 @@ namespace Content.Server.GameObjects.EntitySystems
[UsedImplicitly]
internal sealed class MedicalScannerSystem : EntitySystem
{
public override void Update(float frameTime)
{
foreach (var comp in ComponentManager.EntityQuery<MedicalScannerComponent>())

View File

@@ -83,7 +83,7 @@ namespace Content.Server.GameObjects.EntitySystems
ev.Entity.RemoveComponent<PlayerInputMoverComponent>();
}
if (ev.Entity.TryGetComponent(out ICollidableComponent physics) &&
if (ev.Entity.TryGetComponent(out ICollidableComponent? physics) &&
physics.TryGetController(out MoverController controller))
{
controller.StopMoving();

View File

@@ -2,10 +2,13 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Pointing;
using Content.Server.Players;
using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Interfaces;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Robust.Server.GameObjects.Components;
using Robust.Server.Interfaces.Player;
using Robust.Server.Player;
@@ -40,6 +43,8 @@ namespace Content.Server.GameObjects.EntitySystems
/// </summary>
private readonly Dictionary<ICommonSession, TimeSpan> _pointers = new Dictionary<ICommonSession, TimeSpan>();
private const float PointingRange = 15f;
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Disconnected)
@@ -79,7 +84,7 @@ namespace Content.Server.GameObjects.EntitySystems
public bool TryPoint(ICommonSession? session, GridCoordinates coords, EntityUid uid)
{
var player = session?.AttachedEntity;
var player = (session as IPlayerSession)?.ContentData().Mind.CurrentEntity;
if (player == null)
{
return false;
@@ -112,16 +117,27 @@ namespace Content.Server.GameObjects.EntitySystems
}
}
var viewers = _playerManager.GetPlayersInRange(player.Transform.GridPosition, 15);
var arrow = EntityManager.SpawnEntity("pointingarrow", coords);
if (player.TryGetComponent(out VisibilityComponent playerVisibility))
var layer = (int)VisibilityFlags.Normal;
if (player.TryGetComponent(out VisibilityComponent? playerVisibility))
{
var arrowVisibility = arrow.EnsureComponent<VisibilityComponent>();
arrowVisibility.Layer = playerVisibility.Layer;
layer = arrowVisibility.Layer = playerVisibility.Layer;
}
// Get players that are in range and whose visibility layer matches the arrow's.
var viewers = _playerManager.GetPlayersBy((playerSession) =>
{
if ((playerSession.VisibilityMask & layer) == 0)
return false;
var ent = playerSession.ContentData().Mind.CurrentEntity;
return ent != null
&& ent.Transform.MapPosition.InRange(player.Transform.MapPosition, PointingRange);
});
string selfMessage;
string viewerMessage;
string? viewerPointedAtMessage = null;

View File

@@ -1,4 +1,8 @@
using Content.Server.StationEvents;
using System;
using System.Collections.Generic;
using System.Text;
using Content.Server.StationEvents;
using Content.Server.Interfaces.GameTicking;
using JetBrains.Annotations;
using Robust.Server.Console;
using Robust.Server.Interfaces.Player;
@@ -9,25 +13,23 @@ using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using System;
using System.Collections.Generic;
using System.Text;
using static Content.Shared.StationEvents.SharedStationEvent;
namespace Content.Server.GameObjects.EntitySystems.StationEvents
{
[UsedImplicitly]
// Somewhat based off of TG's implementation of events
public sealed class StationEventSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IServerNetManager _netManager;
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private readonly IGameTicker _gameTicker;
#pragma warning restore 649
// Somewhat based off of TG's implementation of events
public StationEvent CurrentEvent { get; private set; }
public IReadOnlyCollection<StationEvent> StationEvents => _stationEvents;
private List<StationEvent> _stationEvents = new List<StationEvent>();
private const float MinimumTimeUntilFirstEvent = 600;
@@ -194,7 +196,18 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
{
return;
}
// Stop events from happening in lobby and force active event to end if the round ends
if (_gameTicker.RunLevel != GameTicking.GameRunLevel.InRound)
{
if (CurrentEvent != null)
{
Enabled = false;
}
return;
}
// Keep running the current event
if (CurrentEvent != null)
{

View File

@@ -51,12 +51,7 @@ namespace Content.Server.GameObjects.EntitySystems
continue;
}
if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
{
break;
}
if (verb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity))
if (!VerbUtility.VerbAccessChecks(userEntity, entity, verb))
{
break;
}
@@ -72,13 +67,7 @@ namespace Content.Server.GameObjects.EntitySystems
continue;
}
if (globalVerb.RequireInteractionRange &&
!VerbUtility.InVerbUseRange(userEntity, entity))
{
break;
}
if (globalVerb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity))
if (!VerbUtility.VerbAccessChecks(userEntity, entity, globalVerb))
{
break;
}
@@ -110,19 +99,9 @@ namespace Content.Server.GameObjects.EntitySystems
//Get verbs, component dependent.
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
continue;
if (verb.BlockedByContainers)
if (!VerbUtility.VerbAccessChecks(userEntity, entity, verb))
{
if (!userEntity.IsInSameOrNoContainer(entity))
{
if (!ContainerHelpers.TryGetContainer(entity, out var container) ||
container.Owner != userEntity)
{
continue;
}
}
continue;
}
var verbData = verb.GetData(userEntity, component);
@@ -136,11 +115,10 @@ namespace Content.Server.GameObjects.EntitySystems
//Get global verbs. Visible for all entities regardless of their components.
foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
{
if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
continue;
if (globalVerb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity))
if (!VerbUtility.VerbAccessChecks(userEntity, entity, globalVerb))
{
continue;
}
var verbData = globalVerb.GetData(userEntity, entity);
if (verbData.IsInvisible)

View File

@@ -5,6 +5,7 @@ namespace Content.Server.GameObjects
[Flags]
public enum VisibilityFlags
{
Normal = 1,
Ghost = 2,
}
}

View File

@@ -12,6 +12,7 @@ namespace Content.Server.GameTicking
public abstract bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false);
public virtual string ModeTitle => "Sandbox";
public virtual string Description => "Secret!";
public virtual bool DisallowLateJoin => false;
public Dictionary<string, HumanoidCharacterProfile> readyProfiles;
}
}

View File

@@ -13,6 +13,7 @@ using System.Linq;
using Content.Server.GameObjects.Components.Suspicion;
using Content.Shared.Roles;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Maths;
@@ -24,17 +25,32 @@ namespace Content.Server.GameTicking.GamePresets
[Dependency] private readonly IChatManager _chatManager;
[Dependency] private readonly IGameTicker _gameTicker;
[Dependency] private readonly IRobustRandom _random;
[Dependency] private readonly IConfigurationManager _cfg;
[Dependency] private IPrototypeManager _prototypeManager;
#pragma warning restore 649
public int MinPlayers { get; set; } = 5;
public int MinTraitors { get; set; } = 2;
public int PlayersPerTraitor { get; set; } = 5;
public int MinPlayers { get; set; }
public int MinTraitors { get; set; }
public int PlayersPerTraitor { get; set; }
public override bool DisallowLateJoin => true;
private static string TraitorID = "SuspicionTraitor";
private static string InnocentID = "SuspicionInnocent";
public static void RegisterCVars(IConfigurationManager cfg)
{
cfg.RegisterCVar("game.suspicion_min_players", 5);
cfg.RegisterCVar("game.suspicion_min_traitors", 2);
cfg.RegisterCVar("game.suspicion_players_per_traitor", 5);
}
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
{
MinPlayers = _cfg.GetCVar<int>("game.suspicion_min_players");
MinTraitors = _cfg.GetCVar<int>("game.suspicion_min_traitors");
PlayersPerTraitor = _cfg.GetCVar<int>("game.suspicion_players_per_traitor");
if (!force && readyPlayers.Count < MinPlayers)
{
_chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {readyPlayers.Count} players readied up out of {MinPlayers} needed.");
@@ -65,14 +81,21 @@ namespace Content.Server.GameTicking.GamePresets
player.AttachedEntity?.EnsureComponent<SuspicionRoleComponent>();
}
var numTraitors = FloatMath.Clamp(readyPlayers.Count % PlayersPerTraitor,
var numTraitors = MathHelper.Clamp(readyPlayers.Count / PlayersPerTraitor,
MinTraitors, readyPlayers.Count);
var traitors = new List<SuspicionTraitorRole>();
for (var i = 0; i < numTraitors; i++)
{
IPlayerSession traitor;
if(prefList.Count == 0)
{
if (list.Count == 0)
{
Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection.");
break;
}
traitor = _random.PickAndTake(list);
Logger.InfoS("preset", "Insufficient preferred traitors, picking at random.");
}
@@ -84,7 +107,9 @@ namespace Content.Server.GameTicking.GamePresets
}
var mind = traitor.Data.ContentData().Mind;
var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorID);
mind.AddRole(new SuspicionTraitorRole(mind, antagPrototype));
var traitorRole = new SuspicionTraitorRole(mind, antagPrototype);
mind.AddRole(traitorRole);
traitors.Add(traitorRole);
}
foreach (var player in list)
@@ -94,6 +119,11 @@ namespace Content.Server.GameTicking.GamePresets
mind.AddRole(new SuspicionInnocentRole(mind, antagPrototype));
}
foreach (var traitor in traitors)
{
traitor.GreetSuspicion(traitors, _chatManager);
}
_gameTicker.AddGameRule<RuleSuspicion>();
return true;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading;
using Content.Server.GameObjects.Components.Suspicion;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Mobs.Roles;
@@ -50,7 +51,8 @@ namespace Content.Server.GameTicking.GameRules
foreach (var playerSession in _playerManager.GetAllPlayers())
{
if (playerSession.AttachedEntity == null
|| !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable))
|| !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable)
|| !playerSession.AttachedEntity.TryGetComponent(out SuspicionRoleComponent suspicionRole))
{
continue;
}
@@ -69,24 +71,46 @@ namespace Content.Server.GameTicking.GameRules
if ((innocentsAlive + traitorsAlive) == 0)
{
_chatManager.DispatchServerAnnouncement("Everybody is dead, it's a stalemate!");
EndRound();
EndRound(Victory.Stalemate);
}
else if (traitorsAlive == 0)
{
_chatManager.DispatchServerAnnouncement("The traitors are dead! The innocents win.");
EndRound();
EndRound(Victory.Innocents);
}
else if (innocentsAlive == 0)
{
_chatManager.DispatchServerAnnouncement("The innocents are dead! The traitors win.");
EndRound();
EndRound(Victory.Traitors);
}
}
private void EndRound()
private enum Victory
{
_gameTicker.EndRound();
Stalemate,
Innocents,
Traitors
}
private void EndRound(Victory victory)
{
string text;
switch (victory)
{
case Victory.Innocents:
text = "The innocents have won!";
break;
case Victory.Traitors:
text = "The traitors have won!";
break;
default:
text = "Nobody wins!";
break;
}
_gameTicker.EndRound(text);
_chatManager.DispatchServerAnnouncement($"Restarting in 10 seconds.");
_checkTimerCancel.Cancel();
Timer.Spawn(TimeSpan.FromSeconds(10), () => _gameTicker.RestartRound());

View File

@@ -80,9 +80,8 @@ namespace Content.Server.GameTicking
[ViewVariables] private readonly List<GameRule> _gameRules = new List<GameRule>();
[ViewVariables] private readonly List<ManifestEntry> _manifest = new List<ManifestEntry>();
// Value is whether they're ready.
[ViewVariables]
private readonly Dictionary<IPlayerSession, bool> _playersInLobby = new Dictionary<IPlayerSession, bool>();
private readonly Dictionary<IPlayerSession, PlayerStatus> _playersInLobby = new Dictionary<IPlayerSession, PlayerStatus>();
[ViewVariables] private bool _initialized;
@@ -94,6 +93,8 @@ namespace Content.Server.GameTicking
[ViewVariables] private GameRunLevel _runLevel;
[ViewVariables(VVAccess.ReadWrite)] private GridCoordinates _spawnPoint;
[ViewVariables] private bool DisallowLateJoin { get; set; } = false;
[ViewVariables] private bool LobbyEnabled => _configurationManager.GetCVar<bool>("game.lobbyenabled");
[ViewVariables] private bool _updateOnRoundEnd;
@@ -132,6 +133,8 @@ namespace Content.Server.GameTicking
_configurationManager.RegisterCVar("game.defaultpreset", "Suspicion", CVar.ARCHIVE);
_configurationManager.RegisterCVar("game.fallbackpreset", "Sandbox", CVar.ARCHIVE);
PresetSuspicion.RegisterCVars(_configurationManager);
_playerManager.PlayerStatusChanged += _handlePlayerStatusChanged;
_netManager.RegisterNetMessage<MsgTickerJoinLobby>(nameof(MsgTickerJoinLobby));
@@ -142,6 +145,7 @@ namespace Content.Server.GameTicking
_netManager.RegisterNetMessage<MsgTickerLobbyReady>(nameof(MsgTickerLobbyReady));
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage));
_netManager.RegisterNetMessage<MsgRequestWindowAttention>(nameof(MsgRequestWindowAttention));
_netManager.RegisterNetMessage<MsgTickerLateJoinStatus>(nameof(MsgTickerLateJoinStatus));
SetStartPreset(_configurationManager.GetCVar<string>("game.defaultpreset"));
@@ -234,7 +238,7 @@ namespace Content.Server.GameTicking
List<IPlayerSession> readyPlayers;
if (LobbyEnabled)
{
readyPlayers = _playersInLobby.Where(p => p.Value).Select(p => p.Key).ToList();
readyPlayers = _playersInLobby.Where(p => p.Value == PlayerStatus.Ready).Select(p => p.Key).ToList();
}
else
{
@@ -285,6 +289,8 @@ namespace Content.Server.GameTicking
// Time to start the preset.
var preset = MakeGamePreset(profiles);
DisallowLateJoin |= preset.DisallowLateJoin;
if (!preset.Start(assignedJobs.Keys.ToList(), force))
{
SetStartPreset(_configurationManager.GetCVar<string>("game.fallbackpreset"));
@@ -295,11 +301,21 @@ namespace Content.Server.GameTicking
{
throw new ApplicationException("Fallback preset failed to start!");
}
DisallowLateJoin = false;
DisallowLateJoin |= newPreset.DisallowLateJoin;
}
_roundStartTimeSpan = IoCManager.Resolve<IGameTiming>().RealTime;
_sendStatusToAll();
ReqWindowAttentionAll();
UpdateLateJoinStatus();
}
private void UpdateLateJoinStatus()
{
var msg = new MsgTickerLateJoinStatus(null) {Disallowed = DisallowLateJoin};
_netManager.ServerSendToAll(msg);
}
private void SendServerMessage(string message)
@@ -314,7 +330,7 @@ namespace Content.Server.GameTicking
(HumanoidCharacterProfile) (await _prefsManager.GetPreferencesAsync(p.SessionId.Username))
.SelectedCharacter;
public void EndRound()
public void EndRound(string roundEndText = "")
{
DebugTools.Assert(RunLevel == GameRunLevel.InRound);
Logger.InfoS("ticker", "Ending round!");
@@ -324,6 +340,7 @@ namespace Content.Server.GameTicking
//Tell every client the round has ended.
var roundEndMessage = _netManager.CreateNetMessage<MsgRoundEndMessage>();
roundEndMessage.GamemodeTitle = MakeGamePreset(null).ModeTitle;
roundEndMessage.RoundEndText = roundEndText;
//Get the timespan of the round.
roundEndMessage.RoundDuration = IoCManager.Resolve<IGameTiming>().RealTime.Subtract(_roundStartTimeSpan);
@@ -368,6 +385,8 @@ namespace Content.Server.GameTicking
if (!_playersInLobby.ContainsKey(player)) return;
_spawnObserver(player);
_playersInLobby[player] = PlayerStatus.Observer;
_netManager.ServerSendToAll(GetStatusSingle(player, PlayerStatus.Observer));
}
public void MakeJoinGame(IPlayerSession player, string jobId = null)
@@ -381,9 +400,10 @@ namespace Content.Server.GameTicking
{
if (!_playersInLobby.ContainsKey(player)) return;
_playersInLobby[player] = ready;
var status = ready ? PlayerStatus.Ready : PlayerStatus.NotReady;
_playersInLobby[player] = ready ? PlayerStatus.Ready : PlayerStatus.NotReady;
_netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient);
_netManager.ServerSendToAll(GetReadySingle(player, ready));
_netManager.ServerSendToAll(GetStatusSingle(player, status));
}
public T AddGameRule<T>() where T : GameRule, new()
@@ -400,12 +420,12 @@ namespace Content.Server.GameTicking
public bool HasGameRule(Type t)
{
if (t == null || !t.IsAssignableFrom(typeof(GameRule)))
if (t == null || !typeof(GameRule).IsAssignableFrom(t))
return false;
foreach (var rule in _gameRules)
{
if (rule.GetType().Equals(t))
if (rule.GetType().IsAssignableFrom(t))
return true;
}
@@ -637,7 +657,7 @@ namespace Content.Server.GameTicking
_playerJoinLobby(player);
}
EntitySystem.Get<GasTileOverlaySystem>().ResettingCleanup();
EntitySystem.Get<PathfindingSystem>().ResettingCleanup();
EntitySystem.Get<AiReachableSystem>().ResettingCleanup();
@@ -646,6 +666,7 @@ namespace Content.Server.GameTicking
_spawnedPositions.Clear();
_manifest.Clear();
DisallowLateJoin = false;
}
private void _preRoundSetup()
@@ -776,6 +797,12 @@ namespace Content.Server.GameTicking
string jobId = null,
bool lateJoin = true)
{
if (lateJoin && DisallowLateJoin)
{
MakeObserve(session);
return;
}
_playerJoinGame(session);
var data = session.ContentData();
@@ -868,13 +895,13 @@ namespace Content.Server.GameTicking
private void _playerJoinLobby(IPlayerSession session)
{
_playersInLobby.Add(session, false);
_playersInLobby.Add(session, PlayerStatus.NotReady);
_prefsManager.OnClientConnected(session);
_netManager.ServerSendMessage(_netManager.CreateNetMessage<MsgTickerJoinLobby>(), session.ConnectedClient);
_netManager.ServerSendMessage(_getStatusMsg(session), session.ConnectedClient);
_netManager.ServerSendMessage(GetInfoMsg(), session.ConnectedClient);
_netManager.ServerSendMessage(GetReadyStatus(), session.ConnectedClient);
_netManager.ServerSendMessage(GetPlayerStatus(), session.ConnectedClient);
}
private void _playerJoinGame(IPlayerSession session)
@@ -886,33 +913,35 @@ namespace Content.Server.GameTicking
_netManager.ServerSendMessage(_netManager.CreateNetMessage<MsgTickerJoinGame>(), session.ConnectedClient);
}
private MsgTickerLobbyReady GetReadyStatus()
private MsgTickerLobbyReady GetPlayerStatus()
{
var msg = _netManager.CreateNetMessage<MsgTickerLobbyReady>();
msg.PlayerReady = new Dictionary<NetSessionId, bool>();
msg.PlayerStatus = new Dictionary<NetSessionId, PlayerStatus>();
foreach (var player in _playersInLobby.Keys)
{
_playersInLobby.TryGetValue(player, out var ready);
msg.PlayerReady.Add(player.SessionId, ready);
_playersInLobby.TryGetValue(player, out var status);
msg.PlayerStatus.Add(player.SessionId, status);
}
return msg;
}
private MsgTickerLobbyReady GetReadySingle(IPlayerSession player, bool ready)
private MsgTickerLobbyReady GetStatusSingle(IPlayerSession player, PlayerStatus status)
{
var msg = _netManager.CreateNetMessage<MsgTickerLobbyReady>();
msg.PlayerReady = new Dictionary<NetSessionId, bool>();
msg.PlayerReady.Add(player.SessionId, ready);
msg.PlayerStatus = new Dictionary<NetSessionId, PlayerStatus>
{
{ player.SessionId, status }
};
return msg;
}
private MsgTickerLobbyStatus _getStatusMsg(IPlayerSession session)
{
_playersInLobby.TryGetValue(session, out var ready);
_playersInLobby.TryGetValue(session, out var status);
var msg = _netManager.CreateNetMessage<MsgTickerLobbyStatus>();
msg.IsRoundStarted = RunLevel != GameRunLevel.PreRoundLobby;
msg.StartTime = _roundStartTimeUtc;
msg.YouAreReady = ready;
msg.YouAreReady = status == PlayerStatus.Ready;
msg.Paused = Paused;
return msg;
}

View File

@@ -22,7 +22,7 @@ namespace Content.Server.Interfaces.GameTicking
void RestartRound();
void StartRound(bool force = false);
void EndRound();
void EndRound(string roundEndText = "");
void Respawn(IPlayerSession targetPlayer);
void MakeObserve(IPlayerSession player);

View File

@@ -24,7 +24,7 @@ namespace Content.Server.Mobs.Roles
base.Greet();
var chat = IoCManager.Resolve<IChatManager>();
chat.DispatchServerMessage(Mind.Session, $"You're a {Name}!");
chat.DispatchServerMessage(Mind.Session, $"You're an {Name}!");
chat.DispatchServerMessage(Mind.Session, $"Objective: {Objective}");
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Interfaces.Chat;
using Content.Shared.Roles;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.Mobs.Roles
{
@@ -19,13 +21,25 @@ namespace Content.Server.Mobs.Roles
public string Objective => Prototype.Objective;
public override bool Antagonist { get; }
public override void Greet()
public void GreetSuspicion(List<SuspicionTraitorRole> traitors, IChatManager chatMgr)
{
base.Greet();
chatMgr.DispatchServerMessage(Mind.Session, Loc.GetString("You're a {0}!", Name));
chatMgr.DispatchServerMessage(Mind.Session, Loc.GetString("Objective: {0}", Objective));
var chat = IoCManager.Resolve<IChatManager>();
chat.DispatchServerMessage(Mind.Session, $"You're a {Name}!");
chat.DispatchServerMessage(Mind.Session, $"Objective: {Objective}");
if (traitors.Count == 1)
{
// Only traitor.
chatMgr.DispatchServerMessage(Mind.Session, Loc.GetString("You're on your own. Good luck!"));
return;
}
var text = string.Join(", ", traitors.Where(p => p != this).Select(p => p.Mind.CharacterName));
var pluralText = Loc.GetPluralString("Your partner in crime is: {0}",
"Your partners in crime are: {0}",
traitors.Count-1, text);
chatMgr.DispatchServerMessage(Mind.Session, pluralText);
}
}
}

View File

@@ -60,7 +60,7 @@ namespace Content.Server.Preferences
await _prefsSemaphore.WaitAsync();
try
{
index = FloatMath.Clamp(index, 0, _maxCharacterSlots - 1);
index = MathHelper.Clamp(index, 0, _maxCharacterSlots - 1);
await _prefsDb.SaveSelectedCharacterIndex(username, index);
}
finally

View File

@@ -50,11 +50,11 @@ namespace Content.Server.Preferences
{
case "sqlite":
var configPreferencesDbPath = _configuration.GetCVar<string>("database.prefs_sqlite_dbpath");
var finalPreferencesDbPath =
var inMemory = _resourceManager.UserData.RootDir == null;
var finalPreferencesDbPath = inMemory ?
null :
Path.Combine(_resourceManager.UserData.RootDir, configPreferencesDbPath);
dbConfig = new SqliteConfiguration(
finalPreferencesDbPath
);
dbConfig = new SqliteConfiguration(finalPreferencesDbPath);
break;
case "postgres":
dbConfig = new PostgresConfiguration(

View File

@@ -1,5 +1,4 @@
#nullable enable
using Content.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.EntitySystems.StationEvents;
using JetBrains.Annotations;
using Robust.Server.Interfaces.Console;