Merge branch 'master' into kitchen

This commit is contained in:
Pieter-Jan Briers
2020-05-05 11:35:20 +02:00
committed by GitHub
112 changed files with 2184 additions and 408 deletions

View File

@@ -151,6 +151,12 @@ namespace Content.Server.GameObjects
return success;
}
public void PutInHandOrDrop(ItemComponent item)
{
if (!PutInHand(item))
item.Owner.Transform.GridPosition = Owner.Transform.GridPosition;
}
public bool CanPutInHand(ItemComponent item)
{
foreach (var hand in ActivePriorityEnumerable())

View File

@@ -0,0 +1,206 @@
using Content.Server.GameObjects.Components.Damage;
using Content.Server.GameObjects.Components.Interactable.Tools;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.GameObjects.Components.Gravity;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Gravity
{
[RegisterComponent]
public class GravityGeneratorComponent: SharedGravityGeneratorComponent, IAttackBy, IBreakAct, IAttackHand
{
private BoundUserInterface _userInterface;
private PowerDeviceComponent _powerDevice;
private SpriteComponent _sprite;
private bool _switchedOn;
private bool _intact;
private GravityGeneratorStatus _status;
public bool Powered => _powerDevice.Powered;
public bool SwitchedOn => _switchedOn;
public bool Intact => _intact;
public GravityGeneratorStatus Status => _status;
public bool NeedsUpdate
{
get
{
switch (_status)
{
case GravityGeneratorStatus.On:
return !(Powered && SwitchedOn && Intact);
case GravityGeneratorStatus.Off:
return SwitchedOn || !(Powered && Intact);
case GravityGeneratorStatus.Unpowered:
return SwitchedOn || Powered || !Intact;
case GravityGeneratorStatus.Broken:
return SwitchedOn || Powered || Intact;
default:
return true; // This _should_ be unreachable
}
}
}
public override string Name => "GravityGenerator";
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GravityGeneratorUiKey.Key);
_userInterface.OnReceiveMessage += HandleUIMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_sprite = Owner.GetComponent<SpriteComponent>();
_switchedOn = true;
_intact = true;
_status = GravityGeneratorStatus.On;
UpdateState();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _switchedOn, "switched_on", true);
serializer.DataField(ref _intact, "intact", true);
}
bool IAttackHand.AttackHand(AttackHandEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent<IActorComponent>(out var actor))
return false;
if (Status != GravityGeneratorStatus.Off && Status != GravityGeneratorStatus.On)
{
return false;
}
OpenUserInterface(actor.playerSession);
return true;
}
public bool AttackBy(AttackByEventArgs eventArgs)
{
if (!eventArgs.AttackWith.TryGetComponent<WelderComponent>(out var welder)) return false;
if (welder.TryUse(5.0f))
{
// Repair generator
var damagable = Owner.GetComponent<DamageableComponent>();
var breakable = Owner.GetComponent<BreakableComponent>();
damagable.HealAllDamage();
breakable.broken = false;
_intact = true;
var entitySystemManager = IoCManager.Resolve<IEntitySystemManager>();
var notifyManager = IoCManager.Resolve<IServerNotifyManager>();
entitySystemManager.GetEntitySystem<AudioSystem>().Play("/Audio/items/welder2.ogg", Owner);
notifyManager.PopupMessage(Owner, eventArgs.User, Loc.GetString("You repair the gravity generator with the welder"));
return true;
} else
{
return false;
}
}
public void OnBreak(BreakageEventArgs eventArgs)
{
_intact = false;
_switchedOn = false;
}
public void UpdateState()
{
if (!Intact)
{
MakeBroken();
} else if (!Powered)
{
MakeUnpowered();
} else if (!SwitchedOn)
{
MakeOff();
} else
{
MakeOn();
}
}
private void HandleUIMessage(ServerBoundUserInterfaceMessage message)
{
switch (message.Message)
{
case GeneratorStatusRequestMessage _:
_userInterface.SetState(new GeneratorState(Status == GravityGeneratorStatus.On));
break;
case SwitchGeneratorMessage msg:
_switchedOn = msg.On;
UpdateState();
break;
default:
break;
}
}
private void OpenUserInterface(IPlayerSession playerSession)
{
_userInterface.Open(playerSession);
}
private void MakeBroken()
{
_status = GravityGeneratorStatus.Broken;
_sprite.LayerSetState(0, "broken");
_sprite.LayerSetVisible(1, false);
}
private void MakeUnpowered()
{
_status = GravityGeneratorStatus.Unpowered;
_sprite.LayerSetState(0, "off");
_sprite.LayerSetVisible(1, false);
}
private void MakeOff()
{
_status = GravityGeneratorStatus.Off;
_sprite.LayerSetState(0, "off");
_sprite.LayerSetVisible(1, false);
}
private void MakeOn()
{
_status = GravityGeneratorStatus.On;
_sprite.LayerSetState(0, "on");
_sprite.LayerSetVisible(1, true);
}
}
public enum GravityGeneratorStatus
{
Broken,
Unpowered,
Off,
On
}
}

