Files
wwdpublic/Content.Server/_Goobstation/Blob/Systems/BlobTileSystem.cs
Kyoth25f ed2301f840 Port Blob (#2441)
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>
2025-07-12 12:20:25 +10:00

233 lines
8.3 KiB
C#

using System.Linq;
using System.Numerics;
using Content.Server.Construction.Components;
using Content.Server.Destructible;
using Content.Server.Emp;
using Content.Shared._Goobstation.Blob;
using Content.Shared._Goobstation.Blob.Components;
using Content.Shared.Damage;
using Content.Shared.Destructible;
using Content.Shared.Mobs.Components;
using Content.Shared.NPC.Components;
using Content.Shared.NPC.Prototypes;
using Content.Shared.NPC.Systems;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server._Goobstation.Blob.Systems;
public sealed class BlobTileSystem : SharedBlobTileSystem
{
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly EmpSystem _empSystem = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly NpcFactionSystem _npcFactionSystem = default!;
private EntityQuery<BlobCoreComponent> _blobCoreQuery;
[ValidatePrototypeId<NpcFactionPrototype>]
private const string BlobFaction = "Blob";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BlobTileComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<BlobTileComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<BlobTileComponent, BlobTileGetPulseEvent>(OnPulsed);
SubscribeLocalEvent<BlobTileComponent, EntityTerminatingEvent>(OnTerminate);
_blobCoreQuery = GetEntityQuery<BlobCoreComponent>();
}
private void OnMapInit(Entity<BlobTileComponent> ent, ref MapInitEvent args)
{
var faction = EnsureComp<NpcFactionMemberComponent>(ent);
Entity<NpcFactionMemberComponent?> factionEnt = (ent, faction);
_npcFactionSystem.ClearFactions(factionEnt, false);
_npcFactionSystem.AddFaction(factionEnt, BlobFaction);
// make alive - true for npc combat
EnsureComp<MobStateComponent>(ent);
EnsureComp<MobThresholdsComponent>(ent);
}
private void OnTerminate(EntityUid uid, BlobTileComponent component, EntityTerminatingEvent args)
{
if (TerminatingOrDeleted(component.Core))
return;
component.Core!.Value.Comp.BlobTiles.Remove(uid);
}
private void OnDestruction(EntityUid uid, BlobTileComponent component, DestructionEventArgs args)
{
if (TerminatingOrDeleted(component.Core) ||
!_blobCoreQuery.TryComp(component.Core, out var blobCoreComponent))
return;
if (blobCoreComponent.CurrentChem == BlobChemType.ElectromagneticWeb)
{
_empSystem.EmpPulse(_transform.GetMapCoordinates(uid), 3f, 50f, 3f);
}
}
private void OnPulsed(EntityUid uid, BlobTileComponent component, BlobTileGetPulseEvent args)
{
if (component.Core == null)
return;
var core = component.Core.Value;
var xform = Transform(uid);
HealTile((uid, component), core);
if (!args.Handled)
return;
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
{
return;
}
var nearNode = _blobCoreSystem.GetNearNode(xform.Coordinates, core.Comp.TilesRadiusLimit);
if (nearNode == null)
return;
var mobTile = _mapSystem.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
var mobAdjacentTiles = new[]
{
mobTile.GridIndices.Offset(Direction.East),
mobTile.GridIndices.Offset(Direction.West),
mobTile.GridIndices.Offset(Direction.North),
mobTile.GridIndices.Offset(Direction.South),
};
_random.Shuffle(mobAdjacentTiles);
var localPos = xform.Coordinates.Position;
var radius = 1.0f;
var innerTiles = _mapSystem.GetLocalTilesIntersecting(xform.GridUid.Value,
grid,
new Box2(localPos + new Vector2(-radius, -radius), localPos + new Vector2(radius, radius)))
.ToArray();
foreach (var innerTile in innerTiles)
{
if (!mobAdjacentTiles.Contains(innerTile.GridIndices))
{
continue;
}
foreach (var ent in _mapSystem.GetAnchoredEntities(xform.GridUid.Value, grid, innerTile.GridIndices))
{
if (!HasComp<DestructibleComponent>(ent) || !HasComp<ConstructionComponent>(ent))
continue;
DoLunge(uid, ent);
_damageableSystem.TryChangeDamage(ent, core.Comp.ChemDamageDict[core.Comp.CurrentChem]);
_audioSystem.PlayPvs(core.Comp.AttackSound, uid, AudioParams.Default);
args.Handled = true;
return;
}
var spawn = true;
foreach (var ent in _mapSystem.GetAnchoredEntities(xform.GridUid.Value, grid, innerTile.GridIndices))
{
if (!HasComp<BlobTileComponent>(ent))
continue;
spawn = false;
break;
}
if (!spawn)
continue;
var location = _mapSystem.ToCoordinates(xform.GridUid.Value, innerTile.GridIndices, grid);
if (_blobCoreSystem.TransformBlobTile(null,
core,
nearNode,
BlobTileType.Normal,
location))
return;
}
}
private void HealTile(Entity<BlobTileComponent> ent, Entity<BlobCoreComponent> core)
{
var healCore = new DamageSpecifier();
var modifier = 1.0f;
if (core.Comp.CurrentChem == BlobChemType.RegenerativeMateria)
{
modifier = 2.0f;
}
foreach (var keyValuePair in ent.Comp.HealthOfPulse.DamageDict)
{
healCore.DamageDict.TryAdd(keyValuePair.Key, keyValuePair.Value * modifier);
}
_damageableSystem.TryChangeDamage(ent, healCore);
}
protected override void TryUpgrade(Entity<BlobTileComponent, BlobUpgradeableTileComponent> target, Entity<BlobCoreComponent> core, EntityUid observer)
{
var ev = new BlobTransformTileActionEvent(
performer: observer,
target: Transform(target).Coordinates,
transformFrom: target.Comp1.BlobTileType,
tileType: target.Comp2.TransformTo);
RaiseLocalEvent(core, ev);
}
public void SwapSpecials(Entity<BlobNodeComponent> from, Entity<BlobNodeComponent> to)
{
(from.Comp.BlobFactory, to.Comp.BlobFactory) = (to.Comp.BlobFactory, from.Comp.BlobFactory);
(from.Comp.BlobResource, to.Comp.BlobResource) = (to.Comp.BlobResource, from.Comp.BlobResource);
(from.Comp.BlobStorage, to.Comp.BlobStorage) = (to.Comp.BlobStorage, from.Comp.BlobStorage);
(from.Comp.BlobTurret, to.Comp.BlobTurret) = (to.Comp.BlobTurret, from.Comp.BlobTurret);
Dirty(from);
Dirty(to);
}
public bool IsEmptySpecial(Entity<BlobNodeComponent> node, BlobTileType tile)
{
return tile switch
{
BlobTileType.Factory => node.Comp.BlobFactory == null || TerminatingOrDeleted(node.Comp.BlobFactory),
BlobTileType.Resource => node.Comp.BlobResource == null || TerminatingOrDeleted(node.Comp.BlobResource),
BlobTileType.Storage => node.Comp.BlobStorage == null || TerminatingOrDeleted(node.Comp.BlobStorage),
BlobTileType.Turret => node.Comp.BlobTurret == null || TerminatingOrDeleted(node.Comp.BlobTurret),
_ => false
};
}
public void DoLunge(EntityUid from, EntityUid target)
{
if(!TransformQuery.TryComp(from, out var userXform))
return;
var targetPos = _transform.GetWorldPosition(target);
var localPos = Vector2.Transform(targetPos, _transform.GetInvWorldMatrix(userXform));
localPos = userXform.LocalRotation.RotateVec(localPos);
RaiseNetworkEvent(new BlobAttackEvent(GetNetEntity(from), GetNetEntity(target), localPos), Filter.Pvs(from));
}
}