mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-28 11:07:50 +03:00
Ports Blob from https://github.com/Goob-Station/Goob-Station/pull/975 that was ported from https://github.com/Rxup/space-station-14. Credit to VigersRay for original code, Roudenn and Rxup for maintaining and jorgun for the Goob port. --- - [X] Port https://github.com/Goob-Station/Goob-Station/pull/975; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/1209; - [X] Port Blob related code from https://github.com/Goob-Station/Goob-Station/pull/1262; - [X] Port Blob related code from https://github.com/Goob-Station/Goob-Station/pull/1340; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/1408; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/1419; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/1440; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/1817; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/2077; - [ ] ~Port https://github.com/Goob-Station/Goob-Station/pull/1916~; - [ ] ~Port https://github.com/Goob-Station/Goob-Station/pull/1917~; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/2077; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/2092; - [X] Port https://github.com/Goob-Station/Goob-Station/pull/2546; - [X] Port https://github.com/Rxup/space-station-14/pull/963; - [X] Port https://github.com/Rxup/space-station-14/pull/998; - [ ] ~Port https://github.com/Goob-Station/Goob-Station/pull/2563~. - [X] Enable Blob and Blob gamemode; - [X] Add `StationGlobConfig` to all stations; - [X] Use `AnnouncerSystem` in `BlobRuleSystem.cs`; - [X] Blob language and Hivemind (from https://github.com/Rxup/space-station-14/pull/176); - [x] Change CVars location; - [X] Add media. --- <details><summary><h1>Media</h1></summary> <p> https://youtu.be/-WtMQwRcmrU?si=su3An6RtiCTZg-DV </p> </details> --- 🆑 VigersRay, Roudenn, Rxup, vladospupuos, fishbait and Kyoth25f - add: Added a new antagonist: Blob --------- Co-authored-by: fishbait <gnesse@gmail.com> Co-authored-by: Fishbait <Fishbait@git.ml> Co-authored-by: Aiden <aiden@djkraz.com> Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Co-authored-by: lanse12 <cloudability.ez@gmail.com> Co-authored-by: BombasterDS <deniskaporoshok@gmail.com> Co-authored-by: Aviu00 <93730715+Aviu00@users.noreply.github.com> Co-authored-by: Piras314 <p1r4s@proton.me> Co-authored-by: shibe <95730644+shibechef@users.noreply.github.com> Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com> Co-authored-by: JohnOakman <sremy2012@hotmail.fr> Co-authored-by: Fat Engineer Gaming <159075414+Fat-Engineer-Gaming@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Rouden <149893554+Roudenn@users.noreply.github.com>
459 lines
16 KiB
C#
459 lines
16 KiB
C#
using System.Linq;
|
||
using Content.Server.Actions;
|
||
using Content.Server._Goobstation.Blob.Components;
|
||
using Content.Server.GameTicking.Rules.Components;
|
||
using Content.Server.Chat.Managers;
|
||
using Content.Server.Hands.Systems;
|
||
using Content.Server.Mind;
|
||
using Content.Server.Roles;
|
||
using Content.Shared.ActionBlocker;
|
||
using Content.Shared.Alert;
|
||
using Content.Shared._Goobstation.Blob;
|
||
using Content.Shared._Goobstation.Blob.Components;
|
||
using Content.Shared.Coordinates.Helpers;
|
||
using Content.Shared.Hands.Components;
|
||
using Content.Shared.Mind;
|
||
using Content.Shared.Popups;
|
||
using Robust.Server.GameObjects;
|
||
using Robust.Server.Player;
|
||
using Robust.Shared.CPUJob.JobQueues.Queues;
|
||
using Robust.Shared.Map;
|
||
using Robust.Shared.Map.Components;
|
||
using Robust.Shared.Player;
|
||
using Robust.Shared.Prototypes;
|
||
|
||
namespace Content.Server._Goobstation.Blob.Systems;
|
||
|
||
public sealed class BlobObserverSystem : SharedBlobObserverSystem
|
||
{
|
||
[Dependency] private readonly ActionsSystem _action = default!;
|
||
[Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!;
|
||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||
[Dependency] private readonly ILogManager _logMan = default!;
|
||
[Dependency] private readonly RoleSystem _roleSystem = default!;
|
||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||
[Dependency] private readonly ISharedPlayerManager _actorSystem = default!;
|
||
[Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!;
|
||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||
[Dependency] private readonly HandsSystem _hands = default!;
|
||
[Dependency] private readonly BlobTileSystem _blobTileSystem = default!;
|
||
|
||
private EntityQuery<BlobTileComponent> _tileQuery;
|
||
|
||
private const double MoverJobTime = 0.005;
|
||
private readonly JobQueue _moveJobQueue = new(MoverJobTime);
|
||
|
||
private ISawmill _logger = default!;
|
||
|
||
[ValidatePrototypeId<EntityPrototype>] private const string BlobCaptureObjective = "BlobCaptureObjective";
|
||
[ValidatePrototypeId<EntityPrototype>] private const string MobObserverBlobController = "MobObserverBlobController";
|
||
|
||
public override void Initialize()
|
||
{
|
||
base.Initialize();
|
||
|
||
SubscribeLocalEvent<BlobCoreComponent, CreateBlobObserverEvent>(OnCreateBlobObserver);
|
||
|
||
SubscribeLocalEvent<BlobObserverComponent, PlayerAttachedEvent>(OnPlayerAttached, before: [typeof(ActionsSystem)]);
|
||
SubscribeLocalEvent<BlobObserverComponent, PlayerDetachedEvent>(OnPlayerDetached, before: [typeof(ActionsSystem)]);
|
||
|
||
SubscribeLocalEvent<BlobCoreComponent, BlobCreateBlobbernautActionEvent>(OnCreateBlobbernaut);
|
||
SubscribeLocalEvent<BlobCoreComponent, BlobToCoreActionEvent>(OnBlobToCore);
|
||
SubscribeLocalEvent<BlobCoreComponent, BlobSwapChemActionEvent>(OnBlobSwapChem);
|
||
SubscribeLocalEvent<BlobCoreComponent, BlobSwapCoreActionEvent>(OnSwapCore);
|
||
SubscribeLocalEvent<BlobCoreComponent, BlobSplitCoreActionEvent>(OnSplitCore);
|
||
|
||
SubscribeLocalEvent<BlobObserverComponent, MoveEvent>(OnMoveEvent);
|
||
SubscribeLocalEvent<BlobObserverComponent, BlobChemSwapPrototypeSelectedMessage>(OnChemSelected);
|
||
|
||
SubscribeLocalEvent<BlobObserverComponent, ComponentStartup>(OnStartup);
|
||
|
||
|
||
_logger = _logMan.GetSawmill("blob.core");
|
||
_tileQuery = GetEntityQuery<BlobTileComponent>();
|
||
}
|
||
|
||
private void OnStartup(Entity<BlobObserverComponent> ent, ref ComponentStartup args)
|
||
{
|
||
_hands.AddHand(ent,"BlobHand",HandLocation.Middle);
|
||
|
||
ent.Comp.VirtualItem = Spawn(MobObserverBlobController, Transform(ent).Coordinates);
|
||
var comp = EnsureComp<BlobObserverControllerComponent>(ent.Comp.VirtualItem);
|
||
comp.Blob = ent;
|
||
Dirty(ent);
|
||
|
||
if (!_hands.TryPickup(ent, ent.Comp.VirtualItem, "BlobHand", false, false, false))
|
||
{
|
||
QueueDel(ent);
|
||
}
|
||
}
|
||
|
||
private void SendBlobBriefing(EntityUid mind)
|
||
{
|
||
if (_mindSystem.TryGetSession(mind, out var session))
|
||
{
|
||
_chatManager.DispatchServerMessage(session, Loc.GetString("blob-role-greeting"));
|
||
}
|
||
}
|
||
|
||
private void OnCreateBlobObserver(EntityUid blobCoreUid, BlobCoreComponent core, CreateBlobObserverEvent args)
|
||
{
|
||
var observer = Spawn(core.ObserverBlobPrototype, Transform(blobCoreUid).Coordinates);
|
||
|
||
core.Observer = observer;
|
||
|
||
if (!TryComp<BlobObserverComponent>(observer, out var blobObserverComponent))
|
||
{
|
||
args.Cancel();
|
||
return;
|
||
}
|
||
|
||
blobObserverComponent.Core = (blobCoreUid, core);
|
||
Dirty(observer,blobObserverComponent);
|
||
|
||
|
||
var isNewMind = false;
|
||
if (!_mindSystem.TryGetMind(blobCoreUid, out var mindId, out var mind))
|
||
{
|
||
if (
|
||
!_playerManager.TryGetSessionById(args.UserId, out var playerSession) ||
|
||
playerSession.AttachedEntity == null ||
|
||
!_mindSystem.TryGetMind(playerSession.AttachedEntity.Value, out mindId, out mind))
|
||
{
|
||
mindId = _mindSystem.CreateMind(args.UserId, "Blob Player");
|
||
mind = Comp<MindComponent>(mindId);
|
||
isNewMind = true;
|
||
}
|
||
}
|
||
|
||
if (!isNewMind)
|
||
{
|
||
var name = mind.Session?.Name ?? "???";
|
||
_mindSystem.WipeMind(mindId, mind);
|
||
mindId = _mindSystem.CreateMind(args.UserId, $"Blob Player ({name})");
|
||
mind = Comp<MindComponent>(mindId);
|
||
}
|
||
|
||
_roleSystem.MindAddRole(mindId, core.MindRoleBlobPrototypeId.Id);
|
||
SendBlobBriefing(mindId);
|
||
|
||
var blobRule = EntityQuery<BlobRuleComponent>().FirstOrDefault();
|
||
blobRule?.Blobs.Add((mindId,mind));
|
||
|
||
_mindSystem.TransferTo(mindId, observer, true, mind: mind);
|
||
if (_actorSystem.TryGetSessionById(args.UserId, out var session))
|
||
{
|
||
_actorSystem.SetAttachedEntity(session, observer, true);
|
||
}
|
||
|
||
_mindSystem.TryAddObjective(mindId, mind, BlobCaptureObjective);
|
||
|
||
UpdateUi(observer, core);
|
||
}
|
||
|
||
private void UpdateActions(ICommonSession playerSession, EntityUid uid, BlobObserverComponent? component = null)
|
||
{
|
||
if (!Resolve(uid, ref component))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (component.Core == null || TerminatingOrDeleted(component.Core.Value))
|
||
{
|
||
_logger.Error("It is not possible to find a core for the observer!");
|
||
return;
|
||
}
|
||
|
||
_action.GrantActions(uid, component.Core.Value.Comp.Actions, component.Core.Value);
|
||
_viewSubscriberSystem.AddViewSubscriber(component.Core.Value, playerSession); // GrantActions require keep in pvs
|
||
}
|
||
|
||
private void OnPlayerAttached(EntityUid uid, BlobObserverComponent component, PlayerAttachedEvent args)
|
||
{
|
||
UpdateActions(args.Player, uid, component);
|
||
_blobCoreSystem.UpdateAllAlerts(component.Core!.Value);
|
||
}
|
||
|
||
private void OnPlayerDetached(EntityUid uid, BlobObserverComponent component, PlayerDetachedEvent args)
|
||
{
|
||
if (component.Core.HasValue && !TerminatingOrDeleted(component.Core.Value))
|
||
{
|
||
_viewSubscriberSystem.RemoveViewSubscriber(component.Core.Value, args.Player);
|
||
}
|
||
}
|
||
|
||
private void OnBlobSwapChem(EntityUid uid,
|
||
BlobCoreComponent blobCoreComponent,
|
||
BlobSwapChemActionEvent args)
|
||
{
|
||
if (!TryComp<BlobObserverComponent>(args.Performer, out var observerComponent))
|
||
return;
|
||
|
||
TryOpenUi(args.Performer, args.Performer, observerComponent);
|
||
args.Handled = true;
|
||
}
|
||
|
||
private void OnChemSelected(EntityUid uid, BlobObserverComponent component, BlobChemSwapPrototypeSelectedMessage args)
|
||
{
|
||
if (component.Core == null || !TryComp<BlobCoreComponent>(component.Core.Value, out var blobCoreComponent))
|
||
return;
|
||
|
||
if (component.SelectedChemId == args.SelectedId)
|
||
return;
|
||
|
||
if (!_blobCoreSystem.TryUseAbility(component.Core.Value, blobCoreComponent.SwapChemCost))
|
||
return;
|
||
|
||
ChangeChem(uid, args.SelectedId, component);
|
||
}
|
||
|
||
private bool ChangeChem(EntityUid uid, BlobChemType newChem, BlobObserverComponent component)
|
||
{
|
||
if (component.Core == null || !TryComp<BlobCoreComponent>(component.Core.Value, out var blobCoreComponent))
|
||
return false;
|
||
|
||
var core = component.Core.Value;
|
||
|
||
component.SelectedChemId = newChem;
|
||
|
||
_blobCoreSystem.ChangeChem(core, newChem, blobCoreComponent);
|
||
UpdateUi(uid, blobCoreComponent);
|
||
|
||
return true;
|
||
}
|
||
|
||
private void TryOpenUi(EntityUid uid, EntityUid user, BlobObserverComponent? component = null)
|
||
{
|
||
if (!Resolve(uid, ref component))
|
||
return;
|
||
|
||
if (!TryComp(user, out ActorComponent? actor))
|
||
return;
|
||
|
||
_uiSystem.TryToggleUi(uid, BlobChemSwapUiKey.Key, actor.PlayerSession);
|
||
}
|
||
|
||
private void UpdateUi(EntityUid uid, BlobCoreComponent blobCoreComponent)
|
||
{
|
||
if (!TryComp<BlobObserverComponent>(uid, out var observerComponent))
|
||
{
|
||
return;
|
||
}
|
||
var state = new BlobChemSwapBoundUserInterfaceState(blobCoreComponent.ChemСolors, observerComponent.SelectedChemId);
|
||
|
||
_uiSystem.SetUiState(uid, BlobChemSwapUiKey.Key, state);
|
||
}
|
||
|
||
// TODO: This is very bad, but it is clearly better than invisible walls, let someone do better.
|
||
private void OnMoveEvent(EntityUid uid, BlobObserverComponent observerComponent, ref MoveEvent args)
|
||
{
|
||
if (observerComponent.IsProcessingMoveEvent)
|
||
return;
|
||
|
||
observerComponent.IsProcessingMoveEvent = true;
|
||
|
||
var job = new BlobObserverMover(EntityManager, _blocker, _transform,this, MoverJobTime)
|
||
{
|
||
Observer = (uid,observerComponent),
|
||
NewPosition = args.NewPosition
|
||
};
|
||
|
||
_moveJobQueue.EnqueueJob(job);
|
||
}
|
||
|
||
public override void Update(float frameTime)
|
||
{
|
||
base.Update(frameTime);
|
||
_moveJobQueue.Process();
|
||
}
|
||
|
||
private void OnSplitCore(EntityUid uid,
|
||
BlobCoreComponent blobCoreComponent,
|
||
BlobSplitCoreActionEvent args)
|
||
{
|
||
if (args.Handled)
|
||
return;
|
||
|
||
if (!blobCoreComponent.CanSplit)
|
||
{
|
||
_popup.PopupEntity(Loc.GetString("blob-cant-split"), args.Performer, args.Performer, PopupType.Large);
|
||
return;
|
||
}
|
||
|
||
var gridUid = _transform.GetGrid(args.Target);
|
||
|
||
if (!TryComp<MapGridComponent>(gridUid, out var grid))
|
||
{
|
||
return;
|
||
}
|
||
var centerTile = _mapSystem.GetLocalTilesIntersecting(gridUid.Value,
|
||
grid,
|
||
new Box2(args.Target.Position, args.Target.Position))
|
||
.ToArray();
|
||
|
||
EntityUid? blobTile = null;
|
||
|
||
foreach (var tileref in centerTile)
|
||
{
|
||
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid.Value, grid,tileref.GridIndices))
|
||
{
|
||
if (!_tileQuery.HasComponent(ent))
|
||
continue;
|
||
blobTile = ent;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (blobTile == null ||
|
||
!HasComp<BlobNodeComponent>(blobTile) ||
|
||
!TryComp<BlobTileComponent>(blobTile, out var blobTileComp) ||
|
||
blobTileComp.BlobTileType == BlobTileType.Core)
|
||
{
|
||
_popup.PopupEntity(Loc.GetString("blob-target-node-blob-invalid"), args.Performer, args.Performer, PopupType.Large);
|
||
args.Handled = true;
|
||
return;
|
||
}
|
||
|
||
if (!_blobCoreSystem.TryUseAbility((uid, blobCoreComponent), blobCoreComponent.SplitCoreCost))
|
||
{
|
||
args.Handled = true;
|
||
return;
|
||
}
|
||
|
||
QueueDel(blobTile.Value);
|
||
var newCore = Spawn(blobCoreComponent.TilePrototypes[BlobTileType.Core], args.Target);
|
||
|
||
blobCoreComponent.CanSplit = false;
|
||
_action.RemoveAction(args.Action);
|
||
|
||
if (TryComp<BlobCoreComponent>(newCore, out var newBlobCoreComponent))
|
||
{
|
||
newBlobCoreComponent.CanSplit = false;
|
||
newBlobCoreComponent.BlobTiles.Add(newCore);
|
||
}
|
||
|
||
args.Handled = true;
|
||
}
|
||
|
||
private void OnSwapCore(EntityUid uid,
|
||
BlobCoreComponent blobCoreComponent,
|
||
BlobSwapCoreActionEvent args)
|
||
{
|
||
if (args.Handled)
|
||
return;
|
||
|
||
var gridUid = _transform.GetGrid(args.Target);
|
||
|
||
if (!TryComp<MapGridComponent>(gridUid, out var grid))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var centerTile = _mapSystem.GetLocalTilesIntersecting(gridUid.Value,
|
||
grid,
|
||
new Box2(args.Target.Position, args.Target.Position))
|
||
.ToArray();
|
||
|
||
EntityUid? blobTile = null;
|
||
|
||
foreach (var tileRef in centerTile)
|
||
{
|
||
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid.Value, grid, tileRef.GridIndices))
|
||
{
|
||
if (!_tileQuery.HasComponent(ent))
|
||
continue;
|
||
blobTile = ent;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (blobTile == null || !HasComp<BlobNodeComponent>(blobTile))
|
||
{
|
||
_popup.PopupEntity(Loc.GetString("blob-target-node-blob-invalid"), args.Performer, args.Performer, PopupType.Large);
|
||
args.Handled = true;
|
||
return;
|
||
}
|
||
|
||
if (!_blobCoreSystem.TryUseAbility((uid, blobCoreComponent), blobCoreComponent.SwapCoreCost))
|
||
{
|
||
args.Handled = true;
|
||
return;
|
||
}
|
||
|
||
// Swap positions of blob's core and node.
|
||
var nodePos = Transform(blobTile.Value).Coordinates;
|
||
var corePos = Transform(uid).Coordinates;
|
||
_transform.SetCoordinates(uid, nodePos.SnapToGrid());
|
||
_transform.SetCoordinates(blobTile.Value, corePos.SnapToGrid());
|
||
var xformCore = Transform(uid);
|
||
if (!xformCore.Anchored)
|
||
{
|
||
_transform.AnchorEntity(uid, xformCore);
|
||
}
|
||
var xformNode = Transform(blobTile.Value);
|
||
if (!xformNode.Anchored)
|
||
{
|
||
_transform.AnchorEntity(blobTile.Value, xformNode);
|
||
}
|
||
|
||
// And then swap their BlobNodeComponents, so they will work properly.
|
||
|
||
_blobTileSystem.SwapSpecials(
|
||
(blobTile.Value, EnsureComp<BlobNodeComponent>(blobTile.Value)),
|
||
(uid, EnsureComp<BlobNodeComponent>(uid)));
|
||
|
||
args.Handled = true;
|
||
}
|
||
|
||
private void OnCreateBlobbernaut(EntityUid uid,
|
||
BlobCoreComponent blobCoreComponent,
|
||
BlobCreateBlobbernautActionEvent args)
|
||
{
|
||
if (args.Handled)
|
||
return;
|
||
|
||
if (!_blobCoreSystem.TryGetTargetBlobTile(args, out var blobTile))
|
||
return;
|
||
|
||
if (blobTile == null || !TryComp<BlobFactoryComponent>(blobTile, out var blobFactoryComponent))
|
||
{
|
||
_popup.PopupEntity(Loc.GetString("blob-target-factory-blob-invalid"), args.Performer, args.Performer, PopupType.LargeCaution);
|
||
return;
|
||
}
|
||
|
||
if (blobFactoryComponent.Blobbernaut != null)
|
||
{
|
||
_popup.PopupEntity(Loc.GetString("blob-target-already-produce-blobbernaut"), args.Performer, args.Performer, PopupType.LargeCaution);
|
||
return;
|
||
}
|
||
|
||
if (!_blobCoreSystem.TryUseAbility((uid, blobCoreComponent), blobCoreComponent.BlobbernautCost, args.Target.AlignWithClosestGridTile()))
|
||
return;
|
||
|
||
var ev = new ProduceBlobbernautEvent();
|
||
RaiseLocalEvent(blobTile.Value, ev);
|
||
|
||
_popup.PopupEntity(Loc.GetString("blob-spent-resource", ("point", blobCoreComponent.BlobbernautCost)),
|
||
blobTile.Value,
|
||
uid,
|
||
PopupType.LargeCaution);
|
||
|
||
args.Handled = true;
|
||
}
|
||
|
||
private void OnBlobToCore(EntityUid uid,
|
||
BlobCoreComponent blobCoreComponent,
|
||
BlobToCoreActionEvent args)
|
||
{
|
||
if (args.Handled)
|
||
return;
|
||
|
||
_transform.SetCoordinates(args.Performer, Transform(uid).Coordinates);
|
||
}
|
||
}
|