View File

@@ -45,6 +45,8 @@ namespace Content.Server.GameObjects
private int StorageCapacityMax = 10000;
public HashSet<IPlayerSession> SubscribedSessions = new HashSet<IPlayerSession>();
public IReadOnlyCollection<IEntity> StoredEntities => storage.ContainedEntities;
public override void Initialize()
{
base.Initialize();
@@ -140,7 +142,6 @@ namespace Content.Server.GameObjects
/// <returns></returns>
public bool AttackBy(AttackByEventArgs eventArgs)
{
_ensureInitialCalculated();
Logger.DebugS("Storage", "Storage (UID {0}) attacked by user (UID {1}) with entity (UID {2}).", Owner.Uid, eventArgs.User.Uid, eventArgs.AttackWith.Uid);
if(Owner.TryGetComponent<PlaceableSurfaceComponent>(out var placeableSurfaceComponent))
@@ -363,8 +364,10 @@ namespace Content.Server.GameObjects
/// <summary>
/// Inserts an entity into the storage component from the players active hand.
/// </summary>
private bool PlayerInsertEntity(IEntity player)
public bool PlayerInsertEntity(IEntity player)
{
_ensureInitialCalculated();
if (!player.TryGetComponent(out IHandsComponent hands) || hands.GetActiveHand == null)
return false;

View File

@@ -118,9 +118,17 @@ namespace Content.Server.GameObjects.Components.Mobs
if (!ShowExamineInfo)
return;
var dead = false;
if(Owner.TryGetComponent<SpeciesComponent>(out var species))
if (species.CurrentDamageState is DeadState)
dead = true;
// TODO: Use gendered pronouns depending on the entity
if(!HasMind)
message.AddMarkup($"[color=red]They are totally catatonic. The stresses of life in deep-space must have been too much for them. Any recovery is unlikely.[/color]");
message.AddMarkup(!dead
? $"[color=red]They are totally catatonic. The stresses of life in deep-space must have been too much for them. Any recovery is unlikely.[/color]"
: $"[color=purple]Their soul has departed.[/color]");
else if(Mind.Session == null)
message.AddMarkup("[color=yellow]They have a blank, absent-minded stare and appears completely unresponsive to anything. They may snap out of it soon.[/color]");
}

View File

@@ -177,7 +177,7 @@ namespace Content.Server.GameObjects
currentstate = threshold;
EntityEventArgs toRaise = new MobDamageStateChangedMessage(this);
var toRaise = new MobDamageStateChangedMessage(this);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, toRaise);
}

View File

@@ -90,6 +90,15 @@ namespace Content.Server.GameObjects.Components.Movement
}
}
/// <inheritdoc />
[ViewVariables]
public float CurrentPushSpeed => 5.0f;
/// <inheritdoc />
[ViewVariables]
public float GrabRange => 0.2f;
/// <summary>
/// Is the entity Sprinting (running)?
/// </summary>

View File

@@ -67,6 +67,14 @@ namespace Content.Server.GameObjects.Components.Movement
}
}
/// <inheritdoc />
[ViewVariables]
public float CurrentPushSpeed => 5.0f;
/// <inheritdoc />
[ViewVariables]
public float GrabRange => 0.2f;
/// <summary>
/// Is the entity Sprinting (running)?
/// </summary>

View File

