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>
349 lines
12 KiB
C#
349 lines
12 KiB
C#
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Content.Server.Atmos.Components;
|
|
using Content.Server.Atmos.EntitySystems;
|
|
using Content.Server.Destructible;
|
|
using Content.Server.Emp;
|
|
using Content.Server.Explosion.EntitySystems;
|
|
using Content.Server.Popups;
|
|
using Content.Shared._Goobstation.Blob;
|
|
using Content.Shared._Goobstation.Blob.Components;
|
|
using Content.Shared._Goobstation.CCVar;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Item;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.SubFloor;
|
|
using Robust.Server.Audio;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.CPUJob.JobQueues;
|
|
using Robust.Shared.CPUJob.JobQueues.Queues;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Server._Goobstation.Blob.Systems;
|
|
|
|
public sealed class BlobCoreActionSystem : SharedBlobCoreActionSystem
|
|
{
|
|
[Dependency] private readonly PopupSystem _popup = default!;
|
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
[Dependency] private readonly IRobustRandom _random = default!;
|
|
[Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!;
|
|
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
|
[Dependency] private readonly FlammableSystem _flammable = default!;
|
|
[Dependency] private readonly EmpSystem _empSystem = default!;
|
|
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
|
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
|
[Dependency] private readonly MapSystem _mapSystem = default!;
|
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
|
[Dependency] private readonly BlobTileSystem _blobTileSystem = default!;
|
|
//[Dependency] private readonly GridFixtureSystem _gridFixture = default!;
|
|
|
|
private const double ActionJobTime = 0.005;
|
|
private readonly JobQueue _actionJobQueue = new(ActionJobTime);
|
|
|
|
private bool _canGrowInSpace = true;
|
|
private EntityQuery<BlobTileComponent> _tileQuery;
|
|
private EntityQuery<BlobCoreComponent> _blobCoreQuery;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<BlobObserverControllerComponent, AfterInteractEvent>(OnInteractController);
|
|
SubscribeLocalEvent<BlobObserverComponent, UserActivateInWorldEvent>(OnInteractTarget);
|
|
|
|
Subs.CVar(_cfg, GoobCVars.BlobCanGrowInSpace, value => _canGrowInSpace = value, true);
|
|
_tileQuery = GetEntityQuery<BlobTileComponent>();
|
|
_blobCoreQuery = GetEntityQuery<BlobCoreComponent>();
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
_actionJobQueue.Process();
|
|
}
|
|
|
|
public sealed class BlobMouseActionProcess(
|
|
Entity<BlobObserverComponent> ent,
|
|
Entity<BlobCoreComponent> core,
|
|
BlobCoreActionSystem system,
|
|
InteractEvent args,
|
|
double maxTime,
|
|
CancellationToken cancellation = default)
|
|
: Job<object>(maxTime, cancellation)
|
|
{
|
|
protected override async Task<object?> Process()
|
|
{
|
|
system.BlobInteract(ent, core, args);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void BlobInteract(Entity<BlobObserverComponent> observer, Entity<BlobCoreComponent> core, InteractEvent args)
|
|
{
|
|
if (TerminatingOrDeleted(observer) || TerminatingOrDeleted(core))
|
|
return;
|
|
|
|
var target = args.Target;
|
|
var location = args.ClickLocation.AlignWithClosestGridTile(entityManager: EntityManager, mapManager: _mapManager);
|
|
|
|
if (!location.IsValid(EntityManager))
|
|
return;
|
|
|
|
var gridUid = _transform.GetGrid(location);
|
|
|
|
if (!TryComp<MapGridComponent>(gridUid, out var grid))
|
|
return;
|
|
|
|
|
|
var fromTile = FindNearBlobTile(location, (gridUid.Value, grid));
|
|
var targetTile = _mapSystem.GetTileRef(gridUid.Value, grid, location);
|
|
var node = _blobCoreSystem.GetNearNode(location, core.Comp.TilesRadiusLimit);
|
|
|
|
if (fromTile != null && node == null)
|
|
_popup.PopupCoordinates(Loc.GetString("blob-target-nearby-not-node"), location, args.User, PopupType.Large);
|
|
|
|
if (fromTile == null || node == null)
|
|
return;
|
|
|
|
var targetEnts = _mapSystem.GetAnchoredEntities(gridUid.Value, grid, targetTile.GridIndices);
|
|
bool growTile = true;
|
|
|
|
foreach (var targetEntity in targetEnts)
|
|
{
|
|
if (TryComp<PhysicsComponent>(targetEntity, out var physics) &&
|
|
physics is { Hard: true, CanCollide: true } &&
|
|
HasComp<DestructibleComponent>(targetEntity))
|
|
{
|
|
target = targetEntity;
|
|
}
|
|
|
|
if (_tileQuery.HasComp(targetEntity))
|
|
{
|
|
growTile = false;
|
|
target = args.Target;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Handle target attack.
|
|
// Only hard objects should be attacked.
|
|
|
|
if (target != null &&
|
|
TryComp<PhysicsComponent>(target, out var physicsTarget) &&
|
|
physicsTarget is { Hard: true, CanCollide: true } &&
|
|
HasComp<DestructibleComponent>(target) &&
|
|
!HasComp<SubFloorHideComponent>(target))
|
|
{
|
|
// Things that we can't attack, including our own tiles.
|
|
|
|
if (HasComp<ItemComponent>(target) ||
|
|
HasComp<BlobMobComponent>(target) ||
|
|
_tileQuery.TryComp(target, out var targetComp) && targetComp.Core != null)
|
|
return;
|
|
|
|
BlobTargetAttack(core, fromTile.Value, target.Value);
|
|
return;
|
|
}
|
|
|
|
if (!growTile)
|
|
return;
|
|
|
|
var targetTileEmpty = false;
|
|
if (targetTile.Tile.IsEmpty)
|
|
{
|
|
if (!_canGrowInSpace)
|
|
return;
|
|
|
|
targetTileEmpty = true;
|
|
}
|
|
|
|
// This code doesn't work.
|
|
// It should merge two grids together if blob clicks from one grid to another.
|
|
// If you can debug this, please do and fix it.
|
|
|
|
/*if (targetTileEmpty)
|
|
{
|
|
var mapPos = _transform.ToMapCoordinates(location);
|
|
var adjacentPos = new[]
|
|
{
|
|
Direction.East,
|
|
Direction.West,
|
|
Direction.North,
|
|
Direction.South
|
|
};
|
|
|
|
var tiles = new HashSet<Entity<BlobTileComponent>>();
|
|
foreach (var dir in adjacentPos)
|
|
{
|
|
tiles.Clear();
|
|
|
|
_lookup.GetEntitiesIntersecting(pos.MapId,
|
|
new Box2(pos.Position, pos.Position),
|
|
tiles,
|
|
LookupFlags.Static);
|
|
|
|
if (tiles.Count == 0)
|
|
continue;
|
|
|
|
var tile = tiles.First();
|
|
var tilePos = Transform(tile);
|
|
|
|
if (tilePos.GridUid == gridUid || tilePos.GridUid == null ||
|
|
!TryComp<MapGridComponent>(tilePos.GridUid, out var tileGrid))
|
|
continue;
|
|
|
|
var locPos = _mapSystem.WorldToLocal(tilePos.GridUid.Value,
|
|
tileGrid,
|
|
mapPos.Position + dir.GetOpposite().ToVec());
|
|
|
|
_gridFixture.Merge(tilePos.GridUid.Value,
|
|
gridUid.Value,
|
|
(Vector2i)locPos,
|
|
Transform(gridUid.Value).LocalRotation);
|
|
}
|
|
}*/
|
|
|
|
var cost = core.Comp.BlobTileCosts[BlobTileType.Normal];
|
|
if (targetTileEmpty)
|
|
{
|
|
cost *= 2.5f;
|
|
|
|
var plating = _tileDefinitionManager["Plating"];
|
|
var platingTile = new Tile(plating.TileId);
|
|
_mapSystem.SetTile(gridUid.Value, grid, location, platingTile);
|
|
}
|
|
|
|
if (!_blobCoreSystem.TryUseAbility(core, cost, location))
|
|
return;
|
|
|
|
_blobCoreSystem.TransformBlobTile(null,
|
|
core,
|
|
node,
|
|
BlobTileType.Normal,
|
|
location);
|
|
|
|
core.Comp.NextAction = _gameTiming.CurTime + GCd + TimeSpan.FromSeconds(Math.Abs(core.Comp.GrowRate));
|
|
}
|
|
|
|
private EntityUid? FindNearBlobTile(EntityCoordinates coords, Entity<MapGridComponent> grid)
|
|
{
|
|
var mobTile = _mapSystem.GetTileRef(grid, grid, coords);
|
|
|
|
var adjacentTiles = new[]
|
|
{
|
|
mobTile.GridIndices.Offset(Direction.East),
|
|
mobTile.GridIndices.Offset(Direction.West),
|
|
mobTile.GridIndices.Offset(Direction.North),
|
|
mobTile.GridIndices.Offset(Direction.South),
|
|
};
|
|
|
|
foreach (var indices in adjacentTiles)
|
|
{
|
|
var uid = _mapSystem.GetAnchoredEntities(grid, grid, indices)
|
|
.Where(_tileQuery.HasComponent)
|
|
.FirstOrNull();
|
|
|
|
// Don't count dead tiles
|
|
if (uid == null || _tileQuery.Comp(uid.Value).Core == null)
|
|
continue;
|
|
|
|
return uid;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void BlobTargetAttack(Entity<BlobCoreComponent> ent, Entity<BlobTileComponent?> from, EntityUid target)
|
|
{
|
|
if (ent.Comp.Observer == null)
|
|
return;
|
|
|
|
if (!_blobCoreSystem.TryUseAbility(ent, ent.Comp.AttackCost, Transform(target).Coordinates))
|
|
return;
|
|
|
|
_blobTileSystem.DoLunge(from, target);
|
|
_damageableSystem.TryChangeDamage(target, ent.Comp.ChemDamageDict[ent.Comp.CurrentChem]);
|
|
|
|
switch (ent.Comp.CurrentChem)
|
|
{
|
|
case BlobChemType.ExplosiveLattice:
|
|
_explosionSystem.QueueExplosion(target, ent.Comp.BlobExplosive, 4, 1, 6, maxTileBreak: 0, user: ent.Comp.Observer ?? ent);
|
|
break;
|
|
case BlobChemType.ElectromagneticWeb:
|
|
{
|
|
if (_random.Prob(0.2f))
|
|
_empSystem.EmpPulse(_transform.GetMapCoordinates(target), 3f, 50f, 3f);
|
|
break;
|
|
}
|
|
case BlobChemType.BlazingOil:
|
|
{
|
|
if (TryComp<FlammableComponent>(target, out var flammable))
|
|
{
|
|
flammable.FireStacks += 2;
|
|
_flammable.Ignite(target, from, flammable);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
ent.Comp.NextAction = _gameTiming.CurTime + GCd + TimeSpan.FromSeconds(Math.Abs(ent.Comp.AttackRate));
|
|
_audioSystem.PlayPvs(ent.Comp.AttackSound, from, AudioParams.Default);
|
|
}
|
|
|
|
private static readonly TimeSpan GCd = TimeSpan.FromMilliseconds(333); // GCD?
|
|
|
|
private void OnInteract(EntityUid uid, BlobObserverComponent observerComponent, AfterInteractEvent args)
|
|
{
|
|
if (args.Target == args.User)
|
|
return;
|
|
|
|
if (observerComponent.Core == null ||
|
|
!_blobCoreQuery.TryComp(observerComponent.Core.Value, out var blobCoreComponent))
|
|
return;
|
|
|
|
if (_gameTiming.CurTime < blobCoreComponent.NextAction)
|
|
return;
|
|
|
|
var location = args.ClickLocation;
|
|
if (!location.IsValid(EntityManager))
|
|
return;
|
|
|
|
args.Handled = true;
|
|
blobCoreComponent.NextAction = _gameTiming.CurTime + GCd;
|
|
|
|
_actionJobQueue.EnqueueJob(new BlobMouseActionProcess(
|
|
(uid, observerComponent),
|
|
(observerComponent.Core.Value, blobCoreComponent),
|
|
this,
|
|
args,
|
|
ActionJobTime
|
|
));
|
|
}
|
|
private void OnInteractTarget(Entity<BlobObserverComponent> ent, ref UserActivateInWorldEvent args)
|
|
{
|
|
var ev = new AfterInteractEvent(args.User, EntityUid.Invalid, args.Target, Transform(args.Target).Coordinates, true);
|
|
OnInteract(ent, ent, ev); // proxy?
|
|
args.Handled = ev.Handled;
|
|
}
|
|
private void OnInteractController(Entity<BlobObserverControllerComponent> ent, ref AfterInteractEvent args)
|
|
{
|
|
var ev = new AfterInteractEvent(args.User, EntityUid.Invalid, args.Target, args.ClickLocation, true);
|
|
OnInteract(ent.Comp.Blob, ent.Comp.Blob, ev); // proxy?
|
|
args.Handled = ev.Handled;
|
|
}
|
|
}
|