Traitor Deathmatch (#2719)
* Traitor Deathmatch: Add the preset [Rebase onto factors line] * Traitor Deathmatch: Being a human head or other not-quite-fully-animate object counts as dead. [ROFL] * Traitor Deathmatch: Add redemption machine (but it doesn't work) [ROFL] * Traitor Deathmatch: Map in redemption machines [ROFL] * Traitor Deathmatch: Make the rounds end properly [ROFL] * Traitor Deathmatch: PDA redemption works [ROFL] * Traitor Deathmatch: New redemption machine sprite ( @Tomeno ) * Traitor Deathmatch: Get rid of redundant using * Traitor Deathmatch: Change scoring, prevent redeeming your own PDA, fix bug where PDAs are not tagged with their owner names * Traitor Deathmatch: Better messages, change cvar name, don't count ghosts for spawnpoint selection
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects.Components;
|
using Robust.Shared.Interfaces.GameObjects.Components;
|
||||||
@@ -38,6 +39,23 @@ namespace Content.Server.Atmos
|
|||||||
return !Equals(air = coordinates.GetTileAir(entityManager)!, default);
|
return !Equals(air = coordinates.GetTileAir(entityManager)!, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsTileAirProbablySafe(this EntityCoordinates coordinates)
|
||||||
|
{
|
||||||
|
// Note that oxygen mix isn't checked, but survival boxes make that not necessary.
|
||||||
|
var air = coordinates.GetTileAir();
|
||||||
|
if (air == null)
|
||||||
|
return false;
|
||||||
|
if (air.Pressure <= Atmospherics.WarningLowPressure)
|
||||||
|
return false;
|
||||||
|
if (air.Pressure >= Atmospherics.WarningHighPressure)
|
||||||
|
return false;
|
||||||
|
if (air.Temperature <= 260)
|
||||||
|
return false;
|
||||||
|
if (air.Temperature >= 360)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static TileAtmosphere GetTileAtmosphere(this Vector2i indices, GridId gridId)
|
public static TileAtmosphere GetTileAtmosphere(this Vector2i indices, GridId gridId)
|
||||||
{
|
{
|
||||||
var gridAtmos = EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(gridId);
|
var gridAtmos = EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(gridId);
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ namespace Content.Server.GameObjects.Components.PDA
|
|||||||
[ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntity == null;
|
[ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntity == null;
|
||||||
[ViewVariables] public bool PenSlotEmpty => _penSlot.ContainedEntity == null;
|
[ViewVariables] public bool PenSlotEmpty => _penSlot.ContainedEntity == null;
|
||||||
|
|
||||||
[ViewVariables] private UplinkAccount? _syndicateUplinkAccount;
|
private UplinkAccount? _syndicateUplinkAccount;
|
||||||
|
|
||||||
|
[ViewVariables] public UplinkAccount? SyndicateUplinkAccount => _syndicateUplinkAccount;
|
||||||
|
|
||||||
[ViewVariables] private readonly PdaAccessSet _accessSet;
|
[ViewVariables] private readonly PdaAccessSet _accessSet;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
|
using Content.Server.GameObjects.Components.PDA;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.Mobs;
|
||||||
|
using Content.Server.Mobs.Roles;
|
||||||
|
using Content.Server.Mobs.Roles.Suspicion;
|
||||||
|
using Content.Server.Interfaces.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.TraitorDeathMatch
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class TraitorDeathMatchRedemptionComponent : Component, IInteractUsing
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => "TraitorDeathMatchRedemption";
|
||||||
|
|
||||||
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!eventArgs.User.TryGetComponent<InventoryComponent>(out var userInv))
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"USER PDA OUT OF RANGE (0039)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventArgs.User.TryGetComponent<MindComponent>(out var userMindComponent))
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"AUTHENTICATION FAILED (0045)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userMind = userMindComponent.Mind;
|
||||||
|
if (userMind == null)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"AUTHENTICATION FAILED (0052)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventArgs.Using.TryGetComponent<PDAComponent>(out var victimPDA))
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"GIVEN PDA IS NOT A PDA (0058)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventArgs.Using.TryGetComponent<TraitorDeathMatchReliableOwnerTagComponent>(out var victimPDAOwner))
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"GIVEN PDA HAS NO OWNER (0064)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (victimPDAOwner.UserId == userMind.UserId)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"GIVEN PDA OWNED BY USER (0070)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPDAEntity = userInv.GetSlotItem(EquipmentSlotDefines.Slots.IDCARD)?.Owner;
|
||||||
|
PDAComponent? userPDA = null;
|
||||||
|
|
||||||
|
if (userPDAEntity != null)
|
||||||
|
if (userPDAEntity.TryGetComponent<PDAComponent>(out var userPDAComponent))
|
||||||
|
userPDA = userPDAComponent;
|
||||||
|
|
||||||
|
if (userPDA == null)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"NO USER PDA IN IDCARD POCKET (0083)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have finally determined both PDA components. FINALLY.
|
||||||
|
|
||||||
|
var userAccount = userPDA.SyndicateUplinkAccount;
|
||||||
|
var victimAccount = victimPDA.SyndicateUplinkAccount;
|
||||||
|
|
||||||
|
if (userAccount == null)
|
||||||
|
{
|
||||||
|
// This shouldn't even BE POSSIBLE in the actual mode this is meant for.
|
||||||
|
// Advanced Syndicate anti-tampering technology.
|
||||||
|
// Owner.PopupMessage(eventArgs.User, Loc.GetString("Tampering detected."));
|
||||||
|
// if (eventArgs.User.TryGetComponent<DamagableComponent>(out var userDamagable))
|
||||||
|
// userDamagable.ChangeDamage(DamageType.Shock, 9001, true, null);
|
||||||
|
// ...So apparently, "it probably shouldn't kill people for a mistake".
|
||||||
|
// :(
|
||||||
|
// Give boring error message instead.
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"USER PDA HAS NO UPLINK ACCOUNT (0102)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (victimAccount == null)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine buzzes, and displays: \"GIVEN PDA HAS NO UPLINK ACCOUNT (0108)\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 is the per-PDA bonus amount.
|
||||||
|
var transferAmount = victimAccount.Balance + 4;
|
||||||
|
victimAccount.ModifyAccountBalance(0);
|
||||||
|
userAccount.ModifyAccountBalance(userAccount.Balance + transferAmount);
|
||||||
|
|
||||||
|
victimPDA.Owner.Delete();
|
||||||
|
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("The machine plays a happy little tune, and displays: \"SUCCESS: {0} TC TRANSFERRED\"", transferAmount));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.TraitorDeathMatch
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class TraitorDeathMatchReliableOwnerTagComponent : Component
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => "TraitorDeathMatchReliableOwnerTag";
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public NetUserId? UserId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameTicking.GameRules;
|
||||||
|
using Content.Server.Interfaces.GameTicking;
|
||||||
|
using Content.Server.Interfaces.Chat;
|
||||||
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
|
using Content.Server.GameObjects.Components.PDA;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Server.GameObjects.Components.Markers;
|
||||||
|
using Content.Server.GameObjects.Components.TraitorDeathMatch;
|
||||||
|
using Content.Server.Mobs;
|
||||||
|
using Content.Server.Mobs.Roles.Traitor;
|
||||||
|
using Content.Server.Players;
|
||||||
|
using Content.Server.Atmos;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Content.Shared.GameObjects.Components.PDA;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
|
using Content.Shared;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Server.Interfaces.Console;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Configuration;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.GamePresets
|
||||||
|
{
|
||||||
|
public sealed class PresetTraitorDeathMatch : GamePreset
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
|
|
||||||
|
public string PDAPrototypeName => "CaptainPDA";
|
||||||
|
public string BeltPrototypeName => "ClothingBeltJanitorFilled";
|
||||||
|
public string BackpackPrototypeName => "ClothingBackpackFilled";
|
||||||
|
|
||||||
|
private RuleMaxTimeRestart _restarter;
|
||||||
|
private bool _safeToEndRound = false;
|
||||||
|
|
||||||
|
private Dictionary<UplinkAccount, string> _allOriginalNames = new();
|
||||||
|
|
||||||
|
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
|
||||||
|
{
|
||||||
|
_gameTicker.AddGameRule<RuleTraitorDeathMatch>();
|
||||||
|
_restarter = _gameTicker.AddGameRule<RuleMaxTimeRestart>();
|
||||||
|
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
|
||||||
|
_restarter.RestartTimer();
|
||||||
|
_safeToEndRound = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSpawnPlayerCompleted(IPlayerSession session, IEntity mob, bool lateJoin)
|
||||||
|
{
|
||||||
|
int startingBalance = _cfg.GetCVar(CCVars.TraitorDeathMatchStartingBalance);
|
||||||
|
|
||||||
|
// Yup, they're a traitor
|
||||||
|
var mind = session.Data.ContentData()?.Mind;
|
||||||
|
var traitorRole = new TraitorRole(mind);
|
||||||
|
if (mind == null)
|
||||||
|
{
|
||||||
|
Logger.ErrorS("preset", "Failed getting mind for TDM player.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mind.AddRole(traitorRole);
|
||||||
|
|
||||||
|
// Delete anything that may contain "dangerous" role-specific items.
|
||||||
|
// (This includes the PDA, as everybody gets the captain PDA in this mode for true-all-access reasons.)
|
||||||
|
var inventory = mind.OwnedEntity.GetComponent<InventoryComponent>();
|
||||||
|
EquipmentSlotDefines.Slots[] victimSlots = new EquipmentSlotDefines.Slots[] {EquipmentSlotDefines.Slots.IDCARD, EquipmentSlotDefines.Slots.BELT, EquipmentSlotDefines.Slots.BACKPACK};
|
||||||
|
foreach (var slot in victimSlots)
|
||||||
|
if (inventory.TryGetSlotItem(slot, out ItemComponent vItem))
|
||||||
|
vItem.Owner.Delete();
|
||||||
|
|
||||||
|
// Replace their items:
|
||||||
|
|
||||||
|
// pda
|
||||||
|
var newPDA = _entityManager.SpawnEntity(PDAPrototypeName, mind.OwnedEntity.Transform.Coordinates);
|
||||||
|
inventory.Equip(EquipmentSlotDefines.Slots.IDCARD, newPDA.GetComponent<ItemComponent>());
|
||||||
|
|
||||||
|
// belt
|
||||||
|
var newTmp = _entityManager.SpawnEntity(BeltPrototypeName, mind.OwnedEntity.Transform.Coordinates);
|
||||||
|
inventory.Equip(EquipmentSlotDefines.Slots.BELT, newTmp.GetComponent<ItemComponent>());
|
||||||
|
|
||||||
|
// backpack
|
||||||
|
newTmp = _entityManager.SpawnEntity(BackpackPrototypeName, mind.OwnedEntity.Transform.Coordinates);
|
||||||
|
inventory.Equip(EquipmentSlotDefines.Slots.BACKPACK, newTmp.GetComponent<ItemComponent>());
|
||||||
|
|
||||||
|
// Like normal traitors, they need access to a traitor account.
|
||||||
|
var uplinkAccount = new UplinkAccount(mind.OwnedEntity.Uid, startingBalance);
|
||||||
|
var pdaComponent = newPDA.GetComponent<PDAComponent>();
|
||||||
|
pdaComponent.InitUplinkAccount(uplinkAccount);
|
||||||
|
_allOriginalNames[uplinkAccount] = mind.OwnedEntity.Name;
|
||||||
|
|
||||||
|
// The PDA needs to be marked with the correct owner.
|
||||||
|
pdaComponent.SetPDAOwner(mind.OwnedEntity.Name);
|
||||||
|
newPDA.AddComponent<TraitorDeathMatchReliableOwnerTagComponent>().UserId = mind.UserId;
|
||||||
|
|
||||||
|
// Finally, it would be preferrable if they spawned as far away from other players as reasonably possible.
|
||||||
|
if (FindAnyIsolatedSpawnLocation(mind, out var bestTarget))
|
||||||
|
{
|
||||||
|
mind.OwnedEntity.Transform.Coordinates = bestTarget;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The station is too drained of air to safely continue.
|
||||||
|
if (_safeToEndRound)
|
||||||
|
{
|
||||||
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("The station is too unsafe to continue. You have one minute."));
|
||||||
|
_restarter.RoundMaxTime = TimeSpan.FromMinutes(1);
|
||||||
|
_restarter.RestartTimer();
|
||||||
|
_safeToEndRound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It would be nice if this function were moved to some generic helpers class.
|
||||||
|
private bool FindAnyIsolatedSpawnLocation(Mind ignoreMe, out EntityCoordinates bestTarget)
|
||||||
|
{
|
||||||
|
// Collate people to avoid...
|
||||||
|
var existingPlayerPoints = new List<EntityCoordinates>();
|
||||||
|
foreach (var player in _playerManager.GetAllPlayers())
|
||||||
|
{
|
||||||
|
var avoidMeMind = player.Data.ContentData()?.Mind;
|
||||||
|
if ((avoidMeMind == null) || (avoidMeMind == ignoreMe))
|
||||||
|
continue;
|
||||||
|
var avoidMeEntity = avoidMeMind.OwnedEntity;
|
||||||
|
if (avoidMeEntity == null)
|
||||||
|
continue;
|
||||||
|
if (avoidMeEntity.TryGetComponent(out IMobStateComponent mobState))
|
||||||
|
{
|
||||||
|
// Does have mob state component; if critical or dead, they don't really matter for spawn checks
|
||||||
|
if (mobState.IsCritical() || mobState.IsDead())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Doesn't have mob state component. Assume something interesting is going on and don't count this as someone to avoid.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
existingPlayerPoints.Add(avoidMeEntity.Transform.Coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over each possible spawn point, comparing to the existing player points.
|
||||||
|
// On failure, the returned target is the location that we're already at.
|
||||||
|
var bestTargetDistanceFromNearest = -1.0f;
|
||||||
|
// Need the random shuffle or it stuffs the first person into Atmospherics pretty reliably
|
||||||
|
var ents = new List<IEntity>(_entityManager.GetEntities(new TypeEntityQuery(typeof(SpawnPointComponent))));
|
||||||
|
_robustRandom.Shuffle(ents);
|
||||||
|
var foundATarget = false;
|
||||||
|
bestTarget = EntityCoordinates.Invalid;
|
||||||
|
foreach (var entity in ents)
|
||||||
|
{
|
||||||
|
if (!entity.Transform.Coordinates.IsTileAirProbablySafe())
|
||||||
|
continue;
|
||||||
|
var distanceFromNearest = float.PositiveInfinity;
|
||||||
|
foreach (var existing in existingPlayerPoints)
|
||||||
|
{
|
||||||
|
if (entity.Transform.Coordinates.TryDistance(_entityManager, existing, out var dist))
|
||||||
|
distanceFromNearest = Math.Min(distanceFromNearest, dist);
|
||||||
|
}
|
||||||
|
if (bestTargetDistanceFromNearest < distanceFromNearest)
|
||||||
|
{
|
||||||
|
bestTarget = entity.Transform.Coordinates;
|
||||||
|
bestTargetDistanceFromNearest = distanceFromNearest;
|
||||||
|
foundATarget = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundATarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnGhostAttempt(Mind mind, bool canReturnGlobal)
|
||||||
|
{
|
||||||
|
var entity = mind.OwnedEntity;
|
||||||
|
if ((entity != null) && (entity.TryGetComponent(out IMobStateComponent mobState)))
|
||||||
|
{
|
||||||
|
if (mobState.IsCritical())
|
||||||
|
{
|
||||||
|
// TODO: This is copy/pasted from ghost code. Really, IDamagableComponent needs a method to reliably kill the target.
|
||||||
|
if (entity.TryGetComponent(out IDamageableComponent damageable))
|
||||||
|
{
|
||||||
|
//todo: what if they dont breathe lol
|
||||||
|
damageable.ChangeDamage(DamageType.Asphyxiation, 100, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!mobState.IsDead())
|
||||||
|
{
|
||||||
|
if (entity.HasComponent<HandsComponent>())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var session = mind.Session;
|
||||||
|
if (session == null)
|
||||||
|
return false;
|
||||||
|
_gameTicker.Respawn(session);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetRoundEndDescription()
|
||||||
|
{
|
||||||
|
var lines = new List<string>();
|
||||||
|
lines.Add("The PDAs recovered afterwards...");
|
||||||
|
foreach (var entity in _entityManager.GetEntities(new TypeEntityQuery(typeof(PDAComponent))))
|
||||||
|
{
|
||||||
|
var pda = entity.GetComponent<PDAComponent>();
|
||||||
|
var uplink = pda.SyndicateUplinkAccount;
|
||||||
|
if ((uplink != null) && _allOriginalNames.ContainsKey(uplink))
|
||||||
|
{
|
||||||
|
lines.Add(Loc.GetString("{0}'s PDA, with {1} TC", _allOriginalNames[uplink], uplink.Balance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string.Join('\n', lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ModeTitle => "Traitor Deathmatch";
|
||||||
|
public override string Description => Loc.GetString("Everyone's a traitor. Everyone wants each other dead.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Content.Server.Interfaces.Chat;
|
||||||
|
using Content.Server.Mobs.Roles.Traitor;
|
||||||
|
using Content.Server.Players;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.GameRules
|
||||||
|
{
|
||||||
|
public class RuleTraitorDeathMatch : GameRule
|
||||||
|
{
|
||||||
|
// This class only exists so that the game rule is available for the conditional spawner.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -491,6 +491,8 @@ namespace Content.Server.GameTicking
|
|||||||
"deathmatch" => typeof(PresetDeathMatch),
|
"deathmatch" => typeof(PresetDeathMatch),
|
||||||
"suspicion" => typeof(PresetSuspicion),
|
"suspicion" => typeof(PresetSuspicion),
|
||||||
"traitor" => typeof(PresetTraitor),
|
"traitor" => typeof(PresetTraitor),
|
||||||
|
"traitordm" => typeof(PresetTraitorDeathMatch),
|
||||||
|
"traitordeathmatch" => typeof(PresetTraitorDeathMatch),
|
||||||
_ => default
|
_ => default
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,13 @@ namespace Content.Shared
|
|||||||
public static readonly CVarDef<int> TraitorMaxPicks =
|
public static readonly CVarDef<int> TraitorMaxPicks =
|
||||||
CVarDef.Create("traitor.max_picks", 20);
|
CVarDef.Create("traitor.max_picks", 20);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TraitorDeathMatch
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static readonly CVarDef<int> TraitorDeathMatchStartingBalance =
|
||||||
|
CVarDef.Create("traitordm.starting_balance", 20);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Console
|
* Console
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Shared.GameObjects.Components.PDA
|
namespace Content.Shared.GameObjects.Components.PDA
|
||||||
{
|
{
|
||||||
@@ -129,12 +130,14 @@ namespace Content.Shared.GameObjects.Components.PDA
|
|||||||
{
|
{
|
||||||
public event Action<UplinkAccount> BalanceChanged;
|
public event Action<UplinkAccount> BalanceChanged;
|
||||||
public EntityUid AccountHolder;
|
public EntityUid AccountHolder;
|
||||||
public int Balance { get; private set; }
|
private int _balance;
|
||||||
|
[ViewVariables]
|
||||||
|
public int Balance => _balance;
|
||||||
|
|
||||||
public UplinkAccount(EntityUid uid, int startingBalance)
|
public UplinkAccount(EntityUid uid, int startingBalance)
|
||||||
{
|
{
|
||||||
AccountHolder = uid;
|
AccountHolder = uid;
|
||||||
Balance = startingBalance;
|
_balance = startingBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ModifyAccountBalance(int newBalance)
|
public bool ModifyAccountBalance(int newBalance)
|
||||||
@@ -143,7 +146,7 @@ namespace Content.Shared.GameObjects.Components.PDA
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Balance = newBalance;
|
_balance = newBalance;
|
||||||
BalanceChanged?.Invoke(this);
|
BalanceChanged?.Invoke(this);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|||||||
@@ -44662,4 +44662,32 @@ entities:
|
|||||||
components:
|
components:
|
||||||
- parent: 1045
|
- parent: 1045
|
||||||
type: Transform
|
type: Transform
|
||||||
|
- uid: 4285
|
||||||
|
type: TraitorDMRedemptionMachineSpawner
|
||||||
|
components:
|
||||||
|
- parent: 855
|
||||||
|
pos: -18.5,25.5
|
||||||
|
rot: -1.5707963267948966 rad
|
||||||
|
type: Transform
|
||||||
|
- uid: 4286
|
||||||
|
type: TraitorDMRedemptionMachineSpawner
|
||||||
|
components:
|
||||||
|
- parent: 855
|
||||||
|
pos: 10.5,13.5
|
||||||
|
rot: -1.5707963267948966 rad
|
||||||
|
type: Transform
|
||||||
|
- uid: 4287
|
||||||
|
type: TraitorDMRedemptionMachineSpawner
|
||||||
|
components:
|
||||||
|
- parent: 855
|
||||||
|
pos: -9.5,-25.5
|
||||||
|
rot: -1.5707963267948966 rad
|
||||||
|
type: Transform
|
||||||
|
- uid: 4288
|
||||||
|
type: TraitorDMRedemptionMachineSpawner
|
||||||
|
components:
|
||||||
|
- parent: 855
|
||||||
|
pos: 27.5,-1.5
|
||||||
|
rot: -1.5707963267948966 rad
|
||||||
|
type: Transform
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -344,3 +344,22 @@
|
|||||||
chance: 0.95
|
chance: 0.95
|
||||||
gameRules:
|
gameRules:
|
||||||
- RuleSuspicion
|
- RuleSuspicion
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Traitor DeathMatch PDA Redemption Machine Spawner
|
||||||
|
id: TraitorDMRedemptionMachineSpawner
|
||||||
|
parent: BaseConditionalSpawner
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
visible: false
|
||||||
|
sprite: Objects/Misc/traitordm.rsi
|
||||||
|
state: redemption
|
||||||
|
|
||||||
|
- type: ConditionalSpawner
|
||||||
|
prototypes:
|
||||||
|
- TraitorDMRedemptionMachine
|
||||||
|
chance: 1.0
|
||||||
|
gameRules:
|
||||||
|
- RuleTraitorDeathMatch
|
||||||
|
|
||||||
|
|||||||
26
Resources/Prototypes/Entities/traitordm.yml
Normal file
26
Resources/Prototypes/Entities/traitordm.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
- type: entity
|
||||||
|
id: TraitorDMRedemptionMachine
|
||||||
|
name: traitor deathmatch pda redemption machine
|
||||||
|
description: Put someone else's PDA into this to get telecrystals.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- sprite: Objects/Misc/traitordm.rsi
|
||||||
|
state: redemption
|
||||||
|
- sprite: Objects/Misc/traitordm.rsi
|
||||||
|
state: redemption-unshaded
|
||||||
|
shader: unshaded
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Physics
|
||||||
|
anchored: true
|
||||||
|
mass: 1
|
||||||
|
shapes:
|
||||||
|
- !type:PhysShapeAabb
|
||||||
|
bounds: "-0.25,-0.25,0.25,0.25"
|
||||||
|
layer:
|
||||||
|
- Clickable
|
||||||
|
- type: TraitorDeathMatchRedemption
|
||||||
|
placement:
|
||||||
|
mode: AlignTileAny
|
||||||
|
|
||||||
10
Resources/Textures/Objects/Misc/traitordm.rsi/meta.json
Normal file
10
Resources/Textures/Objects/Misc/traitordm.rsi/meta.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version":1,
|
||||||
|
"size":{"x":32,"y":32},
|
||||||
|
"states":[
|
||||||
|
{"name":"redemption","directions":1,"delays":[[1.0]]},
|
||||||
|
{"name":"redemption-unshaded","directions":1,"delays":[[1.0]]}
|
||||||
|
],
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Edit by Tomeno using small parts of autolathe from https://github.com/tgstation/tgstation/blob/acb091f9744e9ab7d5a27fb32dd0c03bd019f58c/icons/obj/stationobjs.dmi (may be an earlier version) and tcboss from https://github.com/tgstation/tgstation/blob/e32357e6b0ec0f0a1821d2773f0d1e1d6ce7d494/icons/obj/computer.dmi (may be an earlier version)"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 795 B |
BIN
Resources/Textures/Objects/Misc/traitordm.rsi/redemption.png
Normal file
BIN
Resources/Textures/Objects/Misc/traitordm.rsi/redemption.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 B |
Reference in New Issue
Block a user