@@ -34,6 +34,15 @@ namespace Content.Server.GameObjects.Components.Movement
[ViewVariables(VVAccess.ReadWrite)]
public float CurrentWalkSpeed { get; set; } = 8;
public float CurrentSprintSpeed { get; set; }
/// <inheritdoc />
[ViewVariables]
public float CurrentPushSpeed => 0.0f;
/// <inheritdoc />
[ViewVariables]
public float GrabRange => 0.0f;
public bool Sprinting { get; set; }
public Vector2 VelocityDir { get; } = Vector2.Zero;
public GridCoordinates LastPosition { get; set; }

View File

@@ -1,4 +1,5 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
@@ -16,17 +17,24 @@ namespace Content.Server.GameObjects.Components.Sound
public override string Name => "EmitSoundOnUse";
public string _soundName;
public float _pitchVariation;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _soundName, "sound", "");
serializer.DataField(ref _soundName, "sound", string.Empty);
serializer.DataField(ref _pitchVariation, "variation", 0.0f);
}
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
{
if (!string.IsNullOrWhiteSpace(_soundName))
{
if (_pitchVariation > 0.0)
{
Owner.GetComponent<SoundComponent>().Play(_soundName, AudioHelpers.WithVariation(_pitchVariation).WithVolume(-2f));
return true;
}
Owner.GetComponent<SoundComponent>().Play(_soundName, AudioParams.Default.WithVolume(-2f));
return true;
}

View File

@@ -302,8 +302,8 @@ namespace Content.Server.GameObjects.EntitySystems
var inputSys = EntitySystemManager.GetEntitySystem<InputSystem>();
inputSys.BindMap.BindFunction(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUseItemInHand));
inputSys.BindMap.BindFunction(ContentKeyFunctions.Attack,
new PointerInputCmdHandler(HandleAttack));
inputSys.BindMap.BindFunction(ContentKeyFunctions.WideAttack,
new PointerInputCmdHandler(HandleWideAttack));
inputSys.BindMap.BindFunction(ContentKeyFunctions.ActivateItemInWorld,
new PointerInputCmdHandler(HandleActivateItemInWorld));
}
@@ -362,7 +362,7 @@ namespace Content.Server.GameObjects.EntitySystems
activateComp.Activate(new ActivateEventArgs {User = user});
}
private bool HandleAttack(ICommonSession session, GridCoordinates coords, EntityUid uid)
private bool HandleWideAttack(ICommonSession session, GridCoordinates coords, EntityUid uid)
{
// client sanitization
if (!_mapManager.GridExists(coords.GridID))

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Gravity;
using Content.Server.GameObjects.Components.Mobs;
using JetBrains.Annotations;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Random;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class GravitySystem: EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
private const float GravityKick = 100.0f;
private const uint ShakeTimes = 10;
private Dictionary<GridId, uint> _gridsToShake;
private float internalTimer = 0.0f;
public override void Initialize()
{
EntityQuery = new TypeEntityQuery<GravityGeneratorComponent>();
_gridsToShake = new Dictionary<GridId, uint>();
}
public override void Update(float frameTime)
{
internalTimer += frameTime;
var gridsWithGravity = new List<GridId>();
foreach (var entity in RelevantEntities)
{
var generator = entity.GetComponent<GravityGeneratorComponent>();
if (generator.NeedsUpdate)
{
generator.UpdateState();
}
if (generator.Status == GravityGeneratorStatus.On)
{
gridsWithGravity.Add(entity.Transform.GridID);
}
}
foreach (var grid in _mapManager.GetAllGrids())
{
if (grid.HasGravity && !gridsWithGravity.Contains(grid.Index))
{
grid.HasGravity = false;
ScheduleGridToShake(grid.Index, ShakeTimes);
} else if (!grid.HasGravity && gridsWithGravity.Contains(grid.Index))
{
grid.HasGravity = true;
ScheduleGridToShake(grid.Index, ShakeTimes);
}
}
if (internalTimer > 0.2f)
{
ShakeGrids();
internalTimer = 0.0f;
}
}
private void ScheduleGridToShake(GridId gridId, uint shakeTimes)
{
if (!_gridsToShake.Keys.Contains(gridId))
{
_gridsToShake.Add(gridId, shakeTimes);
}
else
{
_gridsToShake[gridId] = shakeTimes;
}
// Play the gravity sound
foreach (var player in _playerManager.GetAllPlayers())
{
if (player.AttachedEntity == null
|| player.AttachedEntity.Transform.GridID != gridId) continue;
_entitySystemManager.GetEntitySystem<AudioSystem>().Play("/Audio/effects/alert.ogg", player.AttachedEntity);
}
}
private void ShakeGrids()
{
// I have to copy this because C# doesn't allow changing collections while they're
// getting enumerated.
var gridsToShake = new Dictionary<GridId, uint>(_gridsToShake);
foreach (var gridId in _gridsToShake.Keys)
{
if (_gridsToShake[gridId] == 0)
{
gridsToShake.Remove(gridId);
continue;
}
ShakeGrid(gridId);
gridsToShake[gridId] -= 1;
}
_gridsToShake = gridsToShake;
}
private void ShakeGrid(GridId gridId)
{
foreach (var player in _playerManager.GetAllPlayers())
{
if (player.AttachedEntity == null
|| player.AttachedEntity.Transform.GridID != gridId
|| !player.AttachedEntity.TryGetComponent(out CameraRecoilComponent recoil))
{
continue;
}
recoil.Kick(new Vector2(_random.NextFloat(), _random.NextFloat()) * GravityKick);
}
}
}
}

