Adds twenty-one new smites, moves the explosion smite to the verb category. (#8456)
* Adds seven new smites, moves the explosion smite to the verb category. * adds even more smites. * Even more smites, some messages for specific smites. * Adds even more smites. * Removes some junk, adds a smite that angers the pointing arrows. * get rid of dumb component. * Remove mistake from verb menu presentation. * How did that happen? * whoops * c * e * fuck * Loading... * removes the BoM go away * adds the funny kill sign. Fixes ghost smite. * Move systems around. * Adjust organ vomit. * Adds a smite that turns people into an instrument, and one that removes their gravity. * oops * typo Co-authored-by: Veritius <veritiusgaming@gmail.com>
This commit is contained in:
126
Content.Server/Administration/Systems/AdminSystem.cs
Normal file
126
Content.Server/Administration/Systems/AdminSystem.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Events;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Administration.Systems
|
||||
{
|
||||
public sealed class AdminSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
private readonly Dictionary<NetUserId, PlayerInfo> _playerList = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_adminManager.OnPermsChanged += OnAdminPermsChanged;
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
|
||||
}
|
||||
|
||||
public void UpdatePlayerList(IPlayerSession player)
|
||||
{
|
||||
_playerList[player.UserId] = GetPlayerInfo(player);
|
||||
|
||||
var playerInfoChangedEvent = new PlayerInfoChangedEvent
|
||||
{
|
||||
PlayerInfo = _playerList[player.UserId]
|
||||
};
|
||||
|
||||
foreach (var admin in _adminManager.ActiveAdmins)
|
||||
{
|
||||
RaiseNetworkEvent(playerInfoChangedEvent, admin.ConnectedClient);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRoleEvent(RoleEvent ev)
|
||||
{
|
||||
if (!ev.Role.Antagonist || ev.Role.Mind.Session == null)
|
||||
return;
|
||||
|
||||
UpdatePlayerList(ev.Role.Mind.Session);
|
||||
}
|
||||
|
||||
private void OnAdminPermsChanged(AdminPermsChangedEventArgs obj)
|
||||
{
|
||||
if(!obj.IsAdmin)
|
||||
{
|
||||
RaiseNetworkEvent(new FullPlayerListEvent(), obj.Player.ConnectedClient);
|
||||
return;
|
||||
}
|
||||
|
||||
SendFullPlayerList(obj.Player);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(PlayerDetachedEvent ev)
|
||||
{
|
||||
// If disconnected then the player won't have a connected entity to get character name from.
|
||||
// The disconnected state gets sent by OnPlayerStatusChanged.
|
||||
if(ev.Player.Status == SessionStatus.Disconnected) return;
|
||||
|
||||
UpdatePlayerList(ev.Player);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(PlayerAttachedEvent ev)
|
||||
{
|
||||
if(ev.Player.Status == SessionStatus.Disconnected) return;
|
||||
|
||||
UpdatePlayerList(ev.Player);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
_adminManager.OnPermsChanged -= OnAdminPermsChanged;
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
UpdatePlayerList(e.Session);
|
||||
}
|
||||
|
||||
private void SendFullPlayerList(IPlayerSession playerSession)
|
||||
{
|
||||
var ev = new FullPlayerListEvent();
|
||||
|
||||
ev.PlayersInfo = _playerList.Values.ToList();
|
||||
|
||||
RaiseNetworkEvent(ev, playerSession.ConnectedClient);
|
||||
}
|
||||
|
||||
private PlayerInfo GetPlayerInfo(IPlayerSession session)
|
||||
{
|
||||
var name = session.Name;
|
||||
var username = string.Empty;
|
||||
|
||||
if (session.AttachedEntity != null)
|
||||
username = EntityManager.GetComponent<MetaDataComponent>(session.AttachedEntity.Value).EntityName;
|
||||
|
||||
var mind = session.ContentData()?.Mind;
|
||||
|
||||
var job = mind?.AllRoles.FirstOrDefault(role => role is Job);
|
||||
var startingRole = job != null ? CultureInfo.CurrentCulture.TextInfo.ToTitleCase(job.Name) : string.Empty;
|
||||
|
||||
var antag = mind?.AllRoles.Any(r => r.Antagonist) ?? false;
|
||||
|
||||
var connected = session.Status is SessionStatus.Connected or SessionStatus.InGame;
|
||||
|
||||
return new PlayerInfo(name, username, startingRole, antag, session.AttachedEntity.GetValueOrDefault(), session.UserId,
|
||||
connected);
|
||||
}
|
||||
}
|
||||
}
|
||||
534
Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
Normal file
534
Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
Normal file
@@ -0,0 +1,534 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Administration.Components;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Clothing.Components;
|
||||
using Content.Server.Damage.Systems;
|
||||
using Content.Server.Disease;
|
||||
using Content.Server.Disease.Components;
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.GhostKick;
|
||||
using Content.Server.Interaction.Components;
|
||||
using Content.Server.Medical;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Server.Pointing.Components;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Tabletop;
|
||||
using Content.Server.Tabletop.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Disease;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Tabletop.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.Administration.Systems;
|
||||
|
||||
public sealed partial class AdminVerbSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly GhostKickManager _ghostKickManager = default!;
|
||||
[Dependency] private readonly PolymorphableSystem _polymorphableSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
|
||||
[Dependency] private readonly CreamPieSystem _creamPieSystem = default!;
|
||||
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
|
||||
[Dependency] private readonly TabletopSystem _tabletopSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
|
||||
[Dependency] private readonly GodmodeSystem _godmodeSystem = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly VomitSystem _vomitSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
// All smite verbs have names so invokeverb works.
|
||||
private void AddSmiteVerbs(GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<ActorComponent?>(args.User, out var actor))
|
||||
return;
|
||||
|
||||
var player = actor.PlayerSession;
|
||||
|
||||
if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun))
|
||||
return;
|
||||
|
||||
Verb explode = new()
|
||||
{
|
||||
Text = "Explode",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png",
|
||||
Act = () =>
|
||||
{
|
||||
var coords = Transform(args.Target).MapPosition;
|
||||
Timer.Spawn(_gameTiming.TickPeriod,
|
||||
() => _explosionSystem.QueueExplosion(coords, ExplosionSystem.DefaultExplosionPrototypeId,
|
||||
4, 1, 2, maxTileBreak: 0), // it gibs, damage doesn't need to be high.
|
||||
CancellationToken.None);
|
||||
|
||||
if (TryComp(args.Target, out SharedBodyComponent? body))
|
||||
{
|
||||
body.Gib();
|
||||
}
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Explode them.",
|
||||
};
|
||||
args.Verbs.Add(explode);
|
||||
|
||||
Verb chess = new()
|
||||
{
|
||||
Text = "Chess Dimension",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Fun/Tabletop/chessboard.rsi/chessboard.png",
|
||||
Act = () =>
|
||||
{
|
||||
_godmodeSystem.EnableGodmode(args.Target); // So they don't suffocate.
|
||||
EnsureComp<TabletopDraggableComponent>(args.Target);
|
||||
RemComp<PhysicsComponent>(args.Target); // So they can be dragged around.
|
||||
var xform = Transform(args.Target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-chess-self"), args.Target, Filter.Entities(args.Target));
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("admin-smite-chess-others", ("name", args.Target)), xform.Coordinates, Filter.Pvs(args.Target).RemoveWhereAttachedEntity(x => x == args.Target));
|
||||
var board = Spawn("ChessBoard", xform.Coordinates);
|
||||
var session = _tabletopSystem.EnsureSession(Comp<TabletopGameComponent>(board));
|
||||
xform.Coordinates = EntityCoordinates.FromMap(_mapManager, session.Position);
|
||||
xform.WorldRotation = Angle.Zero;
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Banishment to the Chess Dimension.",
|
||||
};
|
||||
args.Verbs.Add(chess);
|
||||
|
||||
if (TryComp<FlammableComponent>(args.Target, out var flammable))
|
||||
{
|
||||
Verb flames = new()
|
||||
{
|
||||
Text = "Set Alight",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/Alerts/Fire/fire.png",
|
||||
Act = () =>
|
||||
{
|
||||
// Fuck you. Burn Forever.
|
||||
flammable.FireStacks = 99999.9f;
|
||||
_flammableSystem.Ignite(args.Target);
|
||||
var xform = Transform(args.Target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-set-alight-self"), args.Target, Filter.Entities(args.Target));
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("admin-smite-set-alight-others", ("name", args.Target)), xform.Coordinates, Filter.Pvs(args.Target).RemoveWhereAttachedEntity(x => x == args.Target));
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Makes them burn.",
|
||||
};
|
||||
args.Verbs.Add(flames);
|
||||
}
|
||||
|
||||
Verb monkey = new()
|
||||
{
|
||||
Text = "Monkeyify",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Mobs/Animals/monkey.rsi/dead.png",
|
||||
Act = () =>
|
||||
{
|
||||
_polymorphableSystem.PolymorphEntity(args.Target, "AdminMonkeySmite");
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Monkey mode.",
|
||||
};
|
||||
args.Verbs.Add(monkey);
|
||||
|
||||
if (TryComp<DiseaseCarrierComponent>(args.Target, out var carrier))
|
||||
{
|
||||
Verb lungCancer = new()
|
||||
{
|
||||
Text = "Lung Cancer",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/lung-l.png",
|
||||
Act = () =>
|
||||
{
|
||||
_diseaseSystem.TryInfect(carrier, _prototypeManager.Index<DiseasePrototype>("StageIIIALungCancer"),
|
||||
1.0f, true);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Stage IIIA Lung Cancer, for when they really like the hit show Breaking Bad.",
|
||||
};
|
||||
args.Verbs.Add(lungCancer);
|
||||
}
|
||||
|
||||
if (TryComp<DamageableComponent>(args.Target, out var damageable) &&
|
||||
TryComp<MobStateComponent>(args.Target, out var mobState))
|
||||
{
|
||||
Verb hardElectrocute = new()
|
||||
{
|
||||
Text = "Electrocute",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Clothing/Hands/Gloves/Color/yellow.rsi/icon.png",
|
||||
Act = () =>
|
||||
{
|
||||
int damageToDeal;
|
||||
var critState = mobState._highestToLowestStates.Where(x => x.Value.IsCritical()).FirstOrNull();
|
||||
if (critState is null)
|
||||
{
|
||||
// We can't crit them so try killing them.
|
||||
var deadState = mobState._highestToLowestStates.Where(x => x.Value.IsDead()).FirstOrNull();
|
||||
if (deadState is null)
|
||||
return; // whelp.
|
||||
|
||||
damageToDeal = deadState.Value.Key - (int) damageable.TotalDamage;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageToDeal = critState.Value.Key - (int) damageable.TotalDamage;
|
||||
}
|
||||
|
||||
if (damageToDeal <= 0)
|
||||
damageToDeal = 100; // murder time.
|
||||
|
||||
if (_inventorySystem.TryGetSlots(args.Target, out var slotDefinitions))
|
||||
{
|
||||
foreach (var slot in slotDefinitions)
|
||||
{
|
||||
if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var slotEnt))
|
||||
continue;
|
||||
|
||||
RemComp<InsulatedComponent>(slotEnt.Value); // Fry the gloves.
|
||||
}
|
||||
}
|
||||
|
||||
_electrocutionSystem.TryDoElectrocution(args.Target, null, damageToDeal,
|
||||
TimeSpan.FromSeconds(30), true);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Electrocutes them, rendering anything they were wearing useless.",
|
||||
};
|
||||
args.Verbs.Add(hardElectrocute);
|
||||
}
|
||||
|
||||
if (TryComp<CreamPiedComponent>(args.Target, out var creamPied))
|
||||
{
|
||||
Verb creamPie = new()
|
||||
{
|
||||
Text = "Creampie",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Consumable/Food/Baked/pie.rsi/plain-slice.png",
|
||||
Act = () =>
|
||||
{
|
||||
_creamPieSystem.SetCreamPied(args.Target, creamPied, true);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "A cream pie, condensed into a button.",
|
||||
};
|
||||
args.Verbs.Add(creamPie);
|
||||
}
|
||||
|
||||
if (TryComp<BloodstreamComponent>(args.Target, out var bloodstream))
|
||||
{
|
||||
Verb bloodRemoval = new()
|
||||
{
|
||||
Text = "Remove blood",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Fluids/tomato_splat.rsi/puddle-1.png",
|
||||
Act = () =>
|
||||
{
|
||||
_bloodstreamSystem.SpillAllSolutions(args.Target, bloodstream);
|
||||
var xform = Transform(args.Target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-remove-blood-self"), args.Target, Filter.Entities(args.Target));
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("admin-smite-remove-blood-others", ("name", args.Target)), xform.Coordinates, Filter.Pvs(args.Target).RemoveWhereAttachedEntity(x => x == args.Target));
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Removes their blood. All of it.",
|
||||
};
|
||||
args.Verbs.Add(bloodRemoval);
|
||||
}
|
||||
|
||||
// bobby...
|
||||
if (TryComp<BodyComponent>(args.Target, out var body))
|
||||
{
|
||||
Verb vomitOrgans = new()
|
||||
{
|
||||
Text = "Vomit organs",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Fluids/vomit_toxin.rsi/vomit_toxin-1.png",
|
||||
Act = () =>
|
||||
{
|
||||
_vomitSystem.Vomit(args.Target, -1000, -1000); // You feel hollow!
|
||||
var organs = _bodySystem.GetComponentsOnMechanisms<TransformComponent>(args.Target, body);
|
||||
var baseXform = Transform(args.Target);
|
||||
foreach (var (xform, mechanism) in organs)
|
||||
{
|
||||
if (HasComp<BrainComponent>(xform.Owner) || HasComp<EyeComponent>(xform.Owner))
|
||||
continue;
|
||||
|
||||
mechanism.Part?.RemoveMechanism(mechanism);
|
||||
xform.Coordinates = baseXform.Coordinates.Offset(_random.NextVector2(0.5f,0.75f));
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-vomit-organs-self"), args.Target, Filter.Entities(args.Target));
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("admin-smite-vomit-organs-others", ("name", args.Target)), baseXform.Coordinates, Filter.Pvs(args.Target).RemoveWhereAttachedEntity(x => x == args.Target));
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Causes them to vomit, including their internal organs.",
|
||||
};
|
||||
args.Verbs.Add(vomitOrgans);
|
||||
|
||||
Verb handRemoval = new()
|
||||
{
|
||||
Text = "Remove hands",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/fist.svg.192dpi.png",
|
||||
Act = () =>
|
||||
{
|
||||
var baseXform = Transform(args.Target);
|
||||
foreach (var part in body.GetPartsOfType(BodyPartType.Hand))
|
||||
{
|
||||
body.RemovePart(part);
|
||||
Transform(part.Owner).Coordinates = baseXform.Coordinates;
|
||||
}
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-remove-hands-self"), args.Target, Filter.Entities(args.Target));
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("admin-smite-remove-hands-others", ("name", args.Target)), baseXform.Coordinates, Filter.Pvs(args.Target).RemoveWhereAttachedEntity(x => x == args.Target));
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Removes the target's hands.",
|
||||
};
|
||||
args.Verbs.Add(handRemoval);
|
||||
}
|
||||
|
||||
if (TryComp<PhysicsComponent>(args.Target, out var physics))
|
||||
{
|
||||
Verb pinball = new()
|
||||
{
|
||||
Text = "Pinball",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Fun/toys.rsi/basketball.png",
|
||||
Act = () =>
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
var fixtures = Comp<FixturesComponent>(args.Target);
|
||||
xform.Anchored = false; // Just in case.
|
||||
physics.BodyType = BodyType.Dynamic;
|
||||
physics.BodyStatus = BodyStatus.InAir;
|
||||
physics.WakeBody();
|
||||
foreach (var (_, fixture) in fixtures.Fixtures)
|
||||
{
|
||||
if (!fixture.Hard)
|
||||
continue;
|
||||
fixture.Restitution = 1.1f;
|
||||
}
|
||||
|
||||
physics.LinearVelocity = _random.NextVector2(1.5f, 1.5f);
|
||||
physics.AngularVelocity = MathF.PI * 12;
|
||||
physics.LinearDamping = 0.0f;
|
||||
physics.AngularDamping = 0.0f;
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message =
|
||||
"Turns them into a super bouncy ball, flinging them around until they clip through the station into the abyss.",
|
||||
};
|
||||
args.Verbs.Add(pinball);
|
||||
|
||||
Verb yeet = new()
|
||||
{
|
||||
Text = "Yeet",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png",
|
||||
Act = () =>
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
var fixtures = Comp<FixturesComponent>(args.Target);
|
||||
xform.Anchored = false; // Just in case.
|
||||
physics.BodyType = BodyType.Dynamic;
|
||||
physics.BodyStatus = BodyStatus.InAir;
|
||||
physics.WakeBody();
|
||||
foreach (var (_, fixture) in fixtures.Fixtures)
|
||||
{
|
||||
fixture.Hard = false;
|
||||
}
|
||||
|
||||
physics.LinearVelocity = _random.NextVector2(8.0f, 8.0f);
|
||||
physics.AngularVelocity = MathF.PI * 12;
|
||||
physics.LinearDamping = 0.0f;
|
||||
physics.AngularDamping = 0.0f;
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Banishes them into the depths of space by turning on no-clip and tossing them.",
|
||||
};
|
||||
args.Verbs.Add(yeet);
|
||||
}
|
||||
|
||||
Verb bread = new()
|
||||
{
|
||||
Text = "Become Bread",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Consumable/Food/Baked/bread.rsi/plain.png",
|
||||
Act = () =>
|
||||
{
|
||||
_polymorphableSystem.PolymorphEntity(args.Target, "AdminBreadSmite");
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "It turns them into bread. Really. That's all it does.",
|
||||
};
|
||||
args.Verbs.Add(bread);
|
||||
|
||||
if (TryComp<ActorComponent>(args.Target, out var actorComponent))
|
||||
{
|
||||
Verb ghostKick = new()
|
||||
{
|
||||
Text = "Ghostkick",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/gavel.svg.192dpi.png",
|
||||
Act = () =>
|
||||
{
|
||||
_ghostKickManager.DoDisconnect(actorComponent.PlayerSession.ConnectedClient, "Smitten.");
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Silently kicks the user, dropping their connection.",
|
||||
};
|
||||
args.Verbs.Add(ghostKick);
|
||||
}
|
||||
|
||||
if (TryComp<InventoryComponent>(args.Target, out var inventory)) {
|
||||
Verb nyanify = new()
|
||||
{
|
||||
Text = "Nyanify",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Clothing/Head/Hats/catears.rsi/icon.png",
|
||||
Act = () =>
|
||||
{
|
||||
var ears = Spawn("ClothingHeadHatCatEars", Transform(args.Target).Coordinates);
|
||||
EnsureComp<UnremoveableComponent>(ears);
|
||||
_inventorySystem.TryUnequip(args.Target, "head", true, true, false, inventory);
|
||||
_inventorySystem.TryEquip(args.Target, ears, "head", true, true, false, inventory);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Forcibly adds cat ears. There is no escape.",
|
||||
};
|
||||
args.Verbs.Add(nyanify);
|
||||
|
||||
Verb killSign = new()
|
||||
{
|
||||
Text = "Kill sign",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Misc/killsign.rsi/icon.png",
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<KillSignComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Marks a player for death by their fellows.",
|
||||
};
|
||||
args.Verbs.Add(killSign);
|
||||
|
||||
// TODO: Port cluwne outfit.
|
||||
Verb clown = new()
|
||||
{
|
||||
Text = "Clown",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Fun/bikehorn.rsi/icon.png",
|
||||
Act = () =>
|
||||
{
|
||||
SetOutfitCommand.SetOutfit(args.Target, "ClownGear", EntityManager, (_, clothing) =>
|
||||
{
|
||||
if (HasComp<ClothingComponent>(clothing))
|
||||
EnsureComp<UnremoveableComponent>(clothing);
|
||||
EnsureComp<ClumsyComponent>(args.Target);
|
||||
});
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Clowns them. The suit cannot be removed.",
|
||||
};
|
||||
args.Verbs.Add(clown);
|
||||
}
|
||||
|
||||
Verb angerPointingArrows = new()
|
||||
{
|
||||
Text = "Anger Pointing Arrows",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/Misc/pointing.rsi/pointing.png",
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<PointingArrowAngeringComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Angers the pointing arrows, causing them to assault this entity.",
|
||||
};
|
||||
args.Verbs.Add(angerPointingArrows);
|
||||
|
||||
Verb dust = new()
|
||||
{
|
||||
Text = "Dust",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Materials/materials.rsi/ash.png",
|
||||
Act = () =>
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(args.Target);
|
||||
Spawn("Ash", Transform(args.Target).Coordinates);
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-turned-ash-other", ("name", args.Target)), args.Target, Filter.Pvs(args.Target));
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Reduces the target to a small pile of ash.",
|
||||
};
|
||||
args.Verbs.Add(dust);
|
||||
|
||||
Verb youtubeVideoSimulation = new()
|
||||
{
|
||||
Text = "Buffering",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/Misc/buffering_smite_icon.png",
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<BufferingComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Causes the target to randomly start buffering, freezing them in place for a short timespan while they load.",
|
||||
};
|
||||
args.Verbs.Add(youtubeVideoSimulation);
|
||||
|
||||
Verb instrumentation = new()
|
||||
{
|
||||
Text = "Become Instrument",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Objects/Fun/Instruments/h_synthesizer.rsi/icon.png",
|
||||
Act = () =>
|
||||
{
|
||||
_polymorphableSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite");
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "It turns them into a supersynth. Really. That's all it does.",
|
||||
};
|
||||
args.Verbs.Add(instrumentation);
|
||||
|
||||
Verb noGravity = new()
|
||||
{
|
||||
Text = "Remove gravity",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Structures/Machines/gravity_generator.rsi/off.png",
|
||||
Act = () =>
|
||||
{
|
||||
var grav = EnsureComp<MovementIgnoreGravityComponent>(args.Target);
|
||||
grav.Weightless = true;
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = "Grants them anti-gravity.",
|
||||
};
|
||||
args.Verbs.Add(noGravity);
|
||||
}
|
||||
}
|
||||
327
Content.Server/Administration/Systems/AdminVerbSystem.cs
Normal file
327
Content.Server/Administration/Systems/AdminVerbSystem.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Administration.UI;
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Configurable;
|
||||
using Content.Server.Disposal.Tube.Components;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Shared.Configurable.SharedConfigurationComponent;
|
||||
|
||||
namespace Content.Server.Administration.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// System to provide various global admin/debug verbs
|
||||
/// </summary>
|
||||
public sealed partial class AdminVerbSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConGroupController _groupController = default!;
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
|
||||
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
private readonly Dictionary<IPlayerSession, EditSolutionsEui> _openSolutionUis = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAdminVerbs);
|
||||
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddDebugVerbs);
|
||||
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddSmiteVerbs);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<SolutionContainerManagerComponent, SolutionChangedEvent>(OnSolutionChanged);
|
||||
}
|
||||
|
||||
private void AddAdminVerbs(GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<ActorComponent?>(args.User, out var actor))
|
||||
return;
|
||||
|
||||
var player = actor.PlayerSession;
|
||||
|
||||
if (_adminManager.IsAdmin(player))
|
||||
{
|
||||
if (TryComp(args.Target, out ActorComponent? targetActor))
|
||||
{
|
||||
// AdminHelp
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("ahelp-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Admin;
|
||||
verb.IconTexture = "/Textures/Interface/gavel.svg.192dpi.png";
|
||||
verb.Act = () =>
|
||||
_console.RemoteExecuteCommand(player, $"openahelp \"{targetActor.PlayerSession.UserId}\"");
|
||||
verb.Impact = LogImpact.Low;
|
||||
args.Verbs.Add(verb);
|
||||
|
||||
// Freeze
|
||||
var frozen = HasComp<AdminFrozenComponent>(args.Target);
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = frozen
|
||||
? Loc.GetString("admin-verbs-unfreeze")
|
||||
: Loc.GetString("admin-verbs-freeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
IconTexture = "/Textures/Interface/VerbIcons/snow.svg.192dpi.png",
|
||||
Act = () =>
|
||||
{
|
||||
if (frozen)
|
||||
RemComp<AdminFrozenComponent>(args.Target);
|
||||
else
|
||||
EnsureComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
// XenoArcheology
|
||||
if (TryComp<ArtifactComponent>(args.Target, out var artifact))
|
||||
{
|
||||
// make artifact always active (by adding timer trigger)
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("artifact-verb-make-always-active"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () => EntityManager.AddComponent<ArtifactTimerTriggerComponent>(args.Target),
|
||||
Disabled = EntityManager.HasComponent<ArtifactTimerTriggerComponent>(args.Target),
|
||||
Impact = LogImpact.High
|
||||
});
|
||||
|
||||
// force to activate artifact ignoring timeout
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("artifact-verb-activate"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () => _artifactSystem.ForceActivateArtifact(args.Target, component: artifact),
|
||||
Impact = LogImpact.High
|
||||
});
|
||||
}
|
||||
|
||||
// TeleportTo
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("admin-verbs-teleport-to"),
|
||||
Category = VerbCategory.Admin,
|
||||
IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png",
|
||||
Act = () => _console.ExecuteCommand(player, $"tpto {args.Target}"),
|
||||
Impact = LogImpact.Low
|
||||
});
|
||||
|
||||
// TeleportHere
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("admin-verbs-teleport-here"),
|
||||
Category = VerbCategory.Admin,
|
||||
IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png",
|
||||
Act = () => _console.ExecuteCommand(player, $"tpto {args.Target} {args.User}"),
|
||||
Impact = LogImpact.Low
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDebugVerbs(GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<ActorComponent?>(args.User, out var actor))
|
||||
return;
|
||||
|
||||
var player = actor.PlayerSession;
|
||||
|
||||
// Delete verb
|
||||
if (_groupController.CanCommand(player, "deleteentity"))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("delete-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png";
|
||||
verb.Act = () => EntityManager.DeleteEntity(args.Target);
|
||||
verb.Impact = LogImpact.Medium;
|
||||
verb.ConfirmationPopup = true;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Rejuvenate verb
|
||||
if (_groupController.CanCommand(player, "rejuvenate"))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("rejuvenate-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png";
|
||||
verb.Act = () => RejuvenateCommand.PerformRejuvenate(args.Target);
|
||||
verb.Impact = LogImpact.Medium;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Control mob verb
|
||||
if (_groupController.CanCommand(player, "controlmob") &&
|
||||
args.User != args.Target &&
|
||||
EntityManager.HasComponent<MindComponent>(args.User) &&
|
||||
EntityManager.TryGetComponent<MindComponent?>(args.Target, out var targetMind))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("control-mob-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
// TODO VERB ICON control mob icon
|
||||
verb.Act = () =>
|
||||
{
|
||||
player.ContentData()?.Mind?.TransferTo(args.Target, ghostCheckOverride: true);
|
||||
};
|
||||
verb.Impact = LogImpact.High;
|
||||
verb.ConfirmationPopup = true;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Make Sentient verb
|
||||
if (_groupController.CanCommand(player, "makesentient") &&
|
||||
args.User != args.Target &&
|
||||
!EntityManager.HasComponent<MindComponent>(args.Target))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("make-sentient-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/sentient.svg.192dpi.png";
|
||||
verb.Act = () => MakeSentientCommand.MakeSentient(args.Target, EntityManager);
|
||||
verb.Impact = LogImpact.Medium;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Set clothing verb
|
||||
if (_groupController.CanCommand(player, "setoutfit") &&
|
||||
EntityManager.HasComponent<InventoryComponent>(args.Target))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("set-outfit-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png";
|
||||
verb.Act = () => _euiManager.OpenEui(new SetOutfitEui(args.Target), player);
|
||||
verb.Impact = LogImpact.Medium;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// In range unoccluded verb
|
||||
if (_groupController.CanCommand(player, "inrangeunoccluded"))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("in-range-unoccluded-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png";
|
||||
verb.Act = () =>
|
||||
{
|
||||
var message = args.User.InRangeUnOccluded(args.Target)
|
||||
? Loc.GetString("in-range-unoccluded-verb-on-activate-not-occluded")
|
||||
: Loc.GetString("in-range-unoccluded-verb-on-activate-occluded");
|
||||
args.Target.PopupMessage(args.User, message);
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Get Disposal tube direction verb
|
||||
if (_groupController.CanCommand(player, "tubeconnections") &&
|
||||
EntityManager.TryGetComponent<IDisposalTubeComponent?>(args.Target, out var tube))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("tube-direction-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png";
|
||||
verb.Act = () => tube.PopupDirections(args.User);
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Make ghost role verb
|
||||
if (_groupController.CanCommand(player, "makeghostrole") &&
|
||||
!(EntityManager.GetComponentOrNull<MindComponent>(args.Target)?.HasMind ?? false))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("make-ghost-role-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
// TODO VERB ICON add ghost icon
|
||||
// Where is the national park service icon for haunted forests?
|
||||
verb.Act = () => _ghostRoleSystem.OpenMakeGhostRoleEui(player, args.Target);
|
||||
verb.Impact = LogImpact.Medium;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
if (_groupController.CanAdminMenu(player) &&
|
||||
EntityManager.TryGetComponent<ConfigurationComponent?>(args.Target, out var config))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("configure-verb-get-data-text");
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png";
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.Act = () => _uiSystem.TryOpen(args.Target, ConfigurationUiKey.Key, actor.PlayerSession);
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Add verb to open Solution Editor
|
||||
if (_groupController.CanCommand(player, "addreagent") &&
|
||||
EntityManager.HasComponent<SolutionContainerManagerComponent>(args.Target))
|
||||
{
|
||||
Verb verb = new();
|
||||
verb.Text = Loc.GetString("edit-solutions-verb-get-data-text");
|
||||
verb.Category = VerbCategory.Debug;
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/spill.svg.192dpi.png";
|
||||
verb.Act = () => OpenEditSolutionsEui(player, args.Target);
|
||||
verb.Impact = LogImpact.Medium; // maybe high depending on WHAT reagents they add...
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
#region SolutionsEui
|
||||
private void OnSolutionChanged(EntityUid uid, SolutionContainerManagerComponent component, SolutionChangedEvent args)
|
||||
{
|
||||
foreach (var eui in _openSolutionUis.Values)
|
||||
{
|
||||
if (eui.Target == uid)
|
||||
eui.StateDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenEditSolutionsEui(IPlayerSession session, EntityUid uid)
|
||||
{
|
||||
if (session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
if (_openSolutionUis.ContainsKey(session))
|
||||
_openSolutionUis[session].Close();
|
||||
|
||||
var eui = _openSolutionUis[session] = new EditSolutionsEui(uid);
|
||||
_euiManager.OpenEui(eui, session);
|
||||
eui.StateDirty();
|
||||
}
|
||||
|
||||
public void OnEditSolutionsEuiClosed(IPlayerSession session)
|
||||
{
|
||||
_openSolutionUis.Remove(session, out var eui);
|
||||
}
|
||||
|
||||
private void Reset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_openSolutionUis.Clear();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
39
Content.Server/Administration/Systems/BufferingSystem.cs
Normal file
39
Content.Server/Administration/Systems/BufferingSystem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Content.Server.Administration.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Administration.Systems;
|
||||
|
||||
public sealed class BufferingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var buffering in EntityQuery<BufferingComponent>())
|
||||
{
|
||||
if (buffering.BufferingIcon is not null)
|
||||
{
|
||||
buffering.BufferingTimer -= frameTime;
|
||||
if (!(buffering.BufferingTimer <= 0.0f))
|
||||
continue;
|
||||
|
||||
Del(buffering.BufferingIcon.Value);
|
||||
RemComp<AdminFrozenComponent>(buffering.Owner);
|
||||
buffering.TimeTilNextBuffer = _random.NextFloat(buffering.MinimumTimeTilNextBuffer, buffering.MaximumTimeTilNextBuffer);
|
||||
buffering.BufferingIcon = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffering.TimeTilNextBuffer -= frameTime;
|
||||
if (!(buffering.TimeTilNextBuffer <= 0.0f))
|
||||
continue;
|
||||
|
||||
buffering.BufferingTimer = _random.NextFloat(buffering.MinimumBufferTime, buffering.MaximumBufferTime);
|
||||
buffering.BufferingIcon = Spawn("BufferingIcon", new EntityCoordinates(buffering.Owner, Vector2.Zero));
|
||||
EnsureComp<AdminFrozenComponent>(buffering.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
263
Content.Server/Administration/Systems/BwoinkSystem.cs
Normal file
263
Content.Server/Administration/Systems/BwoinkSystem.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Systems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class BwoinkSystem : SharedBwoinkSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
private readonly HttpClient _httpClient = new();
|
||||
private string _webhookUrl = string.Empty;
|
||||
private string _serverName = string.Empty;
|
||||
private readonly Dictionary<NetUserId, (string id, string username, string content)> _relayMessages = new();
|
||||
private readonly Dictionary<NetUserId, Queue<string>> _messageQueues = new();
|
||||
private readonly HashSet<NetUserId> _processingChannels = new();
|
||||
private const ushort MessageMax = 2000;
|
||||
private int _maxAdditionalChars;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_config.OnValueChanged(CCVars.DiscordAHelpWebhook, OnWebhookChanged, true);
|
||||
_config.OnValueChanged(CVars.GameHostName, OnServerNameChanged, true);
|
||||
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("AHELP");
|
||||
_maxAdditionalChars = GenerateAHelpMessage("", "", true, true).Length + Header("").Length;
|
||||
|
||||
SubscribeLocalEvent<RoundStartingEvent>(RoundStarting);
|
||||
}
|
||||
|
||||
private void RoundStarting(RoundStartingEvent ev)
|
||||
{
|
||||
_relayMessages.Clear();
|
||||
}
|
||||
|
||||
private void OnServerNameChanged(string obj)
|
||||
{
|
||||
_serverName = obj;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_config.UnsubValueChanged(CCVars.DiscordAHelpWebhook, OnWebhookChanged);
|
||||
_config.UnsubValueChanged(CVars.GameHostName, OnServerNameChanged);
|
||||
}
|
||||
|
||||
private void OnWebhookChanged(string obj)
|
||||
{
|
||||
_webhookUrl = obj;
|
||||
}
|
||||
|
||||
private string Header(string serverName) => $"Server: {serverName}";
|
||||
|
||||
private async void ProcessQueue(NetUserId channelId, Queue<string> messages)
|
||||
{
|
||||
if (!_relayMessages.TryGetValue(channelId, out var oldMessage) || messages.Sum(x => x.Length+2) + oldMessage.content.Length > MessageMax)
|
||||
{
|
||||
var lookup = await _playerLocator.LookupIdAsync(channelId);
|
||||
|
||||
if (lookup == null)
|
||||
{
|
||||
_sawmill.Log(LogLevel.Error, $"Unable to find player for netuserid {channelId} when sending discord webhook.");
|
||||
_relayMessages.Remove(channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
oldMessage = (string.Empty, lookup.Username, Header(_serverName));
|
||||
}
|
||||
|
||||
while (messages.TryDequeue(out var message))
|
||||
{
|
||||
oldMessage.content += $"\n{message}";
|
||||
}
|
||||
|
||||
var payload = new WebhookPayload()
|
||||
{
|
||||
Username = $"R:{_gameTicker.RoundId}|N:{oldMessage.username}",
|
||||
Content = oldMessage.content
|
||||
};
|
||||
|
||||
if (oldMessage.id == string.Empty)
|
||||
{
|
||||
var request = await _httpClient.PostAsync($"{_webhookUrl}?wait=true",
|
||||
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
|
||||
|
||||
var content = await request.Content.ReadAsStringAsync();
|
||||
if (!request.IsSuccessStatusCode)
|
||||
{
|
||||
_sawmill.Log(LogLevel.Error, $"Discord returned bad status code when posting message: {request.StatusCode}\nResponse: {content}");
|
||||
_relayMessages.Remove(channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
var id = JsonNode.Parse(content)?["id"];
|
||||
if (id == null)
|
||||
{
|
||||
_sawmill.Log(LogLevel.Error, $"Could not find id in json-content returned from discord webhook: {content}");
|
||||
_relayMessages.Remove(channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
oldMessage.id = id.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = await _httpClient.PatchAsync($"{_webhookUrl}/messages/{oldMessage.id}",
|
||||
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
|
||||
|
||||
if (!request.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await request.Content.ReadAsStringAsync();
|
||||
_sawmill.Log(LogLevel.Error, $"Discord returned bad status code when patching message: {request.StatusCode}\nResponse: {content}");
|
||||
_relayMessages.Remove(channelId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_relayMessages[channelId] = oldMessage;
|
||||
|
||||
_processingChannels.Remove(channelId);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var channelId in _messageQueues.Keys.ToArray())
|
||||
{
|
||||
if(_processingChannels.Contains(channelId)) continue;
|
||||
|
||||
var queue = _messageQueues[channelId];
|
||||
_messageQueues.Remove(channelId);
|
||||
if (queue.Count == 0) continue;
|
||||
_processingChannels.Add(channelId);
|
||||
|
||||
ProcessQueue(channelId, queue);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
base.OnBwoinkTextMessage(message, eventArgs);
|
||||
var senderSession = (IPlayerSession) eventArgs.SenderSession;
|
||||
|
||||
// TODO: Sanitize text?
|
||||
// Confirm that this person is actually allowed to send a message here.
|
||||
var personalChannel = senderSession.UserId == message.ChannelId;
|
||||
var senderAdmin = _adminManager.GetAdminData(senderSession);
|
||||
var authorized = personalChannel || senderAdmin != null;
|
||||
if (!authorized)
|
||||
{
|
||||
// Unauthorized bwoink (log?)
|
||||
return;
|
||||
}
|
||||
|
||||
var escapedText = FormattedMessage.EscapeText(message.Text);
|
||||
|
||||
var bwoinkText = senderAdmin switch
|
||||
{
|
||||
var x when x is not null && x.Flags == AdminFlags.Adminhelp =>
|
||||
$"[color=purple]{senderSession.Name}[/color]: {escapedText}",
|
||||
var x when x is not null && x.HasFlag(AdminFlags.Adminhelp) =>
|
||||
$"[color=red]{senderSession.Name}[/color]: {escapedText}",
|
||||
_ => $"{senderSession.Name}: {escapedText}",
|
||||
};
|
||||
|
||||
var msg = new BwoinkTextMessage(message.ChannelId, senderSession.UserId, bwoinkText);
|
||||
|
||||
LogBwoink(msg);
|
||||
|
||||
// Admins
|
||||
var targets = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient).ToList();
|
||||
|
||||
// And involved player
|
||||
if (_playerManager.TryGetSessionById(message.ChannelId, out var session))
|
||||
if (!targets.Contains(session.ConnectedClient))
|
||||
targets.Add(session.ConnectedClient);
|
||||
|
||||
foreach (var channel in targets)
|
||||
RaiseNetworkEvent(msg, channel);
|
||||
|
||||
var noReceivers = targets.Count == 1;
|
||||
|
||||
var sendsWebhook = _webhookUrl != string.Empty;
|
||||
if (sendsWebhook)
|
||||
{
|
||||
if (!_messageQueues.ContainsKey(msg.ChannelId))
|
||||
_messageQueues[msg.ChannelId] = new Queue<string>();
|
||||
|
||||
var str = message.Text;
|
||||
var unameLength = senderSession.Name.Length;
|
||||
|
||||
if (unameLength+str.Length+_maxAdditionalChars > MessageMax)
|
||||
{
|
||||
str = str[..(MessageMax - _maxAdditionalChars - unameLength)];
|
||||
}
|
||||
_messageQueues[msg.ChannelId].Enqueue(GenerateAHelpMessage(senderSession.Name, str, !personalChannel, noReceivers));
|
||||
}
|
||||
|
||||
if (noReceivers)
|
||||
{
|
||||
var systemText = sendsWebhook ?
|
||||
Loc.GetString("bwoink-system-starmute-message-no-other-users-webhook") :
|
||||
Loc.GetString("bwoink-system-starmute-message-no-other-users");
|
||||
var starMuteMsg = new BwoinkTextMessage(message.ChannelId, SystemUserId, systemText);
|
||||
RaiseNetworkEvent(starMuteMsg, senderSession.ConnectedClient);
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateAHelpMessage(string username, string message, bool admin, bool noReceiver)
|
||||
{
|
||||
var stringbuilder = new StringBuilder();
|
||||
if (noReceiver)
|
||||
stringbuilder.Append(":sos:");
|
||||
stringbuilder.Append(admin ? ":outbox_tray:" : ":inbox_tray:");
|
||||
stringbuilder.Append(' ');
|
||||
stringbuilder.Append(username);
|
||||
stringbuilder.Append(": ");
|
||||
stringbuilder.Append(message);
|
||||
return stringbuilder.ToString();
|
||||
}
|
||||
|
||||
private struct WebhookPayload
|
||||
{
|
||||
[JsonPropertyName("username")]
|
||||
public string Username { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("allowed_mentions")]
|
||||
public Dictionary<string, string[]> AllowedMentions { get; set; } =
|
||||
new()
|
||||
{
|
||||
{ "parse", Array.Empty<string>() }
|
||||
};
|
||||
|
||||
public WebhookPayload() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user