View File

@@ -1,9 +1,14 @@
using System;
using System.Linq;
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Throw;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.Input;
using Content.Shared.Interfaces;
using Content.Shared.Physics;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
@@ -19,6 +24,7 @@ using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -33,6 +39,7 @@ namespace Content.Server.GameObjects.EntitySystems
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
private const float ThrowForce = 1.5f; // Throwing force of mobs in Newtons
@@ -50,6 +57,8 @@ namespace Content.Server.GameObjects.EntitySystems
input.BindMap.BindFunction(ContentKeyFunctions.Drop, new PointerInputCmdHandler(HandleDrop));
input.BindMap.BindFunction(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem));
input.BindMap.BindFunction(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem));
input.BindMap.BindFunction(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack));
input.BindMap.BindFunction(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt));
}
/// <inheritdoc />
@@ -126,7 +135,7 @@ namespace Content.Server.GameObjects.EntitySystems
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
if(interactionSystem.InRangeUnobstructed(coords.ToMap(_mapManager), ent.Transform.WorldPosition, 0f, ignoredEnt: ent))
if(interactionSystem.InRangeUnobstructed(coords.ToMap(_mapManager), ent.Transform.WorldPosition, ignoredEnt: ent))
if (coords.InRange(_mapManager, ent.Transform.GridPosition, InteractionSystem.InteractionRange))
{
handsComp.Drop(handsComp.ActiveIndex, coords);
@@ -190,5 +199,53 @@ namespace Content.Server.GameObjects.EntitySystems
return true;
}
private void HandleSmartEquipBackpack(ICommonSession session)
{
HandleSmartEquip(session, EquipmentSlotDefines.Slots.BACKPACK);
}
private void HandleSmartEquipBelt(ICommonSession session)
{
HandleSmartEquip(session, EquipmentSlotDefines.Slots.BELT);
}
private void HandleSmartEquip(ICommonSession session, EquipmentSlotDefines.Slots equipementSlot)
{
var plyEnt = ((IPlayerSession) session).AttachedEntity;
if (plyEnt == null || !plyEnt.IsValid())
return;
if (!plyEnt.TryGetComponent(out HandsComponent handsComp) || !plyEnt.TryGetComponent(out InventoryComponent inventoryComp))
return;
if (!inventoryComp.TryGetSlotItem(equipementSlot, out ItemComponent equipmentItem)
|| !equipmentItem.Owner.TryGetComponent<ServerStorageComponent>(out var storageComponent))
{
_notifyManager.PopupMessage(plyEnt, plyEnt, Loc.GetString("You have no {0} to take something out of!", EquipmentSlotDefines.SlotNames[equipementSlot].ToLower()));
return;
}
var heldItem = handsComp.GetHand(handsComp.ActiveIndex)?.Owner;
if (heldItem != null)
{
storageComponent.PlayerInsertEntity(plyEnt);
}
else
{
if (storageComponent.StoredEntities.Count == 0)
{
_notifyManager.PopupMessage(plyEnt, plyEnt, Loc.GetString("There's nothing in your {0} to take out!", EquipmentSlotDefines.SlotNames[equipementSlot].ToLower()));
}
else
{
var lastStoredEntity = Enumerable.Last(storageComponent.StoredEntities);
if (storageComponent.Remove(lastStoredEntity))
handsComp.PutInHandOrDrop(lastStoredEntity.GetComponent<ItemComponent>());
}
}
}
}
}

View File

@@ -1,4 +1,6 @@
using Content.Server.GameObjects.Components;
using System;
using System.Net;
using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Sound;
@@ -15,6 +17,7 @@ using Robust.Server.Interfaces.Player;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Input;
@@ -28,6 +31,7 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -44,6 +48,7 @@ namespace Content.Server.GameObjects.EntitySystems
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IRobustRandom _robustRandom;
[Dependency] private readonly IConfigurationManager _configurationManager;
[Dependency] private readonly IEntityManager _entityManager;
#pragma warning restore 649
private AudioSystem _audioSystem;
@@ -130,13 +135,43 @@ namespace Content.Server.GameObjects.EntitySystems
}
var mover = entity.GetComponent<IMoverComponent>();
var physics = entity.GetComponent<PhysicsComponent>();
UpdateKinematics(entity.Transform, mover, physics);
if (entity.TryGetComponent<CollidableComponent>(out var collider))
{
UpdateKinematics(entity.Transform, mover, physics, collider);
}
else
{
UpdateKinematics(entity.Transform, mover, physics);
}
}
}
private void UpdateKinematics(ITransformComponent transform, IMoverComponent mover, PhysicsComponent physics)
private void UpdateKinematics(ITransformComponent transform, IMoverComponent mover, PhysicsComponent physics, CollidableComponent collider = null)
{
bool weightless = false;
var tile = _mapManager.GetGrid(transform.GridID).GetTileRef(transform.GridPosition).Tile;
if ((!_mapManager.GetGrid(transform.GridID).HasGravity || tile.IsEmpty) && collider != null)
{
weightless = true;
// No gravity: is our entity touching anything?
var touching = false;
foreach (var entity in _entityManager.GetEntitiesInRange(transform.Owner, mover.GrabRange, true))
{
if (entity.TryGetComponent<CollidableComponent>(out var otherCollider))
{
if (otherCollider.Owner == transform.Owner) continue; // Don't try to push off of yourself!
touching |= ((collider.CollisionMask & otherCollider.CollisionLayer) != 0x0
|| (otherCollider.CollisionMask & collider.CollisionLayer) != 0x0) // Ensure collision
&& !entity.HasComponent<ItemComponent>(); // This can't be an item
}
}
if (!touching)
{
return;
}
}
if (mover.VelocityDir.LengthSquared < 0.001 || !ActionBlockerSystem.CanMove(mover.Owner))
{
if (physics.LinearVelocity != Vector2.Zero)
@@ -145,6 +180,13 @@ namespace Content.Server.GameObjects.EntitySystems
}
else
{
if (weightless)
{
physics.LinearVelocity = mover.VelocityDir * mover.CurrentPushSpeed;
transform.LocalRotation = mover.VelocityDir.GetDir().ToAngle();
return;
}
physics.LinearVelocity = mover.VelocityDir * (mover.Sprinting ? mover.CurrentSprintSpeed : mover.CurrentWalkSpeed);
transform.LocalRotation = mover.VelocityDir.GetDir().ToAngle();

View File

@@ -1,11 +1,14 @@
namespace Content.Server.GameTicking
using System.Collections.Generic;
using Robust.Server.Interfaces.Player;
namespace Content.Server.GameTicking
{
/// <summary>
/// A round-start setup preset, such as which antagonists to spawn.
/// </summary>
public abstract class GamePreset
{
public abstract void Start();
public abstract bool Start(IReadOnlyList<IPlayerSession> players);
public virtual string ModeTitle => "Sandbox";
public virtual string Description => "Secret!";
}

View File

@@ -1,5 +1,7 @@
using Content.Server.GameTicking.GameRules;
using System.Collections.Generic;
using Content.Server.GameTicking.GameRules;
using Content.Server.Interfaces.GameTicking;
using Robust.Server.Interfaces.Player;
using Robust.Shared.IoC;
namespace Content.Server.GameTicking.GamePresets
@@ -10,9 +12,10 @@ namespace Content.Server.GameTicking.GamePresets
[Dependency] private readonly IGameTicker _gameTicker;
#pragma warning restore 649
public override void Start()
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers)
{
_gameTicker.AddGameRule<RuleDeathMatch>();
return true;
}
public override string ModeTitle => "Deathmatch";

View File

@@ -1,4 +1,6 @@
using Content.Server.Sandbox;
using System.Collections.Generic;
using Content.Server.Sandbox;
using Robust.Server.Interfaces.Player;
using Robust.Shared.IoC;
namespace Content.Server.GameTicking.GamePresets
@@ -9,9 +11,10 @@ namespace Content.Server.GameTicking.GamePresets
[Dependency] private readonly ISandboxManager _sandboxManager;
#pragma warning restore 649
public override void Start()
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers)
{
_sandboxManager.IsSandboxEnabled = true;
return true;
}
public override string ModeTitle => "Sandbox";

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameTicking.GameRules;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Mobs.Roles;
using Content.Server.Players;
using Content.Server.Sandbox;
using NFluidsynth;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Random;
using Logger = Robust.Shared.Log.Logger;
namespace Content.Server.GameTicking.GamePresets
{
public class PresetSuspicion : GamePreset
{
#pragma warning disable 649
[Dependency] private readonly ISandboxManager _sandboxManager;
[Dependency] private readonly IChatManager _chatManager;
[Dependency] private readonly IGameTicker _gameTicker;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
public int MinPlayers { get; set; } = 5;
public int MinTraitors { get; set; } = 2;
public int PlayersPerTraitor { get; set; } = 5;
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers)
{
if (readyPlayers.Count < MinPlayers)
{
_chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {readyPlayers.Count} players readied up out of {MinPlayers} needed.");
return false;
}
var list = new List<IPlayerSession>(readyPlayers);
var numTraitors = Math.Max(readyPlayers.Count() % PlayersPerTraitor, MinTraitors);
for (var i = 0; i < numTraitors; i++)
{
var traitor = _random.PickAndTake(list);
var mind = traitor.Data.ContentData().Mind;
mind.AddRole(new SuspicionTraitorRole(mind));
}
foreach (var player in list)
{
var mind = player.Data.ContentData().Mind;
mind.AddRole(new SuspicionInnocentRole(mind));
}
_gameTicker.AddGameRule<RuleSuspicion>();
return true;
}
public override string ModeTitle => "Suspicion";
public override string Description => "Suspicion on the Space Station. There are traitors on board... Can you kill them before they kill you?";
}
}

View File

@@ -0,0 +1,122 @@
using System;
using System.Threading;
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Mobs.Roles;
using Content.Server.Players;
using NFluidsynth;
using Robust.Server.Interfaces.Player;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Logger = Robust.Shared.Log.Logger;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameTicking.GameRules
{
/// <summary>
/// Simple GameRule that will do a free-for-all death match.
/// Kill everybody else to win.
/// </summary>
public sealed class RuleSuspicion : GameRule, IEntityEventSubscriber
{
private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(1);
#pragma warning disable 649
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private readonly IChatManager _chatManager;
[Dependency] private readonly IEntityManager _entityManager;
[Dependency] private readonly IGameTicker _gameTicker;
#pragma warning restore 649
private readonly CancellationTokenSource _checkTimerCancel = new CancellationTokenSource();
public override void Added()
{
_chatManager.DispatchServerAnnouncement("There are traitors on the station! Find them, and kill them!");
_entityManager.EventBus.SubscribeEvent<MobDamageStateChangedMessage>(EventSource.Local, this, _onMobDamageStateChanged);
Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token);
}
private void _onMobDamageStateChanged(MobDamageStateChangedMessage message)
{
var owner = message.Species.Owner;
if (!(message.Species.CurrentDamageState is DeadState))
return;
if (!owner.TryGetComponent<MindComponent>(out var mind))
return;
if (!mind.HasMind)
return;
message.Species.Owner.Description +=
mind.Mind.HasRole<SuspicionTraitorRole>() ? "\nThey were a traitor!" : "\nThey were an innocent!";
}
public override void Removed()
{
base.Removed();
_checkTimerCancel.Cancel();
}
private void _checkWinConditions()
{
var traitorsAlive = 0;
var innocentsAlive = 0;
foreach (var playerSession in _playerManager.GetAllPlayers())
{
if (playerSession.AttachedEntity == null
|| !playerSession.AttachedEntity.TryGetComponent(out SpeciesComponent species))
{
continue;
}
if (!species.CurrentDamageState.IsConscious)
{
continue;
}
if (playerSession.ContentData().Mind.HasRole<SuspicionTraitorRole>())
traitorsAlive++;
else
innocentsAlive++;
}
if ((innocentsAlive + traitorsAlive) == 0)
{
_chatManager.DispatchServerAnnouncement("Everybody is dead, it's a stalemate!");
EndRound();
}
else if (traitorsAlive == 0)
{
_chatManager.DispatchServerAnnouncement("The traitors are dead! The innocents win.");
EndRound();
}
else if (innocentsAlive == 0)
{
_chatManager.DispatchServerAnnouncement("The innocents are dead! The traitors win.");
EndRound();
}
}
private void EndRound()
{
_gameTicker.EndRound();
_chatManager.DispatchServerAnnouncement($"Restarting in 10 seconds.");
_checkTimerCancel.Cancel();
Timer.Spawn(TimeSpan.FromSeconds(10), () => _gameTicker.RestartRound());
}
}
}

View File

@@ -104,7 +104,8 @@ namespace Content.Server.GameTicking
_configurationManager.RegisterCVar("game.lobbyenabled", false, CVar.ARCHIVE);
_configurationManager.RegisterCVar("game.lobbyduration", 20, CVar.ARCHIVE);
_configurationManager.RegisterCVar("game.defaultpreset", "Sandbox", CVar.ARCHIVE);
_configurationManager.RegisterCVar("game.defaultpreset", "Suspicion", CVar.ARCHIVE);
_configurationManager.RegisterCVar("game.fallbackpreset", "Sandbox", CVar.ARCHIVE);
_playerManager.PlayerStatusChanged += _handlePlayerStatusChanged;
@@ -181,11 +182,6 @@ namespace Content.Server.GameTicking
SendServerMessage("The round is starting now...");
RunLevel = GameRunLevel.InRound;
var preset = MakeGamePreset();
preset.Start();
List<IPlayerSession> readyPlayers;
if (LobbyEnabled)
{
@@ -196,6 +192,8 @@ namespace Content.Server.GameTicking
readyPlayers = _playersInLobby.Keys.ToList();
}
RunLevel = GameRunLevel.InRound;
// Get the profiles for each player for easier lookup.
var profiles = readyPlayers.ToDictionary(p => p, GetPlayerProfile);
@@ -222,6 +220,18 @@ namespace Content.Server.GameTicking
SpawnPlayer(player, job, false);
}
// Time to start the preset.
var preset = MakeGamePreset();
if (!preset.Start(assignedJobs.Keys.ToList()))
{
SetStartPreset(_configurationManager.GetCVar<string>("game.fallbackpreset"));
var newPreset = MakeGamePreset();
_chatManager.DispatchServerAnnouncement($"Failed to start {preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}...");
if(!newPreset.Start(readyPlayers))
throw new ApplicationException("Fallback preset failed to start!");
}
_roundStartTimeSpan = IoCManager.Resolve<IGameTiming>().RealTime;
_sendStatusToAll();
}
@@ -255,15 +265,16 @@ namespace Content.Server.GameTicking
var listOfPlayerInfo = new List<RoundEndPlayerInfo>();
foreach(var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name))
{
if(ply.AttachedEntity.TryGetComponent<MindComponent>(out var mindComponent)
&& mindComponent.HasMind)
var mind = ply.ContentData().Mind;
if(mind != null)
{
var antag = mind.AllRoles.Any(role => role.Antag);
var playerEndRoundInfo = new RoundEndPlayerInfo()
{
PlayerOOCName = ply.Name,
PlayerICName = mindComponent.Mind.CurrentEntity.Name,
Role = mindComponent.Mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unkown"),
Antag = false
PlayerICName = mind.CurrentEntity.Name,
Role = antag ? mind.AllRoles.First(role => role.Antag).Name : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unkown"),
Antag = antag
};
listOfPlayerInfo.Add(playerEndRoundInfo);
}
@@ -339,6 +350,7 @@ namespace Content.Server.GameTicking
{
"Sandbox" => typeof(PresetSandbox),
"DeathMatch" => typeof(PresetDeathMatch),
"Suspicion" => typeof(PresetSuspicion),
_ => throw new NotSupportedException()
});

View File

@@ -19,6 +19,17 @@ namespace Content.Server.Interfaces.GameObjects.Components.Movement
/// </summary>
float CurrentSprintSpeed { get; }
/// <summary>
/// The movement speed (m/s) of the entity when it pushes off of a solid object in zero gravity.
/// </summary>
float CurrentPushSpeed { get; }
/// <summary>
/// How far an entity can reach (in meters) to grab hold of a solid object in zero gravity.
/// </summary>
float GrabRange { get; }
/// <summary>
/// Is the entity Sprinting (running)?
/// </summary>

View File

@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Players;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.Mobs
@@ -130,6 +133,13 @@ namespace Content.Server.Mobs
_roles.Remove(role);
}
public bool HasRole<T>() where T : Role
{
var t = typeof(T);
return _roles.Any(role => role.GetType() == t);
}
/// <summary>
/// Transfer this mind's control over to a new entity.
/// </summary>

View File

@@ -1,6 +1,9 @@
// Hey look,
// Antag Datums.
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.Utility;
namespace Content.Server.Mobs
{
/// <summary>
@@ -20,6 +23,11 @@ namespace Content.Server.Mobs
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Whether this role should be considered antagonistic or not.
/// </summary>
public abstract bool Antag { get; }
protected Role(Mind mind)
{
Mind = mind;

View File

@@ -11,8 +11,9 @@ namespace Content.Server.Mobs.Roles
public JobPrototype Prototype { get; }
public override string Name { get; }
public override bool Antag => false;
public String StartingGear => Prototype.StartingGear;
public string StartingGear => Prototype.StartingGear;
public Job(Mind mind, JobPrototype jobPrototype) : base(mind)
{
@@ -25,9 +26,7 @@ namespace Content.Server.Mobs.Roles
base.Greet();
var chat = IoCManager.Resolve<IChatManager>();
chat.DispatchServerMessage(
Mind.Session,
String.Format("You're a new {0}. Do your best!", Name));
chat.DispatchServerMessage(Mind.Session, $"You're a new {Name}. Do your best!");
}
}

View File

@@ -0,0 +1,25 @@
using Content.Server.GameObjects;
using Content.Server.Interfaces.Chat;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
namespace Content.Server.Mobs.Roles
{
public class SuspicionInnocentRole : Role
{
public SuspicionInnocentRole(Mind mind) : base(mind)
{
}
public override string Name => "Innocent";
public override bool Antag => false;
public override void Greet()
{
base.Greet();
var chat = IoCManager.Resolve<IChatManager>();
chat.DispatchServerMessage(Mind.Session, "You're an innocent!");
}
}
}

View File

@@ -0,0 +1,25 @@
using Content.Server.GameObjects;
using Content.Server.Interfaces.Chat;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
namespace Content.Server.Mobs.Roles
{
public sealed class SuspicionTraitorRole : Role
{
public SuspicionTraitorRole(Mind mind) : base(mind)
{
}
public override string Name => "Traitor";
public override bool Antag => true;
public override void Greet()
{
base.Greet();
var chat = IoCManager.Resolve<IChatManager>();
chat.DispatchServerMessage(Mind.Session, "You're a traitor!");
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.Interfaces.Chat;
using Robust.Shared.IoC;
namespace Content.Server.Mobs.Roles
{
public sealed class Traitor : Role
{
public Traitor(Mind mind) : base(mind)
{
}
public override string Name => "Traitor";
public override void Greet()
{
base.Greet();
var chat = IoCManager.Resolve<IChatManager>();
chat.DispatchServerMessage(
Mind.Session,
"You're a traitor. Go fuck something up. Or something. I don't care to be honest.");
}
}
}

View File

@@ -65,6 +65,14 @@ namespace Content.Server.Throw
var spd = a / (1f / timing.TickRate); // acceleration is applied in 1 tick instead of 1 second, scale appropriately
physComp.LinearVelocity = angle.ToVec() * spd;
if (throwSourceEnt != null)
{
var p = throwSourceEnt.GetComponent<PhysicsComponent>();
var playerAccel = 5 * throwForce / (float) Math.Max(0.001, p.Mass);
p.LinearVelocity = Angle.FromDegrees(angle.Degrees + 180).ToVec()
* playerAccel / (1f / timing.TickRate);
}
}
}
}