diff --git a/Content.Client/Blob/BlobChemSwapBoundUserInterface.cs b/Content.Client/Blob/BlobChemSwapBoundUserInterface.cs new file mode 100644 index 00000000000..60454ea9030 --- /dev/null +++ b/Content.Client/Blob/BlobChemSwapBoundUserInterface.cs @@ -0,0 +1,51 @@ +using Content.Shared.Blob; +using JetBrains.Annotations; +using Robust.Client.GameObjects; + +namespace Content.Client.Blob; + +[UsedImplicitly] +public sealed class BlobChemSwapBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private BlobChemSwapMenu? _menu; + + public BlobChemSwapBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _menu = new BlobChemSwapMenu(); + _menu.OnClose += Close; + _menu.OnIdSelected += OnIdSelected; + _menu.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is not BlobChemSwapBoundUserInterfaceState st) + return; + + _menu?.UpdateState(st.ChemList, st.SelectedChem); + } + + private void OnIdSelected(BlobChemType selectedId) + { + SendMessage(new BlobChemSwapPrototypeSelectedMessage(selectedId)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _menu?.Close(); + _menu = null; + } + } +} diff --git a/Content.Client/Blob/BlobChemSwapMenu.xaml b/Content.Client/Blob/BlobChemSwapMenu.xaml new file mode 100644 index 00000000000..297b7aeb8e9 --- /dev/null +++ b/Content.Client/Blob/BlobChemSwapMenu.xaml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/Content.Client/Blob/BlobChemSwapMenu.xaml.cs b/Content.Client/Blob/BlobChemSwapMenu.xaml.cs new file mode 100644 index 00000000000..3732d19cb01 --- /dev/null +++ b/Content.Client/Blob/BlobChemSwapMenu.xaml.cs @@ -0,0 +1,77 @@ +using System.Numerics; +using Content.Client.Stylesheets; +using Content.Shared.Blob; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Blob; + +[GenerateTypedNameReferences] +public sealed partial class BlobChemSwapMenu : DefaultWindow +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly SpriteSystem _sprite; + public event Action? OnIdSelected; + + private Dictionary _possibleChems = new(); + private BlobChemType _selectedId; + + public BlobChemSwapMenu() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _sprite = _entityManager.System(); + } + + public void UpdateState(Dictionary chemList, BlobChemType selectedChem) + { + _possibleChems = chemList; + _selectedId = selectedChem; + UpdateGrid(); + } + + private void UpdateGrid() + { + ClearGrid(); + + var group = new ButtonGroup(); + + foreach (var blobChem in _possibleChems) + { + if (!_prototypeManager.TryIndex("NormalBlobTile", out EntityPrototype? proto)) + continue; + + var button = new Button + { + MinSize = new Vector2(64, 64), + HorizontalExpand = true, + Group = group, + StyleClasses = {StyleBase.ButtonSquare}, + ToggleMode = true, + Pressed = _selectedId == blobChem.Key, + ToolTip = Loc.GetString($"blob-chem-{blobChem.Key.ToString().ToLower()}-info"), + TooltipDelay = 0.01f, + }; + button.OnPressed += _ => OnIdSelected?.Invoke(blobChem.Key); + Grid.AddChild(button); + + var texture = _sprite.GetPrototypeIcon(proto); + button.AddChild(new TextureRect + { + Stretch = TextureRect.StretchMode.KeepAspectCentered, + Modulate = blobChem.Value, + Texture = texture.Default, + }); + } + } + + private void ClearGrid() + { + Grid.RemoveAllChildren(); + } +} diff --git a/Content.Client/Blob/BlobObserverSystem.cs b/Content.Client/Blob/BlobObserverSystem.cs new file mode 100644 index 00000000000..32dc213e333 --- /dev/null +++ b/Content.Client/Blob/BlobObserverSystem.cs @@ -0,0 +1,45 @@ +using Content.Shared.Blob; +using Content.Shared.GameTicking; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.GameStates; +using Robust.Shared.Player; + +namespace Content.Client.Blob; + +public sealed class BlobObserverSystem : SharedBlobObserverSystem +{ + [Dependency] private readonly ILightManager _lightManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandleState); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeNetworkEvent(RoundRestartCleanup); + } + + private void HandleState(EntityUid uid, BlobObserverComponent component, ref ComponentHandleState args) + { + if (args.Current is not BlobChemSwapComponentState state) + return; + component.SelectedChemId = state.SelectedChem; + } + + private void OnPlayerAttached(EntityUid uid, BlobObserverComponent component, PlayerAttachedEvent args) + { + _lightManager.DrawLighting = false; + } + + private void OnPlayerDetached(EntityUid uid, BlobObserverComponent component, PlayerDetachedEvent args) + { + _lightManager.DrawLighting = true; + } + + private void RoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _lightManager.DrawLighting = true; + } +} diff --git a/Content.Client/Blob/BlobTileComponent.cs b/Content.Client/Blob/BlobTileComponent.cs new file mode 100644 index 00000000000..f30e75c83ca --- /dev/null +++ b/Content.Client/Blob/BlobTileComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Blob; + +namespace Content.Client.Blob; + +[RegisterComponent] +public partial class BlobTileComponent : SharedBlobTileComponent +{ + +} diff --git a/Content.Client/Blob/BlobTileSystem.cs b/Content.Client/Blob/BlobTileSystem.cs new file mode 100644 index 00000000000..28de9f916c6 --- /dev/null +++ b/Content.Client/Blob/BlobTileSystem.cs @@ -0,0 +1,38 @@ +using Content.Client.DamageState; +using Content.Shared.Blob; +using Robust.Client.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Client.Blob; + +public sealed class BlobTileSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnBlobTileHandleState); + } + + private void OnBlobTileHandleState(EntityUid uid, BlobTileComponent component, ref ComponentHandleState args) + { + if (args.Current is not BlobTileComponentState state) + return; + + if (component.Color == state.Color) + return; + + component.Color = state.Color; + TryComp(uid, out var sprite); + + if (sprite == null) + return; + + foreach (var key in new []{ DamageStateVisualLayers.Base, DamageStateVisualLayers.BaseUnshaded }) + { + if (!sprite.LayerMapTryGet(key, out _)) + continue; + + sprite.LayerSetColor(key, component.Color); + } + } +} diff --git a/Content.Client/Blob/BlobbernautComponent.cs b/Content.Client/Blob/BlobbernautComponent.cs new file mode 100644 index 00000000000..fb7430736e7 --- /dev/null +++ b/Content.Client/Blob/BlobbernautComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Blob; + +namespace Content.Client.Blob; + +[RegisterComponent] +public partial class BlobbernautComponent : SharedBlobbernautComponent +{ + +} diff --git a/Content.Client/Blob/BlobbernautSystem.cs b/Content.Client/Blob/BlobbernautSystem.cs new file mode 100644 index 00000000000..9a757ca43a5 --- /dev/null +++ b/Content.Client/Blob/BlobbernautSystem.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Content.Client.DamageState; +using Content.Shared.Blob; +using Robust.Client.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Client.Blob; + +public sealed class BlobbernautSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnBlobTileHandleState); + } + + private void OnBlobTileHandleState(EntityUid uid, BlobbernautComponent component, ref ComponentHandleState args) + { + if (args.Current is not BlobbernautComponentState state) + return; + + if (component.Color == state.Color) + return; + + component.Color = state.Color; + TryComp(uid, out var sprite); + + if (sprite == null) + return; + + foreach (var key in new []{ DamageStateVisualLayers.Base }) + { + if (!sprite.LayerMapTryGet(key, out _)) + continue; + + sprite.LayerSetColor(key, component.Color); + } + } +} diff --git a/Content.Client/Chemistry/Components/SmokeComponent.cs b/Content.Client/Chemistry/Components/SmokeComponent.cs new file mode 100644 index 00000000000..d57e17266ec --- /dev/null +++ b/Content.Client/Chemistry/Components/SmokeComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Chemistry.Components; + +namespace Content.Client.Chemistry.Components; + +[RegisterComponent] +public partial class SmokeComponent : SharedSmokeComponent +{ + +} diff --git a/Content.Client/Chemistry/Components/SmokeSystem.cs b/Content.Client/Chemistry/Components/SmokeSystem.cs new file mode 100644 index 00000000000..65ecc5bf79e --- /dev/null +++ b/Content.Client/Chemistry/Components/SmokeSystem.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Content.Shared.Chemistry.Components; +using Robust.Client.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Client.Chemistry.Components; + +public sealed class SmokeSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnBlobTileHandleState); + } + + private void OnBlobTileHandleState(EntityUid uid, SmokeComponent component, ref ComponentHandleState args) + { + if (args.Current is not SmokeComponentState state) + return; + + if (component.Color == state.Color) + return; + + component.Color = state.Color; + TryComp(uid, out var sprite); + + if (sprite == null) + return; + + for (var i = 0; i < sprite.AllLayers.Count(); i++) + { + sprite.LayerSetColor(i, component.Color); + } + } +} diff --git a/Content.Server/Blob/BlobBorderComponent.cs b/Content.Server/Blob/BlobBorderComponent.cs new file mode 100644 index 00000000000..3ef032851eb --- /dev/null +++ b/Content.Server/Blob/BlobBorderComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.Blob +{ + [RegisterComponent] + public sealed class BlobBorderComponent : Component + { + + } +} diff --git a/Content.Server/Blob/BlobCarrierSystem.cs b/Content.Server/Blob/BlobCarrierSystem.cs new file mode 100644 index 00000000000..cca11668052 --- /dev/null +++ b/Content.Server/Blob/BlobCarrierSystem.cs @@ -0,0 +1,131 @@ +using Content.Server.Actions; +using Content.Shared.Mind.Components; +using Content.Shared.Actions.ActionTypes; +using Content.Server.Body.Systems; +using Content.Server.Ghost.Roles.Components; +using Content.Server.Mind; +using Content.Shared.Blob; +using Content.Shared.Mobs; +using Content.Shared.Popups; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server.Blob +{ + public sealed class BlobCarrierSystem : EntitySystem + { + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly ActionsSystem _action = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnTransformToBlobChanged); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnMindAdded); + SubscribeLocalEvent(OnMindRemove); + } + + private void OnMindAdded(EntityUid uid, BlobCarrierComponent component, MindAddedMessage args) + { + component.HasMind = true; + } + + private void OnMindRemove(EntityUid uid, BlobCarrierComponent component, MindRemovedMessage args) + { + component.HasMind = false; + } + + private void OnTransformToBlobChanged(EntityUid uid, BlobCarrierComponent component, TransformToBlobActionEvent args) + { + TransformToBlob(uid, component); + } + + private void OnStartup(EntityUid uid, BlobCarrierComponent component, ComponentStartup args) + { + var transformToBlob = "TransformToBlob"; + _action.AddAction(uid, transformToBlob); + var ghostRole = EnsureComp(uid); + EnsureComp(uid); + ghostRole.RoleName = Loc.GetString("blob-carrier-role-name"); + ghostRole.RoleDescription = Loc.GetString("blob-carrier-role-desc"); + ghostRole.RoleRules = Loc.GetString("blob-carrier-role-rules"); + } + + private void OnShutdown(EntityUid uid, BlobCarrierComponent component, ComponentShutdown args) + { + + } + + private void OnMobStateChanged(EntityUid uid, BlobCarrierComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + { + TransformToBlob(uid, component); + } + } + + private void TransformToBlob(EntityUid uid, BlobCarrierComponent carrier) + { + var xform = Transform(uid); + if (!_mapManager.TryGetGrid(xform.GridUid, out var map)) + return; + + if (_mind.TryGetMind(uid, out var mind, out var mind1) && mind1.Session != null) + { + var core = Spawn(carrier.CoreBlobPrototype, xform.Coordinates); + + if (!TryComp(core, out var blobCoreComponent)) + return; + + _blobCoreSystem.CreateBlobObserver(core, mind.Session, blobCoreComponent); + } + else + { + Spawn(carrier.CoreBlobGhostRolePrototype, xform.Coordinates); + } + + _bodySystem.GibBody(uid); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var blobFactoryQuery = EntityQueryEnumerator(); + while (blobFactoryQuery.MoveNext(out var ent, out var comp)) + { + if (!comp.HasMind) + return; + + comp.TransformationTimer += frameTime; + + if (_gameTiming.CurTime < comp.NextAlert) + continue; + + var remainingTime = Math.Round(comp.TransformationDelay - comp.TransformationTimer, 0); + _popup.PopupEntity(Loc.GetString("carrier-blob-alert", ("second", remainingTime)), ent, ent, PopupType.LargeCaution); + + comp.NextAlert = _gameTiming.CurTime + TimeSpan.FromSeconds(comp.AlertInterval); + + if (!(comp.TransformationTimer >= comp.TransformationDelay)) + continue; + + TransformToBlob(ent, comp); + } + } + } +} diff --git a/Content.Server/Blob/BlobCoreSystem.cs b/Content.Server/Blob/BlobCoreSystem.cs new file mode 100644 index 00000000000..ba6ddd03cab --- /dev/null +++ b/Content.Server/Blob/BlobCoreSystem.cs @@ -0,0 +1,340 @@ +using System.Linq; +using System.Numerics; +using Content.Server.Chat.Managers; +using Content.Server.Explosion.Components; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Fluids.EntitySystems; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Mind; +using Content.Server.Objectives; +using Content.Server.Roles; +using Content.Shared.Alert; +using Content.Shared.Blob; +using Content.Shared.Damage; +using Content.Shared.Destructible; +using Content.Shared.FixedPoint; +using Content.Shared.Popups; +using Content.Shared.Roles; +using Content.Shared.Weapons.Melee; +using Robust.Server.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Robust.Shared.Player; +using Robust.Shared.Audio.Systems; + +namespace Content.Server.Blob; + +public sealed class BlobCoreSystem : EntitySystem +{ + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly BlobObserverSystem _blobObserver = default!; + [Dependency] private readonly ExplosionSystem _explosionSystem = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnDestruction); + SubscribeLocalEvent(OnDamaged); + SubscribeLocalEvent(OnPlayerAttached); + } + + private void OnPlayerAttached(EntityUid uid, BlobCoreComponent component, PlayerAttachedEvent args) + { + var xform = Transform(uid); + if (!_mapManager.TryGetGrid(xform.GridUid, out var map)) + return; + + CreateBlobObserver(uid, args.Player.UserId, component); + } + + public bool CreateBlobObserver(EntityUid blobCoreUid, NetUserId userId, BlobCoreComponent? core = null) + { + var xform = Transform(blobCoreUid); + + if (!Resolve(blobCoreUid, ref core)) + return false; + + var blobRule = EntityQuery().FirstOrDefault(); + + if (blobRule == null) + { + _gameTicker.StartGameRule("Blob", out var ruleEntity); + blobRule = Comp(ruleEntity); + } + + var observer = Spawn(core.ObserverBlobPrototype, xform.Coordinates); + + core.Observer = observer; + + if (!TryComp(observer, out var blobObserverComponent)) + return false; + + blobObserverComponent.Core = blobCoreUid; + + _mindSystem.TryGetMind(userId, out var mind); + if (mind == null) + return false; + + _mindSystem.TransferTo(mind, observer, ghostCheckOverride: false); + + _alerts.ShowAlert(observer, AlertType.BlobHealth, (short) Math.Clamp(Math.Round(core.CoreBlobTotalHealth.Float() / 10f), 0, 20)); + + var antagPrototype = _prototypeManager.Index(core.AntagBlobPrototypeId); + var blobRole = new BlobRole(mind, antagPrototype); + + _mindSystem.AddRole(mind, blobRole); + SendBlobBriefing(mind); + + blobRule.Blobs.Add(blobRole); + + if (_prototypeManager.TryIndex("BlobCaptureObjective", out var objective) + && objective.CanBeAssigned(mind)) + { + _mindSystem.TryAddObjective(blobRole.Mind, objective); + } + + if (_mindSystem.TryGetSession(mind, out var session)) + { + _audioSystem.PlayGlobal(core.GreetSoundNotification, session); + } + + _blobObserver.UpdateUi(observer, blobObserverComponent); + + return true; + } + + private void SendBlobBriefing(Mind.Mind mind) + { + if (_mindSystem.TryGetSession(mind, out var session)) + { + _chatManager.DispatchServerMessage(session, Loc.GetString("blob-role-greeting")); + } + } + + private void OnDamaged(EntityUid uid, BlobCoreComponent component, DamageChangedEvent args) + { + var maxHealth = component.CoreBlobTotalHealth; + var currentHealth = maxHealth - args.Damageable.TotalDamage; + + if (component.Observer != null) + _alerts.ShowAlert(component.Observer.Value, AlertType.BlobHealth, (short) Math.Clamp(Math.Round(currentHealth.Float() / 10f), 0, 20)); + } + + private void OnStartup(EntityUid uid, BlobCoreComponent component, ComponentStartup args) + { + ChangeBlobPoint(uid, 0, component); + + if (TryComp(uid, out var blobTileComponent)) + { + blobTileComponent.Core = uid; + blobTileComponent.Color = component.ChemСolors[component.CurrentChem]; + Dirty(blobTileComponent); + } + + component.BlobTiles.Add(uid); + + ChangeChem(uid, component.DefaultChem, component); + } + + public void ChangeChem(EntityUid uid, BlobChemType newChem, BlobCoreComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (newChem == component.CurrentChem) + return; + + var oldChem = component.CurrentChem; + component.CurrentChem = newChem; + foreach (var blobTile in component.BlobTiles) + { + if (!TryComp(blobTile, out var blobTileComponent)) + continue; + + blobTileComponent.Color = component.ChemСolors[newChem]; + Dirty(blobTileComponent); + + if (TryComp(blobTile, out var blobFactoryComponent)) + { + if (TryComp(blobFactoryComponent.Blobbernaut, out var blobbernautComponent)) + { + blobbernautComponent.Color = component.ChemСolors[newChem]; + Dirty(blobbernautComponent); + + if (TryComp(blobFactoryComponent.Blobbernaut, out var meleeWeaponComponent)) + { + var blobbernautDamage = new DamageSpecifier(); + foreach (var keyValuePair in component.ChemDamageDict[component.CurrentChem].DamageDict) + { + blobbernautDamage.DamageDict.Add(keyValuePair.Key, keyValuePair.Value * 0.8f); + } + meleeWeaponComponent.Damage = blobbernautDamage; + } + + ChangeBlobEntChem(blobFactoryComponent.Blobbernaut.Value, oldChem, newChem); + } + + foreach (var compBlobPod in blobFactoryComponent.BlobPods) + { + if (TryComp(compBlobPod, out var smokeOnTriggerComponent)) + { + smokeOnTriggerComponent.SmokeColor = component.ChemСolors[newChem]; + } + } + } + + ChangeBlobEntChem(blobTile, oldChem, newChem); + } + } + + private void OnDestruction(EntityUid uid, BlobCoreComponent component, DestructionEventArgs args) + { + if (component.Observer != null) + { + QueueDel(component.Observer.Value); + } + + foreach (var blobTile in component.BlobTiles) + { + if (!TryComp(blobTile, out var blobTileComponent)) + continue; + blobTileComponent.Core = null; + + blobTileComponent.Color = Color.White; + Dirty(blobTileComponent); + } + } + + private void ChangeBlobEntChem(EntityUid uid, BlobChemType oldChem, BlobChemType newChem) + { + var explosionResistance = EnsureComp(uid); + if (oldChem == BlobChemType.ExplosiveLattice) + { + _explosionSystem.SetExplosionResistance(uid, 0.3f, explosionResistance); + } + switch (newChem) + { + case BlobChemType.ExplosiveLattice: + _damageable.SetDamageModifierSetId(uid, "ExplosiveLatticeBlob"); + _explosionSystem.SetExplosionResistance(uid, 0f, explosionResistance); + break; + case BlobChemType.ElectromagneticWeb: + _damageable.SetDamageModifierSetId(uid, "ElectromagneticWebBlob"); + break; + case BlobChemType.ReactiveSpines: + _damageable.SetDamageModifierSetId(uid, "ReactiveSpinesBlob"); + break; + default: + _damageable.SetDamageModifierSetId(uid, "BaseBlob"); + break; + } + } + + public bool TransformBlobTile(EntityUid? oldTileUid, EntityUid coreTileUid, string newBlobTileProto, + EntityCoordinates coordinates, BlobCoreComponent? blobCore = null, bool returnCost = true, + FixedPoint2? transformCost = null) + { + if (!Resolve(coreTileUid, ref blobCore)) + return false; + if (oldTileUid != null) + { + QueueDel(oldTileUid.Value); + blobCore.BlobTiles.Remove(oldTileUid.Value); + } + var tileBlob = EntityManager.SpawnEntity(newBlobTileProto, coordinates); + + if (TryComp(tileBlob, out var blobTileComponent)) + { + blobTileComponent.ReturnCost = returnCost; + blobTileComponent.Core = coreTileUid; + blobTileComponent.Color = blobCore.ChemСolors[blobCore.CurrentChem]; + Dirty(blobTileComponent); + + var explosionResistance = EnsureComp(tileBlob); + + if (blobCore.CurrentChem == BlobChemType.ExplosiveLattice) + { + _explosionSystem.SetExplosionResistance(tileBlob, 0f, explosionResistance); + } + } + if (blobCore.Observer != null && transformCost != null) + { + _popup.PopupEntity(Loc.GetString("blob-spent-resource", ("point", transformCost)), + tileBlob, + blobCore.Observer.Value, + PopupType.LargeCaution); + } + blobCore.BlobTiles.Add(tileBlob); + return true; + } + + public bool RemoveBlobTile(EntityUid tileUid, EntityUid coreTileUid, BlobCoreComponent? blobCore = null) + { + if (!Resolve(coreTileUid, ref blobCore)) + return false; + + QueueDel(tileUid); + blobCore.BlobTiles.Remove(tileUid); + + return true; + } + + public bool ChangeBlobPoint(EntityUid uid, FixedPoint2 amount, BlobCoreComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + + component.Points += amount; + + if (component.Observer != null) + _alerts.ShowAlert(component.Observer.Value, AlertType.BlobResource, (short) Math.Clamp(Math.Round(component.Points.Float() / 10f), 0, 16)); + + return true; + } + + public bool TryUseAbility(EntityUid uid, EntityUid coreUid, BlobCoreComponent component, FixedPoint2 abilityCost) + { + if (component.Points < abilityCost) + { + _popup.PopupEntity(Loc.GetString("blob-not-enough-resources"), uid, uid, PopupType.Large); + return false; + } + + ChangeBlobPoint(coreUid, -abilityCost, component); + + return true; + } + + public bool CheckNearNode(EntityUid observer, EntityCoordinates coords, MapGridComponent grid, BlobCoreComponent core) + { + var radius = 3f; + + var innerTiles = grid.GetLocalTilesIntersecting( + new Box2(coords.Position + new Vector2(-radius, -radius), coords.Position + new Vector2(radius, radius)), false).ToArray(); + + foreach (var tileRef in innerTiles) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (HasComp(ent) || HasComp(ent)) + return true; + } + } + + _popup.PopupCoordinates(Loc.GetString("blob-target-nearby-not-node"), coords, observer, PopupType.Large); + return false; + } +} diff --git a/Content.Server/Blob/BlobFactoryComponent.cs b/Content.Server/Blob/BlobFactoryComponent.cs new file mode 100644 index 00000000000..8c19d819b3f --- /dev/null +++ b/Content.Server/Blob/BlobFactoryComponent.cs @@ -0,0 +1,32 @@ +namespace Content.Server.Blob; + +[RegisterComponent] +public sealed class BlobFactoryComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly)] + public float SpawnedCount = 0; + + [DataField("spawnLimit"), ViewVariables(VVAccess.ReadWrite)] + public float SpawnLimit = 3; + + [DataField("spawnRate"), ViewVariables(VVAccess.ReadWrite)] + public float SpawnRate = 10; + + [DataField("blobSporeId"), ViewVariables(VVAccess.ReadWrite)] + public string Pod = "MobBlobPod"; + + [DataField("blobbernautId"), ViewVariables(VVAccess.ReadWrite)] + public string BlobbernautId = "MobBlobBlobbernaut"; + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? Blobbernaut = default!; + + [ViewVariables(VVAccess.ReadOnly)] + public List BlobPods = new (); + + public TimeSpan NextSpawn = TimeSpan.Zero; +} + +public sealed class ProduceBlobbernautEvent : EntityEventArgs +{ +} diff --git a/Content.Server/Blob/BlobFactorySystem.cs b/Content.Server/Blob/BlobFactorySystem.cs new file mode 100644 index 00000000000..56cf444e7f7 --- /dev/null +++ b/Content.Server/Blob/BlobFactorySystem.cs @@ -0,0 +1,96 @@ +using Content.Server.Blob.NPC.BlobPod; +using Content.Server.Fluids.EntitySystems; +using Content.Shared.Blob; +using Content.Shared.Damage; +using Content.Shared.Destructible; +using Content.Shared.Weapons.Melee; +using Robust.Shared.Timing; + +namespace Content.Server.Blob; + +public sealed class BlobFactorySystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnPulsed); + SubscribeLocalEvent(OnProduceBlobbernaut); + SubscribeLocalEvent(OnDestruction); + } + + private void OnStartup(EntityUid uid, BlobFactoryComponent observerComponent, ComponentStartup args) + { + + } + + private void OnDestruction(EntityUid uid, BlobFactoryComponent component, DestructionEventArgs args) + { + if (TryComp(component.Blobbernaut, out var blobbernautComponent)) + { + blobbernautComponent.Factory = null; + } + } + + private void OnProduceBlobbernaut(EntityUid uid, BlobFactoryComponent component, ProduceBlobbernautEvent args) + { + if (component.Blobbernaut != null) + return; + + if (!TryComp(uid, out var blobTileComponent) || blobTileComponent.Core == null) + return; + + if (!TryComp(blobTileComponent.Core, out var blobCoreComponent)) + return; + + var xform = Transform(uid); + + var blobbernaut = Spawn(component.BlobbernautId, xform.Coordinates); + + component.Blobbernaut = blobbernaut; + if (TryComp(blobbernaut, out var blobbernautComponent)) + { + blobbernautComponent.Factory = uid; + blobbernautComponent.Color = blobCoreComponent.ChemСolors[blobCoreComponent.CurrentChem]; + Dirty(blobbernautComponent); + } + if (TryComp(blobbernaut, out var meleeWeaponComponent)) + { + var blobbernautDamage = new DamageSpecifier(); + foreach (var keyValuePair in blobCoreComponent.ChemDamageDict[blobCoreComponent.CurrentChem].DamageDict) + { + blobbernautDamage.DamageDict.Add(keyValuePair.Key, keyValuePair.Value * 0.8f); + } + meleeWeaponComponent.Damage = blobbernautDamage; + } + } + + private void OnPulsed(EntityUid uid, BlobFactoryComponent component, BlobTileGetPulseEvent args) + { + if (!TryComp(uid, out var blobTileComponent) || blobTileComponent.Core == null) + return; + + if (!TryComp(blobTileComponent.Core, out var blobCoreComponent)) + return; + + if (component.SpawnedCount >= component.SpawnLimit) + return; + + if (_gameTiming.CurTime < component.NextSpawn) + return; + + var xform = Transform(uid); + var pod = Spawn(component.Pod, xform.Coordinates); + component.BlobPods.Add(pod); + var blobPod = EnsureComp(pod); + blobPod.Core = blobTileComponent.Core.Value; + var smokeOnTrigger = EnsureComp(pod); + smokeOnTrigger.SmokeColor = blobCoreComponent.ChemСolors[blobCoreComponent.CurrentChem]; + component.SpawnedCount += 1; + component.NextSpawn = _gameTiming.CurTime + TimeSpan.FromSeconds(component.SpawnRate); + } + +} diff --git a/Content.Server/Blob/BlobMobComponent.cs b/Content.Server/Blob/BlobMobComponent.cs new file mode 100644 index 00000000000..0858bc6b691 --- /dev/null +++ b/Content.Server/Blob/BlobMobComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; + +namespace Content.Server.Blob; + +[RegisterComponent] +public sealed class BlobMobComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly), DataField("healthOfPulse")] + public DamageSpecifier HealthOfPulse = new() + { + DamageDict = new Dictionary + { + { "Blunt", -4 }, + { "Slash", -4 }, + { "Piercing", -4 }, + { "Heat", -4 }, + { "Cold", -4 }, + { "Shock", -4 }, + } + }; +} diff --git a/Content.Server/Blob/BlobMobSystem.cs b/Content.Server/Blob/BlobMobSystem.cs new file mode 100644 index 00000000000..500e07433e7 --- /dev/null +++ b/Content.Server/Blob/BlobMobSystem.cs @@ -0,0 +1,36 @@ +using Content.Server.Popups; +using Content.Shared.Damage; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; + +namespace Content.Server.Blob +{ + public sealed class BlobMobSystem : EntitySystem + { + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPulsed); + SubscribeLocalEvent(OnBlobAttackAttempt); + } + + private void OnPulsed(EntityUid uid, BlobMobComponent component, BlobMobGetPulseEvent args) + { + _damageableSystem.TryChangeDamage(uid, component.HealthOfPulse); + } + + private void OnBlobAttackAttempt(EntityUid uid, BlobMobComponent component, AttackAttemptEvent args) + { + if (args.Cancelled || !HasComp(args.Target) && !HasComp(args.Target)) + return; + + // TODO: Move this to shared + _popupSystem.PopupCursor(Loc.GetString("blob-mob-attack-blob"), uid, PopupType.Large); + args.Cancel(); + } + } +} diff --git a/Content.Server/Blob/BlobNodeComponent.cs b/Content.Server/Blob/BlobNodeComponent.cs new file mode 100644 index 00000000000..0448a43a4b0 --- /dev/null +++ b/Content.Server/Blob/BlobNodeComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.FixedPoint; + +namespace Content.Server.Blob; + +[RegisterComponent] +public sealed class BlobNodeComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite), DataField("pulseFrequency")] + public float PulseFrequency = 4; + + [ViewVariables(VVAccess.ReadWrite), DataField("pulseRadius")] + public float PulseRadius = 3f; + + public TimeSpan NextPulse = TimeSpan.Zero; +} + +public sealed class BlobTileGetPulseEvent : EntityEventArgs +{ + public bool Explain { get; set; } +} + +public sealed class BlobMobGetPulseEvent : EntityEventArgs +{ +} diff --git a/Content.Server/Blob/BlobNodeSystem.cs b/Content.Server/Blob/BlobNodeSystem.cs new file mode 100644 index 00000000000..e28a583befd --- /dev/null +++ b/Content.Server/Blob/BlobNodeSystem.cs @@ -0,0 +1,93 @@ +using System.Linq; +using System.Numerics; +using Robust.Shared.Map; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Blob; + +public sealed class BlobNodeSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + } + + private void OnStartup(EntityUid uid, BlobNodeComponent component, ComponentStartup args) + { + + } + + private void Pulse(EntityUid uid, BlobNodeComponent component) + { + var xform = Transform(uid); + + var radius = component.PulseRadius; + + var localPos = xform.Coordinates.Position; + + if (!_map.TryGetGrid(xform.GridUid, out var grid)) + { + return; + } + + if (!TryComp(uid, out var blobTileComponent) || blobTileComponent.Core == null) + return; + + var innerTiles = grid.GetLocalTilesIntersecting( + new Box2(localPos + new Vector2(-radius, -radius), localPos + new Vector2(radius, radius)), false).ToArray(); + + _random.Shuffle(innerTiles); + + var explain = true; + foreach (var tileRef in innerTiles) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!HasComp(ent)) + continue; + + var ev = new BlobTileGetPulseEvent + { + Explain = explain + }; + RaiseLocalEvent(ent, ev); + explain = false; + } + } + + foreach (var lookupUid in _lookup.GetEntitiesInRange(xform.Coordinates, radius)) + { + if (!HasComp(lookupUid)) + continue; + var ev = new BlobMobGetPulseEvent(); + RaiseLocalEvent(lookupUid, ev); + } + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var blobFactoryQuery = EntityQueryEnumerator(); + while (blobFactoryQuery.MoveNext(out var ent, out var comp)) + { + if (_gameTiming.CurTime < comp.NextPulse) + return; + + if (TryComp(ent, out var blobTileComponent) && blobTileComponent.Core != null) + { + Pulse(ent, comp); + } + + comp.NextPulse = _gameTiming.CurTime + TimeSpan.FromSeconds(comp.PulseFrequency); + } + } +} diff --git a/Content.Server/Blob/BlobObserverSystem.cs b/Content.Server/Blob/BlobObserverSystem.cs new file mode 100644 index 00000000000..dfafc7944d6 --- /dev/null +++ b/Content.Server/Blob/BlobObserverSystem.cs @@ -0,0 +1,868 @@ +using System.Linq; +using System.Numerics; +using Content.Server.Actions; +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.Shared.ActionBlocker; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Blob; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.Damage; +using Content.Shared.Interaction; +using Content.Shared.Item; +using Content.Shared.Maps; +using Content.Shared.Mobs.Components; +using Content.Shared.Popups; +using Content.Shared.Random.Helpers; +using Content.Shared.SubFloor; +using Robust.Server.GameObjects; +using Robust.Server.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Robust.Shared.Player; + +namespace Content.Server.Blob; + +public sealed class BlobObserverSystem : SharedBlobObserverSystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly ActionsSystem _action = default!; + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly ExplosionSystem _explosionSystem = default!; + [Dependency] private readonly FlammableSystem _flammable = default!; + [Dependency] private readonly EmpSystem _empSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnCreateFactory); + SubscribeLocalEvent(OnCreateResource); + SubscribeLocalEvent(OnCreateNode); + SubscribeLocalEvent(OnCreateBlobbernaut); + SubscribeLocalEvent(OnBlobToCore); + SubscribeLocalEvent(OnBlobToNode); + SubscribeLocalEvent(OnBlobHelp); + SubscribeLocalEvent(OnBlobSwapChem); + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnSwapCore); + SubscribeLocalEvent(OnSplitCore); + SubscribeLocalEvent(OnMoveEvent); + SubscribeLocalEvent(GetState); + SubscribeLocalEvent(OnChemSelected); + } + + private void OnBlobSwapChem(EntityUid uid, BlobObserverComponent observerComponent, + BlobSwapChemActionEvent args) + { + TryOpenUi(uid, args.Performer, observerComponent); + args.Handled = true; + } + + private void OnChemSelected(EntityUid uid, BlobObserverComponent component, BlobChemSwapPrototypeSelectedMessage args) + { + if (component.Core == null || !TryComp(component.Core.Value, out var blobCoreComponent)) + return; + + if (component.SelectedChemId == args.SelectedId) + return; + + if (!_blobCoreSystem.TryUseAbility(uid, component.Core.Value, blobCoreComponent, + blobCoreComponent.SwapChemCost)) + return; + + ChangeChem(uid, args.SelectedId, component); + } + + private void ChangeChem(EntityUid uid, BlobChemType newChem, BlobObserverComponent component) + { + if (component.Core == null || !TryComp(component.Core.Value, out var blobCoreComponent)) + return; + component.SelectedChemId = newChem; + _blobCoreSystem.ChangeChem(component.Core.Value, newChem, blobCoreComponent); + + _popup.PopupEntity(Loc.GetString("blob-spent-resource", ("point", blobCoreComponent.SwapChemCost)), + uid, + uid, + PopupType.LargeCaution); + + UpdateUi(uid, component); + } + + private void GetState(EntityUid uid, BlobObserverComponent component, ref ComponentGetState args) + { + args.State = new BlobChemSwapComponentState + { + SelectedChem = component.SelectedChemId + }; + } + + 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); + } + + public void UpdateUi(EntityUid uid, BlobObserverComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Core == null || !TryComp(component.Core.Value, out var blobCoreComponent)) + return; + + var state = new BlobChemSwapBoundUserInterfaceState(blobCoreComponent.ChemСolors, component.SelectedChemId); + + _uiSystem.TrySetUiState(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; + + if (observerComponent.Core == null) + { + observerComponent.IsProcessingMoveEvent = false; + return; + } + + if (Deleted(observerComponent.Core.Value) || + !TryComp(observerComponent.Core.Value, out var xform)) + { + return; + } + + var corePos = xform.Coordinates; + + var (nearestEntityUid, nearestDistance) = CalculateNearestBlobTileDistance(args.NewPosition); + + if (nearestEntityUid == null) + return; + + if (nearestDistance > 5f) + { + _transform.SetCoordinates(uid, corePos); + + observerComponent.IsProcessingMoveEvent = false; + return; + } + + if (nearestDistance > 3f) + { + observerComponent.CanMove = true; + _blocker.UpdateCanMove(uid); + var direction = (Transform(nearestEntityUid.Value).Coordinates.Position - args.NewPosition.Position); + var newPosition = args.NewPosition.Offset(direction * 0.1f); + + _transform.SetCoordinates(uid, newPosition); + } + + observerComponent.IsProcessingMoveEvent = false; + } + + private (EntityUid? nearestEntityUid, float nearestDistance) CalculateNearestBlobTileDistance(EntityCoordinates position) + { + var nearestDistance = float.MaxValue; + EntityUid? nearestEntityUid = null; + + foreach (var lookupUid in _lookup.GetEntitiesInRange(position, 5f)) + { + if (!HasComp(lookupUid)) + continue; + var tileCords = Transform(lookupUid).Coordinates; + var distance = Vector2.Distance(position.Position, tileCords.Position); + + if (!(distance < nearestDistance)) + continue; + nearestDistance = distance; + nearestEntityUid = lookupUid; + } + + return (nearestEntityUid, nearestDistance); + } + + private void OnBlobHelp(EntityUid uid, BlobObserverComponent observerComponent, + BlobHelpActionEvent args) + { + _popup.PopupEntity(Loc.GetString("blob-help"), uid, uid, PopupType.Large); + args.Handled = true; + } + + private void OnSplitCore(EntityUid uid, BlobObserverComponent observerComponent, + BlobSplitCoreActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + if (!blobCoreComponent.CanSplit) + { + _popup.PopupEntity(Loc.GetString("blob-cant-split"), uid, uid, PopupType.Large); + return; + } + + var gridUid = args.Target.GetGridUid(EntityManager); + + if (!_map.TryGetGrid(gridUid, out var grid)) + { + return; + } + + var centerTile = grid.GetLocalTilesIntersecting( + new Box2(args.Target.Position, args.Target.Position)).ToArray(); + + EntityUid? blobTile = null; + + foreach (var tileref in centerTile) + { + foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices)) + { + if (!TryComp(ent, out var blobTileComponent)) + continue; + blobTile = ent; + break; + } + } + + if (blobTile == null || !TryComp(blobTile, out var blobNodeComponent)) + { + _popup.PopupEntity(Loc.GetString("blob-target-node-blob-invalid"), uid, uid, PopupType.Large); + args.Handled = true; + return; + } + + if (!_blobCoreSystem.TryUseAbility(uid, observerComponent.Core.Value, blobCoreComponent, + blobCoreComponent.SplitCoreCost)) + { + args.Handled = true; + return; + } + + QueueDel(blobTile.Value); + var newCore = EntityManager.SpawnEntity(blobCoreComponent.CoreBlobTile, args.Target); + blobCoreComponent.CanSplit = false; + if (TryComp(newCore, out var newBlobCoreComponent)) + newBlobCoreComponent.CanSplit = false; + + args.Handled = true; + } + + + private void OnSwapCore(EntityUid uid, BlobObserverComponent observerComponent, + BlobSwapCoreActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + var gridUid = args.Target.GetGridUid(EntityManager); + + if (!_map.TryGetGrid(gridUid, out var grid)) + { + return; + } + + var centerTile = grid.GetLocalTilesIntersecting( + new Box2(args.Target.Position, args.Target.Position)).ToArray(); + + EntityUid? blobTile = null; + + foreach (var tileRef in centerTile) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!TryComp(ent, out var blobTileComponent)) + continue; + blobTile = ent; + break; + } + } + + if (blobTile == null || !TryComp(blobTile, out var blobNodeComponent)) + { + _popup.PopupEntity(Loc.GetString("blob-target-node-blob-invalid"), uid, uid, PopupType.Large); + args.Handled = true; + return; + } + + if (!_blobCoreSystem.TryUseAbility(uid, observerComponent.Core.Value, blobCoreComponent, + blobCoreComponent.SwapCoreCost)) + { + args.Handled = true; + return; + } + + var nodePos = Transform(blobTile.Value).Coordinates; + var corePos = Transform(observerComponent.Core.Value).Coordinates; + _transform.SetCoordinates(observerComponent.Core.Value, nodePos.SnapToGrid()); + _transform.SetCoordinates(blobTile.Value, corePos.SnapToGrid()); + var xformCore = Transform(observerComponent.Core.Value); + if (!xformCore.Anchored) + { + _transform.AnchorEntity(observerComponent.Core.Value, xformCore); + } + var xformNode = Transform(blobTile.Value); + if (!xformNode.Anchored) + { + _transform.AnchorEntity(blobTile.Value, xformNode); + } + args.Handled = true; + } + + private void OnBlobToNode(EntityUid uid, BlobObserverComponent observerComponent, + BlobToNodeActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + var blobNodes = new List(); + + var blobNodeQuery = EntityQueryEnumerator(); + while (blobNodeQuery.MoveNext(out var ent, out var node, out var tile)) + { + if (tile.Core == observerComponent.Core.Value && !HasComp(ent)) + blobNodes.Add(ent); + } + + if (blobNodes.Count == 0) + { + _popup.PopupEntity(Loc.GetString("blob-not-have-nodes"), uid, uid, PopupType.Large); + args.Handled = true; + return; + } + + _transform.SetCoordinates(uid, Transform(_random.Pick(blobNodes)).Coordinates); + args.Handled = true; + } + + private void OnCreateBlobbernaut(EntityUid uid, BlobObserverComponent observerComponent, + BlobCreateBlobbernautActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || + !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + var gridUid = args.Target.GetGridUid(EntityManager); + + if (!_map.TryGetGrid(gridUid, out var grid)) + { + return; + } + + var centerTile = grid.GetLocalTilesIntersecting( + new Box2(args.Target.Position, args.Target.Position)).ToArray(); + + EntityUid? blobTile = null; + + foreach (var tileRef in centerTile) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!HasComp(ent)) + continue; + blobTile = ent; + break; + } + } + + if (blobTile == null || !TryComp(blobTile, out var blobFactoryComponent)) + { + _popup.PopupEntity(Loc.GetString("blob-target-factory-blob-invalid"), uid, uid, PopupType.LargeCaution); + return; + } + + if (blobFactoryComponent.Blobbernaut != null) + { + _popup.PopupEntity(Loc.GetString("blob-target-already-produce-blobbernaut"), uid, uid, PopupType.LargeCaution); + return; + } + + if (!_blobCoreSystem.TryUseAbility(uid, observerComponent.Core.Value, blobCoreComponent, blobCoreComponent.BlobbernautCost)) + 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, BlobObserverComponent observerComponent, + BlobToCoreActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || + !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + _transform.SetCoordinates(uid, Transform(observerComponent.Core.Value).Coordinates); + } + + private void OnCreateNode(EntityUid uid, BlobObserverComponent observerComponent, + BlobCreateNodeActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || + !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + var gridUid = args.Target.GetGridUid(EntityManager); + + if (!_map.TryGetGrid(gridUid, out var grid)) + { + return; + } + + var centerTile = grid.GetLocalTilesIntersecting( + new Box2(args.Target.Position, args.Target.Position)).ToArray(); + + var blobTileType = BlobTileType.None; + EntityUid? blobTile = null; + + foreach (var tileRef in centerTile) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!TryComp(ent, out var blobTileComponent)) + continue; + blobTileType = blobTileComponent.BlobTileType; + blobTile = ent; + break; + } + } + + if (blobTileType is not BlobTileType.Normal || + blobTile == null) + { + _popup.PopupEntity(Loc.GetString("blob-target-normal-blob-invalid"), uid, uid, PopupType.Large); + return; + } + + var xform = Transform(blobTile.Value); + + var localPos = xform.Coordinates.Position; + + var radius = blobCoreComponent.NodeRadiusLimit; + + var innerTiles = grid.GetLocalTilesIntersecting( + new Box2(localPos + new Vector2(-radius, -radius), localPos + new Vector2(radius, radius)), false).ToArray(); + + foreach (var tileRef in innerTiles) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!HasComp(ent)) + continue; + _popup.PopupEntity(Loc.GetString("blob-target-close-to-node"), uid, uid, PopupType.Large); + return; + } + } + + if (!_blobCoreSystem.TryUseAbility(uid, observerComponent.Core.Value, blobCoreComponent, blobCoreComponent.NodeBlobCost)) + return; + + if (!_blobCoreSystem.TransformBlobTile(blobTile.Value, + observerComponent.Core.Value, + blobCoreComponent.NodeBlobTile, + args.Target, + blobCoreComponent, + transformCost: blobCoreComponent.NodeBlobCost)) + return; + + args.Handled = true; + } + + private void OnCreateResource(EntityUid uid, BlobObserverComponent observerComponent, + BlobCreateResourceActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || + !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + var gridUid = args.Target.GetGridUid(EntityManager); + + if (!_map.TryGetGrid(gridUid, out var grid)) + { + return; + } + + var centerTile = grid.GetLocalTilesIntersecting( + new Box2(args.Target.Position, args.Target.Position)).ToArray(); + + var blobTileType = BlobTileType.None; + EntityUid? blobTile = null; + + foreach (var tileref in centerTile) + { + foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices)) + { + if (!TryComp(ent, out var blobTileComponent)) + continue; + blobTileType = blobTileComponent.BlobTileType; + blobTile = ent; + break; + } + } + + if (blobTileType is not BlobTileType.Normal || + blobTile == null) + { + _popup.PopupEntity(Loc.GetString("blob-target-normal-blob-invalid"), uid, uid, PopupType.Large); + return; + } + + var xform = Transform(blobTile.Value); + + var localPos = xform.Coordinates.Position; + + var radius = blobCoreComponent.ResourceRadiusLimit; + + var innerTiles = grid.GetLocalTilesIntersecting( + new Box2(localPos + new Vector2(-radius, -radius), localPos + new Vector2(radius, radius)), false).ToArray(); + + foreach (var tileRef in innerTiles) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!HasComp(ent) || HasComp(ent)) + continue; + _popup.PopupEntity(Loc.GetString("blob-target-close-to-resource"), uid, uid, PopupType.Large); + return; + } + } + + if (!_blobCoreSystem.CheckNearNode(uid, xform.Coordinates, grid, blobCoreComponent)) + return; + + if (!_blobCoreSystem.TryUseAbility(uid, + observerComponent.Core.Value, + blobCoreComponent, + blobCoreComponent.ResourceBlobCost)) + return; + + if (!_blobCoreSystem.TransformBlobTile(blobTile.Value, + observerComponent.Core.Value, + blobCoreComponent.ResourceBlobTile, + args.Target, + blobCoreComponent, + transformCost: blobCoreComponent.ResourceBlobCost)) + return; + + args.Handled = true; + } + + private void OnInteract(EntityUid uid, BlobObserverComponent observerComponent, InteractNoHandEvent args) + { + if (args.Target == args.User) + return; + + if (observerComponent.Core == null || + !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + var location = args.ClickLocation; + if (!location.IsValid(EntityManager)) + return; + + var gridId = location.GetGridUid(EntityManager); + if (!HasComp(gridId)) + { + location = location.AlignWithClosestGridTile(); + gridId = location.GetGridUid(EntityManager); + if (!HasComp(gridId)) + return; + } + + if (!_map.TryGetGrid(gridId, out var grid)) + { + return; + } + + if (args.Target != null && + !HasComp(args.Target.Value) && + !HasComp(args.Target.Value)) + { + var target = args.Target.Value; + + // Check if the target is adjacent to a tile with BlobCellComponent horizontally or vertically + var xform = Transform(target); + var mobTile = grid.GetTileRef(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) + }; + if (mobAdjacentTiles.Any(indices => grid.GetAnchoredEntities(indices).Any(ent => HasComp(ent)))) + { + if (HasComp(target) && !HasComp(target)&& !HasComp(target)) + { + if (_blobCoreSystem.TryUseAbility(uid, observerComponent.Core.Value, blobCoreComponent, blobCoreComponent.AttackCost)) + { + if (_gameTiming.CurTime < blobCoreComponent.NextAction) + return; + if (blobCoreComponent.Observer != null) + { + _popup.PopupCoordinates(Loc.GetString("blob-spent-resource", ("point", blobCoreComponent.AttackCost)), + args.ClickLocation, + blobCoreComponent.Observer.Value, + PopupType.LargeCaution); + } + _damageableSystem.TryChangeDamage(target, blobCoreComponent.ChemDamageDict[blobCoreComponent.CurrentChem]); + + if (blobCoreComponent.CurrentChem == BlobChemType.ExplosiveLattice) + { + _explosionSystem.QueueExplosion(target, blobCoreComponent.BlobExplosive, 4, 1, 6, maxTileBreak: 0); + } + + if (blobCoreComponent.CurrentChem == BlobChemType.ElectromagneticWeb) + { + if (_random.Prob(0.2f)) + _empSystem.EmpPulse(xform.MapPosition, 3f, 50f, 3f); + } + + if (blobCoreComponent.CurrentChem == BlobChemType.BlazingOil) + { + if (TryComp(target, out var flammable)) + { + flammable.FireStacks += 2; + _flammable.Ignite(target, uid, flammable); + } + } + blobCoreComponent.NextAction = + _gameTiming.CurTime + TimeSpan.FromSeconds(blobCoreComponent.AttackRate); + _audioSystem.PlayPvs(blobCoreComponent.AttackSound, uid, AudioParams.Default); + return; + } + } + } + } + + var centerTile = grid.GetLocalTilesIntersecting( + new Box2(location.Position, location.Position), false).ToArray(); + + var targetTileEmplty = false; + foreach (var tileRef in centerTile) + { + if (tileRef.Tile.IsEmpty) + { + targetTileEmplty = true; + } + + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (HasComp(ent)) + return; + } + + foreach (var entityUid in _lookup.GetEntitiesIntersecting(tileRef.GridIndices.ToEntityCoordinates(gridId.Value, _map).ToMap(EntityManager))) + { + if (HasComp(entityUid) && !HasComp(entityUid)) + return; + } + } + + var targetTile = grid.GetTileRef(location); + + var adjacentTiles = new[] + { + targetTile.GridIndices.Offset(Direction.East), + targetTile.GridIndices.Offset(Direction.West), + targetTile.GridIndices.Offset(Direction.North), + targetTile.GridIndices.Offset(Direction.South) + }; + + if (!adjacentTiles.Any(indices => + grid.GetAnchoredEntities(indices).Any(ent => HasComp(ent)))) + return; + var cost = blobCoreComponent.NormalBlobCost; + if (targetTileEmplty) + { + cost *= 2; + } + + if (!_blobCoreSystem.TryUseAbility(uid, observerComponent.Core.Value, blobCoreComponent, cost)) + return; + + if (targetTileEmplty) + { + var plating = _tileDefinitionManager["Plating"]; + var platingTile = new Tile(plating.TileId); + grid.SetTile(location, platingTile); + } + + _blobCoreSystem.TransformBlobTile(null, + observerComponent.Core.Value, + blobCoreComponent.NormalBlobTile, + location, + blobCoreComponent, + transformCost: cost); + } + + private void OnStartup(EntityUid uid, BlobObserverComponent component, ComponentStartup args) + { + var helpBlob = new InstantAction( + _proto.Index("HelpBlob")); + _action.AddAction(uid, helpBlob, null); + var swapBlobChem = new InstantAction( + _proto.Index("SwapBlobChem")); + _action.AddAction(uid, swapBlobChem, null); + var teleportBlobToCore = new InstantAction( + _proto.Index("TeleportBlobToCore")); + _action.AddAction(uid, teleportBlobToCore, null); + var teleportBlobToNode = new InstantAction( + _proto.Index("TeleportBlobToNode")); + _action.AddAction(uid, teleportBlobToNode, null); + var createBlobFactory = new WorldTargetAction( + _proto.Index("CreateBlobFactory")); + _action.AddAction(uid, createBlobFactory, null); + var createBlobResource = new WorldTargetAction( + _proto.Index("CreateBlobResource")); + _action.AddAction(uid, createBlobResource, null); + var createBlobNode = new WorldTargetAction( + _proto.Index("CreateBlobNode")); + _action.AddAction(uid, createBlobNode, null); + var createBlobbernaut = new WorldTargetAction( + _proto.Index("CreateBlobbernaut")); + _action.AddAction(uid, createBlobbernaut, null); + var splitBlobCore = new WorldTargetAction( + _proto.Index("SplitBlobCore")); + _action.AddAction(uid, splitBlobCore, null); + var swapBlobCore = new WorldTargetAction( + _proto.Index("SwapBlobCore")); + _action.AddAction(uid, swapBlobCore, null); + } + + private void OnCreateFactory(EntityUid uid, BlobObserverComponent observerComponent, BlobCreateFactoryActionEvent args) + { + if (args.Handled) + return; + + if (observerComponent.Core == null || + !TryComp(observerComponent.Core.Value, out var blobCoreComponent)) + return; + + var gridUid = args.Target.GetGridUid(EntityManager); + + if (!_map.TryGetGrid(gridUid, out var grid)) + { + return; + } + + var centerTile = grid.GetLocalTilesIntersecting( + new Box2(args.Target.Position, args.Target.Position)).ToArray(); + + var blobTileType = BlobTileType.None; + EntityUid? blobTile = null; + + foreach (var tileRef in centerTile) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!TryComp(ent, out var blobTileComponent)) + continue; + blobTileType = blobTileComponent.BlobTileType; + blobTile = ent; + break; + } + } + + if (blobTileType is not BlobTileType.Normal || + blobTile == null) + { + _popup.PopupEntity(Loc.GetString("blob-target-normal-blob-invalid"), uid, uid, PopupType.Large); + return; + } + + var xform = Transform(blobTile.Value); + + var localPos = xform.Coordinates.Position; + + var radius = blobCoreComponent.FactoryRadiusLimit; + + var innerTiles = grid.GetLocalTilesIntersecting( + new Box2(localPos + new Vector2(-radius, -radius), localPos + new Vector2(radius, radius)), false).ToArray(); + + foreach (var tileRef in innerTiles) + { + foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (!HasComp(ent)) + continue; + _popup.PopupEntity(Loc.GetString("Слишком близко к другой фабрике"), uid, uid, PopupType.Large); + return; + } + } + + if (!_blobCoreSystem.CheckNearNode(uid, xform.Coordinates, grid, blobCoreComponent)) + return; + + if (!_blobCoreSystem.TryUseAbility(uid, observerComponent.Core.Value, blobCoreComponent, + blobCoreComponent.FactoryBlobCost)) + { + args.Handled = true; + return; + } + + if (!_blobCoreSystem.TransformBlobTile(null, + observerComponent.Core.Value, + blobCoreComponent.FactoryBlobTile, + args.Target, + blobCoreComponent, + transformCost: blobCoreComponent.FactoryBlobCost)) + return; + + args.Handled = true; + } +} diff --git a/Content.Server/Blob/BlobResourceComponent.cs b/Content.Server/Blob/BlobResourceComponent.cs new file mode 100644 index 00000000000..995e62cee23 --- /dev/null +++ b/Content.Server/Blob/BlobResourceComponent.cs @@ -0,0 +1,10 @@ +using Content.Shared.FixedPoint; + +namespace Content.Server.Blob; + +[RegisterComponent] +public sealed class BlobResourceComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite), DataField("pointsPerPulsed")] + public FixedPoint2 PointsPerPulsed = 3; +} diff --git a/Content.Server/Blob/BlobResourceSystem.cs b/Content.Server/Blob/BlobResourceSystem.cs new file mode 100644 index 00000000000..b3f3f1a394c --- /dev/null +++ b/Content.Server/Blob/BlobResourceSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared.Blob; +using Content.Shared.FixedPoint; +using Content.Shared.Popups; + +namespace Content.Server.Blob; + +public sealed class BlobResourceSystem : EntitySystem +{ + [Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPulsed); + } + + private void OnPulsed(EntityUid uid, BlobResourceComponent component, BlobTileGetPulseEvent args) + { + if (!TryComp(uid, out var blobTileComponent) || blobTileComponent.Core == null) + return; + if (!TryComp(blobTileComponent.Core, out var blobCoreComponent) || + blobCoreComponent.Observer == null) + return; + _popup.PopupEntity(Loc.GetString("blob-get-resource", ("point", component.PointsPerPulsed)), + uid, + blobCoreComponent.Observer.Value, + PopupType.LargeGreen); + + var points = component.PointsPerPulsed; + + if (blobCoreComponent.CurrentChem == BlobChemType.RegenerativeMateria) + { + points += FixedPoint2.New(1); + } + + _blobCoreSystem.ChangeBlobPoint(blobTileComponent.Core.Value, points); + } +} diff --git a/Content.Server/Blob/BlobSpawnerComponent.cs b/Content.Server/Blob/BlobSpawnerComponent.cs new file mode 100644 index 00000000000..6faa9d4635a --- /dev/null +++ b/Content.Server/Blob/BlobSpawnerComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Blob +{ + [RegisterComponent] + public sealed class BlobSpawnerComponent : Component + { + [ViewVariables(VVAccess.ReadWrite), + DataField("corePrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string CoreBlobPrototype = "CoreBlobTile"; + } +} diff --git a/Content.Server/Blob/BlobSpawnerSystem.cs b/Content.Server/Blob/BlobSpawnerSystem.cs new file mode 100644 index 00000000000..ac987999f47 --- /dev/null +++ b/Content.Server/Blob/BlobSpawnerSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Blob; +using Robust.Server.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Player; + +namespace Content.Server.Blob +{ + public sealed class BlobSpawnerSystem : EntitySystem + { + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPlayerAttached); + } + + private void OnPlayerAttached(EntityUid uid, BlobSpawnerComponent component, PlayerAttachedEvent args) + { + var xform = Transform(uid); + if (!_mapManager.TryGetGrid(xform.GridUid, out var map)) + return; + + var core = Spawn(component.CoreBlobPrototype, xform.Coordinates); + + if (!TryComp(core, out var blobCoreComponent)) + return; + + if (_blobCoreSystem.CreateBlobObserver(core, args.Player.UserId, blobCoreComponent)) + QueueDel(uid); + } + } +} diff --git a/Content.Server/Blob/BlobTileComponent.cs b/Content.Server/Blob/BlobTileComponent.cs new file mode 100644 index 00000000000..65e85dc6cef --- /dev/null +++ b/Content.Server/Blob/BlobTileComponent.cs @@ -0,0 +1,55 @@ +using Content.Shared.Blob; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; + +namespace Content.Server.Blob; + +[RegisterComponent] +public sealed class BlobTileComponent : SharedBlobTileComponent +{ + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? Core = default!; + + [ViewVariables(VVAccess.ReadOnly)] + public bool ReturnCost = true; + + [ViewVariables(VVAccess.ReadOnly), DataField("tileType")] + public BlobTileType BlobTileType = BlobTileType.Normal; + + [ViewVariables(VVAccess.ReadOnly), DataField("healthOfPulse")] + public DamageSpecifier HealthOfPulse = new() + { + DamageDict = new Dictionary + { + { "Blunt", -4 }, + { "Slash", -4 }, + { "Piercing", -4 }, + { "Heat", -4 }, + { "Cold", -4 }, + { "Shock", -4 }, + } + }; + + [ViewVariables(VVAccess.ReadOnly), DataField("flashDamage")] + public DamageSpecifier FlashDamage = new() + { + DamageDict = new Dictionary + { + { "Heat", 100 }, + } + }; +} + +[Serializable] +public enum BlobTileType : byte +{ + Normal, + Strong, + Reflective, + Resource, + Storage, + Node, + Factory, + Core, + None, +} diff --git a/Content.Server/Blob/BlobTileSystem.cs b/Content.Server/Blob/BlobTileSystem.cs new file mode 100644 index 00000000000..13f0332ee11 --- /dev/null +++ b/Content.Server/Blob/BlobTileSystem.cs @@ -0,0 +1,529 @@ +using System.Linq; +using System.Numerics; +using Content.Server.Construction.Components; +using Content.Server.Destructible; +using Content.Server.Emp; +using Content.Server.Flash; +using Content.Server.Flash.Components; +using Content.Shared.Blob; +using Content.Shared.Damage; +using Content.Shared.Destructible; +using Content.Shared.FixedPoint; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Map; + +namespace Content.Server.Blob; + +public sealed class BlobTileSystem : SharedBlobTileSystem +{ + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly BlobCoreSystem _blobCoreSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly EmpSystem _empSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + + // SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnDestruction); + SubscribeLocalEvent(OnPulsed); + SubscribeLocalEvent>(AddUpgradeVerb); + SubscribeLocalEvent>(AddRemoveVerb); + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnFlashAttempt); + } + + private void OnFlashAttempt(EntityUid uid, BlobTileComponent component, FlashAttemptEvent args) + { + if (args.Used == null || MetaData(args.Used.Value).EntityPrototype?.ID != "GrenadeFlashBang") + return; + if (component.BlobTileType == BlobTileType.Normal) + { + _damageableSystem.TryChangeDamage(uid, component.FlashDamage); + } + } + + private void OnDestruction(EntityUid uid, BlobTileComponent component, DestructionEventArgs args) + { + if (component.Core == null || !TryComp(component.Core.Value, out var blobCoreComponent)) + return; + + var xform = Transform(uid); + + if (blobCoreComponent.CurrentChem == BlobChemType.ElectromagneticWeb) + { + _empSystem.EmpPulse(xform.MapPosition, 3f, 50f, 3f); + } + } + + private void AddRemoveVerb(EntityUid uid, BlobTileComponent component, GetVerbsEvent args) + { + if (!TryComp(args.User, out var ghostBlobComponent)) + return; + + if (ghostBlobComponent.Core == null || + !TryComp(ghostBlobComponent.Core.Value, out var blobCoreComponent)) + return; + + if (ghostBlobComponent.Core.Value != component.Core) + return; + + if (TryComp(uid, out var transformComponent) && !transformComponent.Anchored) + return; + + if (HasComp(uid)) + return; + + Verb verb = new() + { + Act = () => TryRemove(uid, ghostBlobComponent.Core.Value, component, blobCoreComponent), + Text = Loc.GetString("blob-verb-remove-blob-tile"), + }; + args.Verbs.Add(verb); + } + + private void TryRemove(EntityUid target, EntityUid coreUid, BlobTileComponent tile, BlobCoreComponent core) + { + var xform = Transform(target); + if (!_blobCoreSystem.RemoveBlobTile(target, coreUid, core)) + { + return; + } + + FixedPoint2 returnCost = 0; + + if (tile.ReturnCost) + { + switch (tile.BlobTileType) + { + case BlobTileType.Normal: + { + returnCost = core.NormalBlobCost * core.ReturnResourceOnRemove; + break; + } + case BlobTileType.Strong: + { + returnCost = core.StrongBlobCost * core.ReturnResourceOnRemove; + break; + } + case BlobTileType.Factory: + { + returnCost = core.FactoryBlobCost * core.ReturnResourceOnRemove; + break; + } + case BlobTileType.Resource: + { + returnCost = core.ResourceBlobCost * core.ReturnResourceOnRemove; + break; + } + case BlobTileType.Reflective: + { + returnCost = core.ReflectiveBlobCost * core.ReturnResourceOnRemove; + break; + } + case BlobTileType.Node: + { + returnCost = core.NodeBlobCost * core.ReturnResourceOnRemove; + break; + } + } + } + + if (returnCost > 0) + { + if (TryComp(tile.Core, out var blobCoreComponent) && blobCoreComponent.Observer != null) + { + _popup.PopupCoordinates(Loc.GetString("blob-get-resource", ("point", returnCost)), + xform.Coordinates, + blobCoreComponent.Observer.Value, + PopupType.LargeGreen); + } + _blobCoreSystem.ChangeBlobPoint(coreUid, returnCost, core); + } + } + + private void OnGetState(EntityUid uid, BlobTileComponent component, ref ComponentGetState args) + { + args.State = new BlobTileComponentState() + { + Color = component.Color + }; + } + + private void OnPulsed(EntityUid uid, BlobTileComponent component, BlobTileGetPulseEvent args) + { + + if (!TryComp(uid, out var blobTileComponent) || blobTileComponent.Core == null || + !TryComp(blobTileComponent.Core.Value, out var blobCoreComponent)) + return; + + if (blobCoreComponent.CurrentChem == BlobChemType.RegenerativeMateria) + { + var healCore = new DamageSpecifier(); + foreach (var keyValuePair in component.HealthOfPulse.DamageDict) + { + healCore.DamageDict.Add(keyValuePair.Key, keyValuePair.Value * 10); + } + _damageableSystem.TryChangeDamage(uid, healCore); + } + else + { + _damageableSystem.TryChangeDamage(uid, component.HealthOfPulse); + } + + if (!args.Explain) + return; + + var xform = Transform(uid); + + if (!_map.TryGetGrid(xform.GridUid, out var grid)) + { + return; + } + + var mobTile = grid.GetTileRef(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) + }; + + var localPos = xform.Coordinates.Position; + + var radius = 1.0f; + + var innerTiles = grid.GetLocalTilesIntersecting( + 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 grid.GetAnchoredEntities(innerTile.GridIndices)) + { + if (!HasComp(ent) || !HasComp(ent)) + continue; + _damageableSystem.TryChangeDamage(ent, blobCoreComponent.ChemDamageDict[blobCoreComponent.CurrentChem]); + _audioSystem.PlayPvs(blobCoreComponent.AttackSound, uid, AudioParams.Default); + args.Explain = true; + return; + } + var spawn = true; + foreach (var ent in grid.GetAnchoredEntities(innerTile.GridIndices)) + { + if (!HasComp(ent)) + continue; + spawn = false; + break; + } + + if (!spawn) + continue; + + var location = innerTile.GridIndices.ToEntityCoordinates(xform.GridUid.Value, _map); + + if (_blobCoreSystem.TransformBlobTile(null, + blobTileComponent.Core.Value, + blobCoreComponent.NormalBlobTile, + location, + blobCoreComponent, + false)) + return; + } + } + + private void AddUpgradeVerb(EntityUid uid, BlobTileComponent component, GetVerbsEvent args) + { + if (!TryComp(args.User, out var ghostBlobComponent)) + return; + + if (ghostBlobComponent.Core == null || + !TryComp(ghostBlobComponent.Core.Value, out var blobCoreComponent)) + return; + + if (TryComp(uid, out var transformComponent) && !transformComponent.Anchored) + return; + + var verbName = component.BlobTileType switch + { + BlobTileType.Normal => Loc.GetString("blob-verb-upgrade-to-strong"), + BlobTileType.Strong => Loc.GetString("blob-verb-upgrade-to-reflective"), + _ => "Upgrade" + }; + + AlternativeVerb verb = new() + { + Act = () => TryUpgrade(uid, args.User, ghostBlobComponent.Core.Value, component, blobCoreComponent), + Text = verbName + }; + args.Verbs.Add(verb); + } + + private void TryUpgrade(EntityUid target, EntityUid user, EntityUid coreUid, BlobTileComponent tile, BlobCoreComponent core) + { + var xform = Transform(target); + if (tile.BlobTileType == BlobTileType.Normal) + { + if (!_blobCoreSystem.TryUseAbility(user, coreUid, core, core.StrongBlobCost)) + return; + + _blobCoreSystem.TransformBlobTile(target, + coreUid, + core.StrongBlobTile, + xform.Coordinates, + core, + transformCost: core.StrongBlobCost); + } + else if (tile.BlobTileType == BlobTileType.Strong) + { + if (!_blobCoreSystem.TryUseAbility(user, coreUid, core, core.ReflectiveBlobCost)) + return; + + _blobCoreSystem.TransformBlobTile(target, + coreUid, + core.ReflectiveBlobTile, + xform.Coordinates, + core, + transformCost: core.ReflectiveBlobCost); + } + } + + /* This work very bad. + I replace invisible + wall to teleportation observer + if he moving away from blob tile */ + + // private void OnStartup(EntityUid uid, BlobCellComponent component, ComponentStartup args) + // { + // var xform = Transform(uid); + // var radius = 2.5f; + // var wallSpacing = 1.5f; // Расстояние между стенами и центральной областью + // + // if (!_map.TryGetGrid(xform.GridUid, out var grid)) + // { + // return; + // } + // + // var localpos = xform.Coordinates.Position; + // + // // Получаем тайлы в области с радиусом 2.5 + // var allTiles = grid.GetLocalTilesIntersecting( + // new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray(); + // + // // Получаем тайлы в области с радиусом 1.5 + // var innerTiles = grid.GetLocalTilesIntersecting( + // new Box2(localpos + new Vector2(-wallSpacing, -wallSpacing), localpos + new Vector2(wallSpacing, wallSpacing))).ToArray(); + // + // foreach (var tileref in innerTiles) + // { + // foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices)) + // { + // if (HasComp(ent)) + // QueueDel(ent); + // if (HasComp(ent)) + // { + // var blockTiles = grid.GetLocalTilesIntersecting( + // new Box2(Transform(ent).Coordinates.Position + new Vector2(-wallSpacing, -wallSpacing), + // Transform(ent).Coordinates.Position + new Vector2(wallSpacing, wallSpacing))).ToArray(); + // allTiles = allTiles.Except(blockTiles).ToArray(); + // } + // } + // } + // + // var outerTiles = allTiles.Except(innerTiles).ToArray(); + // + // foreach (var tileRef in outerTiles) + // { + // foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + // { + // if (HasComp(ent)) + // { + // var blockTiles = grid.GetLocalTilesIntersecting( + // new Box2(Transform(ent).Coordinates.Position + new Vector2(-wallSpacing, -wallSpacing), + // Transform(ent).Coordinates.Position + new Vector2(wallSpacing, wallSpacing))).ToArray(); + // outerTiles = outerTiles.Except(blockTiles).ToArray(); + // } + // } + // } + // + // foreach (var tileRef in outerTiles) + // { + // var spawn = true; + // foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + // { + // if (HasComp(ent)) + // { + // spawn = false; + // break; + // } + // } + // if (spawn) + // EntityManager.SpawnEntity("BlobBorder", tileRef.GridIndices.ToEntityCoordinates(xform.GridUid.Value, _map)); + // } + // } + + // private void OnDestruction(EntityUid uid, BlobTileComponent component, DestructionEventArgs args) + // { + // var xform = Transform(uid); + // var radius = 1.0f; + // + // if (!_map.TryGetGrid(xform.GridUid, out var grid)) + // { + // return; + // } + // + // var localPos = xform.Coordinates.Position; + // + // var innerTiles = grid.GetLocalTilesIntersecting( + // new Box2(localPos + new Vector2(-radius, -radius), localPos + new Vector2(radius, radius)), false).ToArray(); + // + // var centerTile = grid.GetLocalTilesIntersecting( + // new Box2(localPos, localPos)).ToArray(); + // + // innerTiles = innerTiles.Except(centerTile).ToArray(); + // + // foreach (var tileref in innerTiles) + // { + // foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices)) + // { + // if (!HasComp(ent)) + // continue; + // var blockTiles = grid.GetLocalTilesIntersecting( + // new Box2(Transform(ent).Coordinates.Position + new Vector2(-radius, -radius), + // Transform(ent).Coordinates.Position + new Vector2(radius, radius)), false).ToArray(); + // + // var tilesToRemove = new List(); + // + // foreach (var blockTile in blockTiles) + // { + // tilesToRemove.Add(blockTile); + // } + // + // innerTiles = innerTiles.Except(tilesToRemove).ToArray(); + // } + // } + // + // foreach (var tileRef in innerTiles) + // { + // foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + // { + // if (HasComp(ent)) + // { + // QueueDel(ent); + // } + // } + // } + // + // EntityManager.SpawnEntity(component.BlobBorder, xform.Coordinates); + // } + // + // private void OnStartup(EntityUid uid, BlobTileComponent component, ComponentStartup args) + // { + // var xform = Transform(uid); + // var wallSpacing = 1.0f; + // + // if (!_map.TryGetGrid(xform.GridUid, out var grid)) + // { + // return; + // } + // + // var localPos = xform.Coordinates.Position; + // + // var innerTiles = grid.GetLocalTilesIntersecting( + // new Box2(localPos + new Vector2(-wallSpacing, -wallSpacing), localPos + new Vector2(wallSpacing, wallSpacing)), false).ToArray(); + // + // var centerTile = grid.GetLocalTilesIntersecting( + // new Box2(localPos, localPos)).ToArray(); + // + // foreach (var tileRef in centerTile) + // { + // foreach (var ent in grid.GetAnchoredEntities(tileRef.GridIndices)) + // { + // if (HasComp(ent)) + // QueueDel(ent); + // } + // } + // innerTiles = innerTiles.Except(centerTile).ToArray(); + // + // foreach (var tileref in innerTiles) + // { + // var spaceNear = false; + // var hasBlobTile = false; + // foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices)) + // { + // if (!HasComp(ent)) + // continue; + // var blockTiles = grid.GetLocalTilesIntersecting( + // new Box2(Transform(ent).Coordinates.Position + new Vector2(-wallSpacing, -wallSpacing), + // Transform(ent).Coordinates.Position + new Vector2(wallSpacing, wallSpacing)), false).ToArray(); + // + // var tilesToRemove = new List(); + // + // foreach (var blockTile in blockTiles) + // { + // if (blockTile.Tile.IsEmpty) + // { + // spaceNear = true; + // } + // else + // { + // tilesToRemove.Add(blockTile); + // } + // } + // + // innerTiles = innerTiles.Except(tilesToRemove).ToArray(); + // + // hasBlobTile = true; + // } + // + // if (!hasBlobTile || spaceNear) + // continue; + // { + // foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices)) + // { + // if (HasComp(ent)) + // { + // QueueDel(ent); + // } + // } + // } + // } + // + // var spaceNearCenter = false; + // + // foreach (var tileRef in innerTiles) + // { + // var spawn = true; + // if (tileRef.Tile.IsEmpty) + // { + // spaceNearCenter = true; + // spawn = false; + // } + // if (grid.GetAnchoredEntities(tileRef.GridIndices).Any(ent => HasComp(ent))) + // { + // spawn = false; + // } + // if (spawn) + // EntityManager.SpawnEntity(component.BlobBorder, tileRef.GridIndices.ToEntityCoordinates(xform.GridUid.Value, _map)); + // } + // if (spaceNearCenter) + // { + // EntityManager.SpawnEntity(component.BlobBorder, xform.Coordinates); + // } + // } +} diff --git a/Content.Server/Blob/BlobbernautComponent.cs b/Content.Server/Blob/BlobbernautComponent.cs new file mode 100644 index 00000000000..a3beb10baf7 --- /dev/null +++ b/Content.Server/Blob/BlobbernautComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Blob; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; + +namespace Content.Server.Blob; + +[RegisterComponent] +public sealed class BlobbernautComponent : SharedBlobbernautComponent +{ + [ViewVariables(VVAccess.ReadWrite), DataField("damageFrequency")] + public float DamageFrequency = 5; + + [ViewVariables(VVAccess.ReadOnly)] + public TimeSpan NextDamage = TimeSpan.Zero; + + [ViewVariables(VVAccess.ReadOnly), DataField("damage")] + public DamageSpecifier Damage = new() + { + DamageDict = new Dictionary + { + { "Piercing", 25 }, + } + }; + + [ViewVariables(VVAccess.ReadOnly)] + public bool IsDead = false; + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? Factory = default!; +} diff --git a/Content.Server/Blob/BlobbernautSystem.cs b/Content.Server/Blob/BlobbernautSystem.cs new file mode 100644 index 00000000000..3852c3bd677 --- /dev/null +++ b/Content.Server/Blob/BlobbernautSystem.cs @@ -0,0 +1,124 @@ +using System.Linq; +using System.Numerics; +using Content.Server.Emp; +using Content.Server.Explosion.EntitySystems; +using Content.Shared.Blob; +using Content.Shared.Damage; +using Content.Shared.Interaction.Events; +using Content.Shared.Mobs; +using Content.Shared.Popups; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Blob +{ + public sealed class BlobbernautSystem : EntitySystem + { + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ExplosionSystem _explosionSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EmpSystem _empSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnMeleeHit); + } + + private void OnMeleeHit(EntityUid uid, BlobbernautComponent component, MeleeHitEvent args) + { + if (args.HitEntities.Count >= 1) + return; + if (!TryComp(component.Factory, out var blobTileComponent)) + return; + if (!TryComp(blobTileComponent.Core, out var blobCoreComponent)) + return; + if (blobCoreComponent.CurrentChem == BlobChemType.ExplosiveLattice) + { + _explosionSystem.QueueExplosion(args.HitEntities.FirstOrDefault(), blobCoreComponent.BlobExplosive, 4, 1, 2, maxTileBreak: 0); + } + if (blobCoreComponent.CurrentChem == BlobChemType.ElectromagneticWeb) + { + var xform = Transform(args.HitEntities.FirstOrDefault()); + if (_random.Prob(0.2f)) + _empSystem.EmpPulse(xform.MapPosition, 3f, 50f, 3f); + } + } + + private void OnGetState(EntityUid uid, BlobbernautComponent component, ref ComponentGetState args) + { + args.State = new BlobbernautComponentState() + { + Color = component.Color + }; + } + + private void OnMobStateChanged(EntityUid uid, BlobbernautComponent component, MobStateChangedEvent args) + { + component.IsDead = args.NewMobState switch + { + MobState.Dead => true, + MobState.Alive => false, + _ => component.IsDead + }; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var blobFactoryQuery = EntityQueryEnumerator(); + while (blobFactoryQuery.MoveNext(out var ent, out var comp)) + { + if (comp.IsDead) + return; + + if (_gameTiming.CurTime < comp.NextDamage) + return; + + if (comp.Factory == null) + { + _popup.PopupEntity(Loc.GetString("blobberaut-factory-destroy"), ent, ent, PopupType.LargeCaution); + _damageableSystem.TryChangeDamage(ent, comp.Damage); + comp.NextDamage = _gameTiming.CurTime + TimeSpan.FromSeconds(comp.DamageFrequency); + return; + } + + var xform = Transform(ent); + + if (!_map.TryGetGrid(xform.GridUid, out var grid)) + { + return; + } + + var radius = 1f; + + var localPos = xform.Coordinates.Position; + var nearbyTile = grid.GetLocalTilesIntersecting( + new Box2(localPos + new Vector2(-radius, -radius), localPos + new Vector2(radius, radius))).ToArray(); + + foreach (var tileRef in nearbyTile) + { + foreach (var entOnTile in grid.GetAnchoredEntities(tileRef.GridIndices)) + { + if (TryComp(entOnTile, out var blobTileComponent) && blobTileComponent.Core != null) + return; + } + } + + _popup.PopupEntity(Loc.GetString("blobberaut-not-on-blob-tile"), ent, ent, PopupType.LargeCaution); + _damageableSystem.TryChangeDamage(ent, comp.Damage); + comp.NextDamage = _gameTiming.CurTime + TimeSpan.FromSeconds(comp.DamageFrequency); + } + } + } +} diff --git a/Content.Server/Blob/NPC/BlobPod/BlobPodComponent.cs b/Content.Server/Blob/NPC/BlobPod/BlobPodComponent.cs new file mode 100644 index 00000000000..273507f9d03 --- /dev/null +++ b/Content.Server/Blob/NPC/BlobPod/BlobPodComponent.cs @@ -0,0 +1,31 @@ +using Robust.Shared.Audio; + +namespace Content.Server.Blob.NPC.BlobPod +{ + [RegisterComponent] + public partial class BlobPodComponent : Component + { + [ViewVariables(VVAccess.ReadOnly)] + public bool IsZombifying = false; + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? ZombifiedEntityUid = default!; + + [ViewVariables(VVAccess.ReadWrite), DataField("zombifyDelay")] + public float ZombifyDelay = 5.00f; + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? Core = null; + + [ViewVariables(VVAccess.ReadWrite), DataField("zombifySoundPath")] + public SoundSpecifier ZombifySoundPath = new SoundPathSpecifier("/Audio/Effects/Fluids/blood1.ogg"); + + [ViewVariables(VVAccess.ReadWrite), DataField("zombifyFinishSoundPath")] + public SoundSpecifier ZombifyFinishSoundPath = new SoundPathSpecifier("/Audio/Effects/gib1.ogg"); + + public IPlayingAudioStream? ZombifyStingStream; + public EntityUid? ZombifyTarget; + } +} + + diff --git a/Content.Server/Blob/NPC/BlobPod/BlobPodSystem.cs b/Content.Server/Blob/NPC/BlobPod/BlobPodSystem.cs new file mode 100644 index 00000000000..27df16690c3 --- /dev/null +++ b/Content.Server/Blob/NPC/BlobPod/BlobPodSystem.cs @@ -0,0 +1,157 @@ +using Content.Server.DoAfter; +using Content.Server.Explosion.EntitySystems; +using Content.Server.NPC.HTN; +using Content.Server.Popups; +using Content.Shared.ActionBlocker; +using Content.Shared.Blob; +using Content.Shared.CombatMode; +using Content.Shared.Destructible; +using Content.Shared.DoAfter; +using Content.Shared.Humanoid; +using Content.Shared.Interaction.Components; +using Content.Shared.Inventory; +using Content.Shared.Mobs.Systems; +using Content.Shared.Rejuvenate; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Server.GameObjects; +using Robust.Shared.Player; + +namespace Content.Server.Blob.NPC.BlobPod +{ + public sealed class BlobPodSystem : EntitySystem + { + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly MobStateSystem _mobs = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly PopupSystem _popups = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly ExplosionSystem _explosionSystem = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(AddDrainVerb); + SubscribeLocalEvent(OnZombify); + SubscribeLocalEvent(OnDestruction); + } + + private void OnDestruction(EntityUid uid, BlobPodComponent component, DestructionEventArgs args) + { + if (!TryComp(component.Core, out var blobCoreComponent)) + return; + if (blobCoreComponent.CurrentChem == BlobChemType.ExplosiveLattice) + { + _explosionSystem.QueueExplosion(uid, blobCoreComponent.BlobExplosive, 4, 1, 2, maxTileBreak: 0); + } + } + + private void AddDrainVerb(EntityUid uid, BlobPodComponent component, GetVerbsEvent args) + { + if (args.User == args.Target) + return; + if (!args.CanAccess) + return; + if (!HasComp(args.Target)) + return; + if (_mobs.IsAlive(args.Target)) + return; + + InnateVerb verb = new() + { + Act = () => + { + NpcStartZombify(uid, args.Target, component); + }, + Text = Loc.GetString("blob-pod-verb-zombify"), + // Icon = new SpriteSpecifier.Texture(new ("/Textures/")), + Priority = 2 + }; + args.Verbs.Add(verb); + } + + private void OnZombify(EntityUid uid, BlobPodComponent component, BlobPodZombifyDoAfterEvent args) + { + component.IsZombifying = false; + if (args.Handled || args.Args.Target == null) + { + component.ZombifyStingStream?.Stop(); + return; + } + + if (args.Cancelled) + { + return; + } + + _inventory.TryGetSlotEntity(args.Args.Target.Value, "head", out var headItem); + if (HasComp(headItem)) + return; + + _inventory.TryUnequip(args.Args.Target.Value, "head", true, true); + var equipped = _inventory.TryEquip(args.Args.Target.Value, uid, "head", true, true); + + if (!equipped) + return; + + _popups.PopupEntity(Loc.GetString("blob-mob-zombify-second-end", ("pod", uid)), args.Args.Target.Value, args.Args.Target.Value, Shared.Popups.PopupType.LargeCaution); + _popups.PopupEntity(Loc.GetString("blob-mob-zombify-third-end", ("pod", uid), ("target", args.Args.Target.Value)), args.Args.Target.Value, Filter.PvsExcept(args.Args.Target.Value), true, Shared.Popups.PopupType.LargeCaution); + + EntityManager.RemoveComponent(uid); + + EntityManager.RemoveComponent(uid); + + EntityManager.EnsureComponent(uid); + + _audioSystem.PlayPvs(component.ZombifyFinishSoundPath, uid); + + var rejEv = new RejuvenateEvent(); + RaiseLocalEvent(args.Args.Target.Value, rejEv); + + component.ZombifiedEntityUid = args.Args.Target.Value; + + var zombieBlob = EnsureComp(args.Args.Target.Value); + zombieBlob.BlobPodUid = uid; + } + + + public bool NpcStartZombify(EntityUid uid, EntityUid target, BlobPodComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + if (!HasComp(target)) + return false; + if (_mobs.IsAlive(target)) + return false; + if (!_actionBlocker.CanInteract(uid, target)) + return false; + + StartZombify(uid, target, component); + return true; + } + + public void StartZombify(EntityUid uid, EntityUid target, BlobPodComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.ZombifyTarget = target; + _popups.PopupEntity(Loc.GetString("blob-mob-zombify-second-start", ("pod", uid)), target, target, Shared.Popups.PopupType.LargeCaution); + _popups.PopupEntity(Loc.GetString("blob-mob-zombify-third-start", ("pod", uid), ("target", target)), target, Filter.PvsExcept(target), true, Shared.Popups.PopupType.LargeCaution); + + component.ZombifyStingStream = _audioSystem.PlayPvs(component.ZombifySoundPath, target); + component.IsZombifying = true; + + var ev = new BlobPodZombifyDoAfterEvent(); + var args = new DoAfterArgs(uid, component.ZombifyDelay, ev, uid, target: target) + { + BreakOnTargetMove = true, + BreakOnUserMove = false, + DistanceThreshold = 2f, + NeedHand = false + }; + + _doAfter.TryStartDoAfter(args); + } + } +} diff --git a/Content.Server/Blob/ZombieBlobComponent.cs b/Content.Server/Blob/ZombieBlobComponent.cs new file mode 100644 index 00000000000..849cc0d54bb --- /dev/null +++ b/Content.Server/Blob/ZombieBlobComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.Audio; + +namespace Content.Server.Blob; + +[RegisterComponent] +public sealed class ZombieBlobComponent : Component +{ + public List OldFactions = new(); + + public EntityUid BlobPodUid = default!; + + public float? OldColdDamageThreshold = null; + + [ViewVariables] + public Dictionary DisabledFixtureMasks { get; } = new(); + + [DataField("greetSoundNotification")] + public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/zombie_start.ogg"); +} diff --git a/Content.Server/Blob/ZombieBlobSystem.cs b/Content.Server/Blob/ZombieBlobSystem.cs new file mode 100644 index 00000000000..a6a3704b318 --- /dev/null +++ b/Content.Server/Blob/ZombieBlobSystem.cs @@ -0,0 +1,183 @@ +using Content.Server.Atmos.Components; +using Robust.Shared.Audio.Systems; +using Content.Server.Body.Components; +using Content.Server.Chat.Managers; +using Content.Server.Mind; +using Content.Shared.Mind.Components; +using Content.Server.NPC; +using Content.Server.NPC.Components; +using Content.Server.NPC.HTN; +using Content.Server.NPC.Systems; +using Content.Server.Speech.Components; +using Content.Server.Temperature.Components; +using Content.Shared.Mobs; +using Content.Shared.Physics; +using Content.Shared.Tag; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Systems; + +namespace Content.Server.Blob +{ + public sealed class ZombieBlobSystem : EntitySystem + { + [Dependency] private readonly NpcFactionSystem _faction = default!; + [Dependency] private readonly NPCSystem _npc = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly FixtureSystem _fixtureSystem = default!; + + private const int ClimbingCollisionGroup = (int) (CollisionGroup.BlobImpassable); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + } + + /// + /// Replaces the current fixtures with non-climbing collidable versions so that climb end can be detected + /// + /// Returns whether adding the new fixtures was successful + private void ReplaceFixtures(EntityUid uid, ZombieBlobComponent climbingComp, FixturesComponent fixturesComp) + { + foreach (var (name, fixture) in fixturesComp.Fixtures) + { + if (climbingComp.DisabledFixtureMasks.ContainsKey(name) + || fixture.Hard == false + || (fixture.CollisionMask & ClimbingCollisionGroup) == 0) + continue; + + climbingComp.DisabledFixtureMasks.Add(fixture.ID, fixture.CollisionMask & ClimbingCollisionGroup); + _physics.SetCollisionMask(uid, fixture, fixture.CollisionMask & ~ClimbingCollisionGroup, fixturesComp); + } + } + + private void OnStartup(EntityUid uid, ZombieBlobComponent component, ComponentStartup args) + { + EnsureComp(uid); + + var oldFactions = new List(); + var factionComp = EnsureComp(uid); + foreach (var factionId in new List(factionComp.Factions)) + { + oldFactions.Add(factionId); + _faction.RemoveFaction(uid, factionId); + } + _faction.AddFaction(uid, "Blob"); + component.OldFactions = oldFactions; + + var accent = EnsureComp(uid); + accent.Accent = "genericAggressive"; + + _tagSystem.AddTag(uid, "BlobMob"); + + EnsureComp(uid); + + //EnsureComp(uid); + + if (TryComp(uid, out var temperatureComponent)) + { + component.OldColdDamageThreshold = temperatureComponent.ColdDamageThreshold; + temperatureComponent.ColdDamageThreshold = 0; + } + + if (TryComp(uid, out var fixturesComp)) + { + ReplaceFixtures(uid, component, fixturesComp); + } + + var mindComp = EnsureComp(uid); + if (_mind.TryGetMind(uid, out var mind, mindComp) && _mind.TryGetSession(mind, out var session)) + { + _chatMan.DispatchServerMessage(session, Loc.GetString("blob-zombie-greeting")); + + _audio.PlayGlobal(component.GreetSoundNotification, session); + } + else + { + var htn = EnsureComp(uid); + htn.RootTask = new HTNCompoundTask() {Task = "SimpleHostileCompound"}; + htn.Blackboard.SetValue(NPCBlackboard.Owner, uid); + _npc.WakeNPC(uid, htn); + } + } + + private void OnShutdown(EntityUid uid, ZombieBlobComponent component, ComponentShutdown args) + { + if (TerminatingOrDeleted(uid)) + { + return; + } + + if (HasComp(uid)) + { + RemComp(uid); + } + + if (HasComp(uid)) + { + RemComp(uid); + } + + if (HasComp(uid)) + { + RemComp(uid); + } + + if (HasComp(uid)) + { + RemComp(uid); + } + + // if (HasComp(uid)) + // { + // RemComp(uid); + // } + + if (TryComp(uid, out var temperatureComponent) && component.OldColdDamageThreshold != null) + { + temperatureComponent.ColdDamageThreshold = component.OldColdDamageThreshold.Value; + } + + _tagSystem.RemoveTag(uid, "BlobMob"); + + QueueDel(component.BlobPodUid); + + EnsureComp(uid); + foreach (var factionId in component.OldFactions) + { + _faction.AddFaction(uid, factionId); + } + _faction.RemoveFaction(uid, "Blob"); + + if (TryComp(uid, out var fixtures)) + { + foreach (var (name, fixtureMask) in component.DisabledFixtureMasks) + { + if (!fixtures.Fixtures.TryGetValue(name, out var fixture)) + { + continue; + } + + _physics.SetCollisionMask(uid, fixture, fixture.CollisionMask | fixtureMask, fixtures); + } + component.DisabledFixtureMasks.Clear(); + } + } + + private void OnMobStateChanged(EntityUid uid, ZombieBlobComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead && !Deleted(uid)) + { + RemComp(uid); + } + } + } +} diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs index 9f080a3dd9d..3b05b60b933 100644 --- a/Content.Server/Body/Components/RespiratorComponent.cs +++ b/Content.Server/Body/Components/RespiratorComponent.cs @@ -60,6 +60,12 @@ public sealed partial class RespiratorComponent : Component public float CycleDelay = 2.0f; public float AccumulatedFrametime; + + /// + /// Whether the entity is immuned to pressure (i.e possess the PressureImmunity component) + /// + [ViewVariables] + public bool HasImmunity = false; } } @@ -67,4 +73,4 @@ public enum RespiratorStatus { Inhaling, Exhaling -} +} \ No newline at end of file diff --git a/Content.Server/Chemistry/Components/SmokeComponent.cs b/Content.Server/Chemistry/Components/SmokeComponent.cs new file mode 100644 index 00000000000..d0873306724 --- /dev/null +++ b/Content.Server/Chemistry/Components/SmokeComponent.cs @@ -0,0 +1,31 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Fluids.Components; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.Chemistry.Components; + +/// +/// Stores solution on an anchored entity that has touch and ingestion reactions +/// to entities that collide with it. Similar to +/// +[RegisterComponent] +public partial class SmokeComponent : SharedSmokeComponent +{ + public const string SolutionName = "solutionArea"; + + [DataField("nextReact", customTypeSerializer:typeof(TimeOffsetSerializer))] + public TimeSpan NextReact = TimeSpan.Zero; + + [DataField("spreadAmount")] + public int SpreadAmount = 0; + + [DataField("smokeColor")] + public Color SmokeColor = Color.Black; + + /// + /// Have we reacted with our tile yet? + /// + [DataField("reactedTile")] + public bool ReactedTile = false; +} + diff --git a/Content.Server/Fluids/EntitySystems/SmokeOnTriggerComponent.cs b/Content.Server/Fluids/EntitySystems/SmokeOnTriggerComponent.cs new file mode 100644 index 00000000000..298dc8faa3d --- /dev/null +++ b/Content.Server/Fluids/EntitySystems/SmokeOnTriggerComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.Chemistry.Components; +using Robust.Shared.Audio; + +namespace Content.Server.Fluids.EntitySystems; + +[RegisterComponent] +public sealed class SmokeOnTriggerComponent : Component +{ + [DataField("spreadAmount"), ViewVariables(VVAccess.ReadWrite)] + public int SpreadAmount = 20; + + [DataField("time"), ViewVariables(VVAccess.ReadWrite)] + public float Time = 20f; + + [DataField("smokeColor")] + public Color SmokeColor = Color.Black; + + [DataField("smokeReagents")] public List SmokeReagents = new(); + + [DataField("sound")] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/smoke.ogg"); +} diff --git a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs index ae170842a0c..76b10b09262 100644 --- a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Body.Systems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Chemistry.ReactionEffects; +using Content.Server.Explosion.EntitySystems; using Content.Server.Spreader; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; diff --git a/Content.Server/GameTicking/Rules/BlobRuleSystem.cs b/Content.Server/GameTicking/Rules/BlobRuleSystem.cs new file mode 100644 index 00000000000..09f5e009734 --- /dev/null +++ b/Content.Server/GameTicking/Rules/BlobRuleSystem.cs @@ -0,0 +1,183 @@ +using System.Linq; +using Content.Server.Chat.Systems; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Mind; +using Content.Server.Nuke; +using Content.Server.RoundEnd; +using Content.Server.Station.Systems; +using Content.Shared.Blob; + +namespace Content.Server.GameTicking.Rules; + +public sealed class BlobRuleSystem : GameRuleSystem +{ + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly NukeCodePaperSystem _nukeCode = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + + private ISawmill _sawmill = default!; + + public override void Initialize() + { + base.Initialize(); + + _sawmill = Logger.GetSawmill("preset"); + + SubscribeLocalEvent(OnRoundEndText); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var blobFactoryQuery = EntityQueryEnumerator(); + while (blobFactoryQuery.MoveNext(out var blobRuleUid, out var blobRuleComp)) + { + var blobCoreQuery = EntityQueryEnumerator(); + while (blobCoreQuery.MoveNext(out var ent, out var comp)) + { + if (comp.BlobTiles.Count >= 50) + { + if (_roundEndSystem.ExpectedCountdownEnd != null) + { + _roundEndSystem.CancelRoundEndCountdown(checkCooldown: false); + _chatSystem.DispatchGlobalAnnouncement(Loc.GetString("blob-alert-recall-shuttle"), + Loc.GetString("Station"), + false, + null, + Color.Red); + } + } + + switch (blobRuleComp.Stage) + { + case BlobStage.Default when comp.BlobTiles.Count < 50: + continue; + case BlobStage.Default: + _chatSystem.DispatchGlobalAnnouncement(Loc.GetString("blob-alert-detect"), + Loc.GetString("Station"), + true, + blobRuleComp.AlertAudio, + Color.Red); + blobRuleComp.Stage = BlobStage.Begin; + break; + case BlobStage.Begin: + { + if (comp.BlobTiles.Count >= 300) + { + _chatSystem.DispatchGlobalAnnouncement(Loc.GetString("blob-alert-critical"), + Loc.GetString("Station"), + true, + blobRuleComp.AlertAudio, + Color.Red); + var stationUid = _stationSystem.GetOwningStation(ent); + if (stationUid != null) + _nukeCode.SendNukeCodes(stationUid.Value); + blobRuleComp.Stage = BlobStage.Critical; + } + break; + } + case BlobStage.Critical: + { + if (comp.BlobTiles.Count >= 400) + { + comp.Points = 99999; + _roundEndSystem.EndRound(); + } + break; + } + } + } + } + } + + private void OnRoundEndText(RoundEndTextAppendEvent ev) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var blob, out var gameRule)) + { + if (!GameTicker.IsGameRuleAdded(uid, gameRule)) + continue; + + if (blob.Blobs.Count < 1) + return; + + var result = Loc.GetString("blob-round-end-result", ("blobCount", blob.Blobs.Count)); + + // yeah this is duplicated from traitor rules lol, there needs to be a generic rewrite where it just goes through all minds with objectives + foreach (var t in blob.Blobs) + { + var name = t.Mind.CharacterName; + _mindSystem.TryGetSession(t.Mind, out var session); + var username = session?.Name; + + var objectives = t.Mind.AllObjectives.ToArray(); + if (objectives.Length == 0) + { + if (username != null) + { + if (name == null) + result += "\n" + Loc.GetString("blob-user-was-a-blob", ("user", username)); + else + { + result += "\n" + Loc.GetString("blob-user-was-a-blob-named", ("user", username), + ("name", name)); + } + } + else if (name != null) + result += "\n" + Loc.GetString("blob-was-a-blob-named", ("name", name)); + + continue; + } + + if (username != null) + { + if (name == null) + { + result += "\n" + Loc.GetString("blob-user-was-a-blob-with-objectives", + ("user", username)); + } + else + { + result += "\n" + Loc.GetString("blob-user-was-a-blob-with-objectives-named", + ("user", username), ("name", name)); + } + } + else if (name != null) + result += "\n" + Loc.GetString("blob-was-a-blob-with-objectives-named", ("name", name)); + + foreach (var objectiveGroup in objectives.GroupBy(o => o.Prototype.Issuer)) + { + foreach (var objective in objectiveGroup) + { + foreach (var condition in objective.Conditions) + { + var progress = condition.Progress; + if (progress > 0.99f) + { + result += "\n- " + Loc.GetString( + "traitor-objective-condition-success", + ("condition", condition.Title), + ("markupColor", "green") + ); + } + else + { + result += "\n- " + Loc.GetString( + "traitor-objective-condition-fail", + ("condition", condition.Title), + ("progress", (int) (progress * 100)), + ("markupColor", "red") + ); + } + } + } + } + } + + ev.AddLine(result); + } + } +} diff --git a/Content.Server/GameTicking/Rules/Components/BlobRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/BlobRuleComponent.cs new file mode 100644 index 00000000000..7da7a58cd0a --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/BlobRuleComponent.cs @@ -0,0 +1,26 @@ +using Content.Server.Blob; +using Content.Server.Roles; +using Robust.Shared.Audio; + +namespace Content.Server.GameTicking.Rules.Components; + +[RegisterComponent, Access(typeof(BlobRuleSystem), typeof(BlobCoreSystem))] +public sealed class BlobRuleComponent : Component +{ + public List Blobs = new(); + + public BlobStage Stage = BlobStage.Default; + + [DataField("alertAodio")] + public SoundSpecifier? AlertAudio = new SoundPathSpecifier("/Audio/Announcements/attention.ogg"); +} + + +public enum BlobStage : byte +{ + Default, + Begin, + Medium, + Critical, + TheEnd +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/BlobPodZombifyOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/BlobPodZombifyOperator.cs new file mode 100644 index 00000000000..11126b2de26 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/BlobPodZombifyOperator.cs @@ -0,0 +1,47 @@ +using Content.Server.Blob.NPC.BlobPod; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific; + +public sealed class BlobPodZombifyOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private BlobPodSystem _blobPodSystem = default!; + + [DataField("zombifyKey")] + public string ZombifyKey = string.Empty; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _blobPodSystem = sysManager.GetEntitySystem(); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var target = blackboard.GetValue(ZombifyKey); + + if (!target.IsValid() || _entManager.Deleted(target)) + return HTNOperatorStatus.Failed; + + if (!_entManager.TryGetComponent(owner, out var pod)) + return HTNOperatorStatus.Failed; + + if (pod.ZombifiedEntityUid != null) + return HTNOperatorStatus.Continuing; + + if (pod.IsZombifying) + return HTNOperatorStatus.Continuing; + + if (pod.ZombifyTarget == null) + { + if (_blobPodSystem.NpcStartZombify(owner, target, pod)) + return HTNOperatorStatus.Continuing; + else + return HTNOperatorStatus.Failed; + } + + pod.ZombifyTarget = null; + return HTNOperatorStatus.Finished; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickBlobPodZombifyTargetOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickBlobPodZombifyTargetOperator.cs new file mode 100644 index 00000000000..a56e7a0ebd3 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickBlobPodZombifyTargetOperator.cs @@ -0,0 +1,89 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Server.NPC.Pathfinding; +using Content.Server.NPC.Systems; +using Content.Shared.Humanoid; +using Content.Shared.Mobs.Systems; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific; + +public sealed class PickBlobPodZombifyTargetOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private NpcFactionSystem _factions = default!; + private MobStateSystem _mobSystem = default!; + + private EntityLookupSystem _lookup = default!; + private PathfindingSystem _pathfinding = default!; + + [DataField("rangeKey", required: true)] + public string RangeKey = string.Empty; + + [DataField("targetKey", required: true)] + public string TargetKey = string.Empty; + + [DataField("zombifyKey")] + public string ZombifyKey = string.Empty; + + /// + /// Where the pathfinding result will be stored (if applicable). This gets removed after execution. + /// + [DataField("pathfindKey")] + public string PathfindKey = NPCBlackboard.PathfindKey; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _lookup = sysManager.GetEntitySystem(); + _pathfinding = sysManager.GetEntitySystem(); + _mobSystem = sysManager.GetEntitySystem(); + _factions = sysManager.GetEntitySystem(); + } + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, + CancellationToken cancelToken) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!blackboard.TryGetValue(RangeKey, out var range, _entManager)) + return (false, null); + + var huAppQuery = _entManager.GetEntityQuery(); + var xformQuery = _entManager.GetEntityQuery(); + + var targets = new List(); + + foreach (var entity in _factions.GetNearbyHostiles(owner, range)) + { + if (!huAppQuery.TryGetComponent(entity, out var humanoidAppearance)) + continue; + + if (_mobSystem.IsAlive(entity)) + continue; + + targets.Add(entity); + } + + foreach (var target in targets) + { + if (!xformQuery.TryGetComponent(target, out var xform)) + continue; + + var targetCoords = xform.Coordinates; + var path = await _pathfinding.GetPath(owner, target, range, cancelToken); + if (path.Result != PathResult.Path) + { + continue; + } + + return (true, new Dictionary() + { + { TargetKey, targetCoords }, + { ZombifyKey, target }, + { PathfindKey, path} + }); + } + + return (false, null); + } +} diff --git a/Content.Server/Objectives/Conditions/BlobCaptureCondition.cs b/Content.Server/Objectives/Conditions/BlobCaptureCondition.cs new file mode 100644 index 00000000000..bd5b6a22451 --- /dev/null +++ b/Content.Server/Objectives/Conditions/BlobCaptureCondition.cs @@ -0,0 +1,71 @@ +using Content.Server.Blob; +using Content.Server.Objectives.Interfaces; +using Content.Shared.Blob; +using JetBrains.Annotations; +using Robust.Shared.Utility; + +namespace Content.Server.Objectives.Conditions; + +[UsedImplicitly] +[DataDefinition] +public partial class BlobCaptureCondition : IObjectiveCondition +{ + private Mind.Mind? _mind; + private int _target; + + public IObjectiveCondition GetAssigned(Mind.Mind mind) + { + return new BlobCaptureCondition + { + _mind = mind, + _target = 400 + }; + } + + public string Title => Loc.GetString("objective-condition-blob-capture-title"); + + public string Description => Loc.GetString("objective-condition-blob-capture-description", ("count", _target)); + + public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Mobs/Aliens/blob.rsi"), "blob_nuke_overlay"); + + public float Progress + { + get + { + var entMan = IoCManager.Resolve(); + // prevent divide-by-zero + if (_target == 0) + return 1f; + + if (_mind?.OwnedEntity == null) + return 0f; + + if (!entMan.TryGetComponent(_mind.OwnedEntity, out var blobObserverComponent) + || !entMan.TryGetComponent(blobObserverComponent.Core, out var blobCoreComponent)) + { + return 0f; + } + + return (float) blobCoreComponent.BlobTiles.Count / (float) _target; + } + } + + public float Difficulty => 4.0f; + + public bool Equals(IObjectiveCondition? other) + { + return other is BlobCaptureCondition cond && Equals(_mind, cond._mind) && _target == cond._target; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is BlobCaptureCondition cond && cond.Equals(this); + } + + public override int GetHashCode() + { + return HashCode.Combine(_mind?.GetHashCode() ?? 0, _target); + } +} diff --git a/Content.Server/Objectives/Requirements/BlobRequirement.cs b/Content.Server/Objectives/Requirements/BlobRequirement.cs new file mode 100644 index 00000000000..34f09867b7e --- /dev/null +++ b/Content.Server/Objectives/Requirements/BlobRequirement.cs @@ -0,0 +1,16 @@ +using Content.Server.Mind; +using Content.Server.Objectives.Interfaces; +using Content.Server.Roles; + +namespace Content.Server.Objectives.Requirements; + +[DataDefinition] +public sealed class BlobRequirement : IObjectiveRequirement +{ + public bool CanBeAssigned(Mind.Mind mind) + { + var entityManager = IoCManager.Resolve(); + var mindSystem = entityManager.System(); + return mindSystem.HasRole(mind); + } +} diff --git a/Content.Server/Roles/BlobRole.cs b/Content.Server/Roles/BlobRole.cs new file mode 100644 index 00000000000..2fd1eb883e3 --- /dev/null +++ b/Content.Server/Roles/BlobRole.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; + +namespace Content.Server.Roles; + +public sealed class BlobRole : AntagonistRole +{ + public BlobRole(Mind.Mind mind, AntagPrototype antagPrototype) : base(mind, antagPrototype) { } +} diff --git a/Content.Server/StationEvents/Components/BlobRuleComponent.cs b/Content.Server/StationEvents/Components/BlobRuleComponent.cs new file mode 100644 index 00000000000..367df67ac5e --- /dev/null +++ b/Content.Server/StationEvents/Components/BlobRuleComponent.cs @@ -0,0 +1,23 @@ +using Content.Server.StationEvents.Events; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Server.StationEvents.Components; + +[RegisterComponent, Access(typeof(BlobSpawnRule))] +public sealed class BlobSpawnRuleComponent : Component +{ + [DataField("carrierBlobProtos", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer)), ViewVariables(VVAccess.ReadWrite)] + public List CarrierBlobProtos = new() + { + "MobMouse", + "MobMouse1", + "MobMouse2" + }; + + [ViewVariables(VVAccess.ReadOnly), DataField("playersPerCarrierBlob")] + public int PlayersPerCarrierBlob = 30; + + [ViewVariables(VVAccess.ReadOnly), DataField("maxCarrierBlob")] + public int MaxCarrierBlob = 3; +} diff --git a/Content.Server/StationEvents/Events/BlobSpawn.cs b/Content.Server/StationEvents/Events/BlobSpawn.cs new file mode 100644 index 00000000000..afdfc4b5e68 --- /dev/null +++ b/Content.Server/StationEvents/Events/BlobSpawn.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Content.Server.StationEvents.Components; +using Content.Server.Station.Components; +using Robust.Shared.Map; +using Robust.Shared.Random; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Shuttles.Components; +using Content.Shared.Blob; +using Robust.Server.Player; + +namespace Content.Server.StationEvents.Events; + +public sealed class BlobSpawnRule : StationEventSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPlayerManager _playerSystem = default!; + + protected override void Started(EntityUid uid, BlobSpawnRuleComponent component, GameRuleComponent gameRule, + GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + if (!TryGetRandomStation(out var station)) + { + return; + } + + var locations = EntityQueryEnumerator(); + var validLocations = new List(); + while (locations.MoveNext(out _, out _, out var transform)) + { + if (!HasComp(transform.GridUid)) + continue; + + if (CompOrNull(transform.GridUid)?.Station == station) + { + validLocations.Add(transform.Coordinates); + } + } + + if (validLocations.Count == 0) + { + Sawmill.Info("No find any valid spawn location for blob"); + return; + } + + var playerPool = _playerSystem.ServerSessions.ToList(); + var numBlobs = MathHelper.Clamp(playerPool.Count / component.PlayersPerCarrierBlob, 1, component.MaxCarrierBlob); + + for (var i = 0; i < numBlobs; i++) + { + var coords = _random.Pick(validLocations); + Sawmill.Info($"Creating carrier blob at {coords}"); + var carrier = Spawn(_random.Pick(component.CarrierBlobProtos), coords); + EnsureComp(carrier); + } + + // start blob rule incase it isn't, for the sweet greentext + GameTicker.StartGameRule("Blob"); + } +} diff --git a/Content.Shared/Blob/BlobCarrerComponent.cs b/Content.Shared/Blob/BlobCarrerComponent.cs new file mode 100644 index 00000000000..90465d3880b --- /dev/null +++ b/Content.Shared/Blob/BlobCarrerComponent.cs @@ -0,0 +1,41 @@ +using Content.Shared.Actions; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Blob; + +[RegisterComponent, NetworkedComponent] +#pragma warning disable RA0003 // Class must be explicitly marked as [Virtual], abstract, static or sealed +public partial class BlobCarrierComponent : Component +#pragma warning restore RA0003 // Class must be explicitly marked as [Virtual], abstract, static or sealed +{ + [ViewVariables(VVAccess.ReadWrite), DataField("transformationDelay")] + public float TransformationDelay = 240; + + [ViewVariables(VVAccess.ReadWrite), DataField("alertInterval")] + public float AlertInterval = 30f; + + [ViewVariables(VVAccess.ReadWrite)] + public TimeSpan NextAlert = TimeSpan.FromSeconds(0); + + [ViewVariables(VVAccess.ReadWrite)] + public bool HasMind = false; + + [ViewVariables(VVAccess.ReadWrite)] + public float TransformationTimer = 0; + + [ViewVariables(VVAccess.ReadWrite), + DataField("corePrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string CoreBlobPrototype = "CoreBlobTile"; + + [ViewVariables(VVAccess.ReadWrite), + DataField("coreBlobGhostRolePrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string CoreBlobGhostRolePrototype = "CoreBlobTileGhostRole"; +} + +public partial class TransformToBlobActionEvent : InstantActionEvent +{ + +} + diff --git a/Content.Shared/Blob/BlobCoreComponent.cs b/Content.Shared/Blob/BlobCoreComponent.cs new file mode 100644 index 00000000000..08542cc6eb7 --- /dev/null +++ b/Content.Shared/Blob/BlobCoreComponent.cs @@ -0,0 +1,198 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Roles; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Blob; + +[RegisterComponent] +public partial class BlobCoreComponent : Component +{ + [DataField("antagBlobPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string AntagBlobPrototypeId = "Blob"; + + [ViewVariables(VVAccess.ReadWrite), DataField("attackRate")] + public float AttackRate = 0.8f; + + [ViewVariables(VVAccess.ReadWrite), DataField("returnResourceOnRemove")] + public float ReturnResourceOnRemove = 0.3f; + + [ViewVariables(VVAccess.ReadWrite), DataField("canSplit")] + public bool CanSplit = true; + + [DataField("attackSound")] + public SoundSpecifier AttackSound = new SoundPathSpecifier("/Audio/Animals/Blob/blobattack.ogg"); + + [ViewVariables(VVAccess.ReadWrite)] + public Dictionary ChemDamageDict { get; set; } = new() + { + { + BlobChemType.BlazingOil, new DamageSpecifier() + { + DamageDict = new Dictionary + { + { "Heat", 15 }, + { "Structural", 150 }, + } + } + }, + { + BlobChemType.ReactiveSpines, new DamageSpecifier() + { + DamageDict = new Dictionary + { + { "Blunt", 8 }, + { "Slash", 8 }, + { "Piercing", 8 }, + { "Structural", 150 }, + } + } + }, + { + BlobChemType.ExplosiveLattice, new DamageSpecifier() + { + DamageDict = new Dictionary + { + { "Heat", 5 }, + { "Structural", 150 }, + } + } + }, + { + BlobChemType.ElectromagneticWeb, new DamageSpecifier() + { + DamageDict = new Dictionary + { + { "Structural", 150 }, + { "Heat", 20 }, + }, + } + }, + { + BlobChemType.RegenerativeMateria, new DamageSpecifier() + { + DamageDict = new Dictionary + { + { "Structural", 150 }, + { "Poison", 15 }, + } + } + }, + }; + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary ChemСolors = new() + { + {BlobChemType.ReactiveSpines, Color.FromHex("#637b19")}, + {BlobChemType.BlazingOil, Color.FromHex("#937000")}, + {BlobChemType.RegenerativeMateria, Color.FromHex("#441e59")}, + {BlobChemType.ExplosiveLattice, Color.FromHex("#6e1900")}, + {BlobChemType.ElectromagneticWeb, Color.FromHex("#0d7777")}, + }; + + [ViewVariables(VVAccess.ReadOnly), DataField("blobExplosive")] + public string BlobExplosive = "Blob"; + + [ViewVariables(VVAccess.ReadOnly), DataField("defaultChem")] + public BlobChemType DefaultChem = BlobChemType.ReactiveSpines; + + [ViewVariables(VVAccess.ReadOnly), DataField("currentChem")] + public BlobChemType CurrentChem = BlobChemType.ReactiveSpines; + + [ViewVariables(VVAccess.ReadWrite), DataField("factoryRadiusLimit")] + public float FactoryRadiusLimit = 6f; + + [ViewVariables(VVAccess.ReadWrite), DataField("resourceRadiusLimit")] + public float ResourceRadiusLimit = 3f; + + [ViewVariables(VVAccess.ReadWrite), DataField("nodeRadiusLimit")] + public float NodeRadiusLimit = 4f; + + [ViewVariables(VVAccess.ReadWrite), DataField("attackCost")] + public FixedPoint2 AttackCost = 2; + + [ViewVariables(VVAccess.ReadWrite), DataField("factoryBlobCost")] + public FixedPoint2 FactoryBlobCost = 60; + + [ViewVariables(VVAccess.ReadWrite), DataField("normalBlobCost")] + public FixedPoint2 NormalBlobCost = 4; + + [ViewVariables(VVAccess.ReadWrite), DataField("resourceBlobCost")] + public FixedPoint2 ResourceBlobCost = 40; + + [ViewVariables(VVAccess.ReadWrite), DataField("nodeBlobCost")] + public FixedPoint2 NodeBlobCost = 50; + + [ViewVariables(VVAccess.ReadWrite), DataField("blobbernautCost")] + public FixedPoint2 BlobbernautCost = 60; + + [ViewVariables(VVAccess.ReadWrite), DataField("strongBlobCost")] + public FixedPoint2 StrongBlobCost = 15; + + [ViewVariables(VVAccess.ReadWrite), DataField("reflectiveBlobCost")] + public FixedPoint2 ReflectiveBlobCost = 15; + + [ViewVariables(VVAccess.ReadWrite), DataField("splitCoreCost")] + public FixedPoint2 SplitCoreCost = 100; + + [ViewVariables(VVAccess.ReadWrite), DataField("swapCoreCost")] + public FixedPoint2 SwapCoreCost = 80; + + [ViewVariables(VVAccess.ReadWrite), DataField("swapChemCost")] + public FixedPoint2 SwapChemCost = 40; + + [ViewVariables(VVAccess.ReadWrite), DataField("reflectiveBlobTile")] + public string ReflectiveBlobTile = "ReflectiveBlobTile"; + + [ViewVariables(VVAccess.ReadWrite), DataField("strongBlobTile")] + public string StrongBlobTile = "StrongBlobTile"; + + [ViewVariables(VVAccess.ReadWrite), DataField("normalBlobTile")] + public string NormalBlobTile = "NormalBlobTile"; + + [ViewVariables(VVAccess.ReadWrite), DataField("factoryBlobTile")] + public string FactoryBlobTile = "FactoryBlobTile"; + + [ViewVariables(VVAccess.ReadWrite), DataField("resourceBlobTile")] + public string ResourceBlobTile = "ResourceBlobTile"; + + [ViewVariables(VVAccess.ReadWrite), DataField("nodeBlobTile")] + public string NodeBlobTile = "NodeBlobTile"; + + [ViewVariables(VVAccess.ReadWrite), DataField("coreBlobTile")] + public string CoreBlobTile = "CoreBlobTileGhostRole"; + + [ViewVariables(VVAccess.ReadWrite), DataField("coreBlobTotalHealth")] + public FixedPoint2 CoreBlobTotalHealth = 400; + + [ViewVariables(VVAccess.ReadWrite), + DataField("ghostPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ObserverBlobPrototype = "MobObserverBlob"; + + [DataField("greetSoundNotification")] + public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Effects/clang.ogg"); + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? Observer = default!; + + [ViewVariables(VVAccess.ReadOnly)] + public List BlobTiles = new(); + + public TimeSpan NextAction = TimeSpan.Zero; + + [ViewVariables(VVAccess.ReadWrite)] + public FixedPoint2 Points = 0; +} + +[Serializable, NetSerializable] +public enum BlobChemType : byte +{ + BlazingOil, + ReactiveSpines, + RegenerativeMateria, + ExplosiveLattice, + ElectromagneticWeb +} diff --git a/Content.Shared/Blob/BlobObserverComponent.cs b/Content.Shared/Blob/BlobObserverComponent.cs new file mode 100644 index 00000000000..6f4e05886a8 --- /dev/null +++ b/Content.Shared/Blob/BlobObserverComponent.cs @@ -0,0 +1,111 @@ +using Content.Shared.Actions; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Blob; + +[RegisterComponent, NetworkedComponent] +public partial class BlobObserverComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly)] + public bool IsProcessingMoveEvent; + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? Core = default!; + + [ViewVariables(VVAccess.ReadOnly)] + public bool CanMove = true; + + [ViewVariables(VVAccess.ReadOnly)] + public BlobChemType SelectedChemId = BlobChemType.ReactiveSpines; +} + +[Serializable, NetSerializable] +public sealed class BlobChemSwapComponentState : ComponentState +{ + public BlobChemType SelectedChem; +} + +[Serializable, NetSerializable] +public sealed class BlobChemSwapBoundUserInterfaceState : BoundUserInterfaceState +{ + public readonly Dictionary ChemList; + public readonly BlobChemType SelectedChem; + + public BlobChemSwapBoundUserInterfaceState(Dictionary chemList, BlobChemType selectedId) + { + ChemList = chemList; + SelectedChem = selectedId; + } +} + +[Serializable, NetSerializable] +public sealed class BlobChemSwapPrototypeSelectedMessage : BoundUserInterfaceMessage +{ + public readonly BlobChemType SelectedId; + + public BlobChemSwapPrototypeSelectedMessage(BlobChemType selectedId) + { + SelectedId = selectedId; + } +} + +[Serializable, NetSerializable] +public enum BlobChemSwapUiKey : byte +{ + Key +} + + +public partial class BlobCreateFactoryActionEvent : WorldTargetActionEvent +{ + +} + +public partial class BlobCreateResourceActionEvent : WorldTargetActionEvent +{ + +} + +public partial class BlobCreateNodeActionEvent : WorldTargetActionEvent +{ + +} + +public partial class BlobCreateBlobbernautActionEvent : WorldTargetActionEvent +{ + +} + +public partial class BlobSplitCoreActionEvent : WorldTargetActionEvent +{ + +} + +public partial class BlobSwapCoreActionEvent : WorldTargetActionEvent +{ + +} + +public partial class BlobToCoreActionEvent : InstantActionEvent +{ + +} + +public partial class BlobToNodeActionEvent : InstantActionEvent +{ + +} + +public partial class BlobHelpActionEvent : InstantActionEvent +{ + +} + +public partial class BlobSwapChemActionEvent : InstantActionEvent +{ + +} + diff --git a/Content.Shared/Blob/BlobTileComponentState.cs b/Content.Shared/Blob/BlobTileComponentState.cs new file mode 100644 index 00000000000..634320eeef0 --- /dev/null +++ b/Content.Shared/Blob/BlobTileComponentState.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Blob; + +[Serializable, NetSerializable] +public sealed class BlobTileComponentState : ComponentState +{ + public Color Color; +} diff --git a/Content.Shared/Blob/BlobbernautComponentState.cs b/Content.Shared/Blob/BlobbernautComponentState.cs new file mode 100644 index 00000000000..9efe60279ca --- /dev/null +++ b/Content.Shared/Blob/BlobbernautComponentState.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Blob; + +[Serializable, NetSerializable] +public sealed class BlobbernautComponentState : ComponentState +{ + public Color Color; +} diff --git a/Content.Shared/Blob/SharedBlobObserverSystem.cs b/Content.Shared/Blob/SharedBlobObserverSystem.cs new file mode 100644 index 00000000000..db8f00c2289 --- /dev/null +++ b/Content.Shared/Blob/SharedBlobObserverSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.Movement.Events; + +namespace Content.Shared.Blob; + +public abstract class SharedBlobObserverSystem: EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUpdateCanMove); + } + + private void OnUpdateCanMove(EntityUid uid, BlobObserverComponent component, UpdateCanMoveEvent args) + { + if (component.CanMove) + return; + + args.Cancel(); + } +} diff --git a/Content.Shared/Blob/SharedBlobPod.cs b/Content.Shared/Blob/SharedBlobPod.cs new file mode 100644 index 00000000000..c262193ff51 --- /dev/null +++ b/Content.Shared/Blob/SharedBlobPod.cs @@ -0,0 +1,10 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Blob; + + +[Serializable, NetSerializable] +public partial class BlobPodZombifyDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Blob/SharedBlobTileComponent.cs b/Content.Shared/Blob/SharedBlobTileComponent.cs new file mode 100644 index 00000000000..762817ed78b --- /dev/null +++ b/Content.Shared/Blob/SharedBlobTileComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Blob; + +[NetworkedComponent] +public partial class SharedBlobTileComponent : Component +{ + [DataField("color")] + public Color Color = Color.White; +} diff --git a/Content.Shared/Blob/SharedBlobTileSystem.cs b/Content.Shared/Blob/SharedBlobTileSystem.cs new file mode 100644 index 00000000000..d507018597d --- /dev/null +++ b/Content.Shared/Blob/SharedBlobTileSystem.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Blob; + +public abstract class SharedBlobTileSystem : EntitySystem +{ + +} diff --git a/Content.Shared/Blob/SharedBlobbernautComponent.cs b/Content.Shared/Blob/SharedBlobbernautComponent.cs new file mode 100644 index 00000000000..e354264e0e3 --- /dev/null +++ b/Content.Shared/Blob/SharedBlobbernautComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Blob; + +[NetworkedComponent] +public partial class SharedBlobbernautComponent : Component +{ + [DataField("color")] + public Color Color = Color.White; +} diff --git a/Content.Shared/Chemistry/Components/SharedSmokeComponent.cs b/Content.Shared/Chemistry/Components/SharedSmokeComponent.cs new file mode 100644 index 00000000000..c2b976d97d9 --- /dev/null +++ b/Content.Shared/Chemistry/Components/SharedSmokeComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Chemistry.Components; + +[NetworkedComponent] +public partial class SharedSmokeComponent : Component +{ + [DataField("color")] + public Color Color = Color.White; +} diff --git a/Content.Shared/Chemistry/Components/SmokeComponentState.cs b/Content.Shared/Chemistry/Components/SmokeComponentState.cs new file mode 100644 index 00000000000..534e0773601 --- /dev/null +++ b/Content.Shared/Chemistry/Components/SmokeComponentState.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Chemistry.Components; + +[Serializable, NetSerializable] +public sealed class SmokeComponentState : ComponentState +{ + public Color Color; +} diff --git a/Content.Shared/DrawDepth/DrawDepth.cs b/Content.Shared/DrawDepth/DrawDepth.cs index f7b1f3648ab..d5bf131dc8d 100644 --- a/Content.Shared/DrawDepth/DrawDepth.cs +++ b/Content.Shared/DrawDepth/DrawDepth.cs @@ -9,32 +9,34 @@ public enum DrawDepth /// /// This is for sub-floors, the floors you see after prying off a tile. /// - LowFloors = DrawDepthTag.Default - 11, + LowFloors = DrawDepthTag.Default - 13, // various entity types that require different // draw depths, as to avoid hiding #region SubfloorEntities - ThickPipe = DrawDepthTag.Default - 10, - ThickWire = DrawDepthTag.Default - 9, - ThinPipe = DrawDepthTag.Default - 8, - ThinWire = DrawDepthTag.Default - 7, + ThickPipe = DrawDepthTag.Default - 12, + ThickWire = DrawDepthTag.Default - 11, + ThinPipe = DrawDepthTag.Default - 10, + ThinWire = DrawDepthTag.Default - 9, #endregion /// /// Things that are beneath regular floors. /// - BelowFloor = DrawDepthTag.Default - 7, + BelowFloor = DrawDepthTag.Default - 8, /// /// Used for entities like carpets. /// - FloorTiles = DrawDepthTag.Default - 6, + FloorTiles = DrawDepthTag.Default - 7, /// /// Things that are actually right on the floor, like puddles. This does not mean objects like /// tables, even though they are technically "on the floor". /// - FloorObjects = DrawDepthTag.Default - 5, + FloorObjects = DrawDepthTag.Default - 6, + + BlobTiles = DrawDepthTag.Default - 5, DeadMobs = DrawDepthTag.Default - 4, diff --git a/Content.Shared/Physics/CollisionGroup.cs b/Content.Shared/Physics/CollisionGroup.cs index 1c10fefd5dc..a338eb4d603 100644 --- a/Content.Shared/Physics/CollisionGroup.cs +++ b/Content.Shared/Physics/CollisionGroup.cs @@ -13,16 +13,18 @@ namespace Content.Shared.Physics; [FlagsFor(typeof(CollisionLayer)), FlagsFor(typeof(CollisionMask))] public enum CollisionGroup { - None = 0, - Opaque = 1 << 0, // 1 Blocks light, can be hit by lasers - Impassable = 1 << 1, // 2 Walls, objects impassable by any means - MidImpassable = 1 << 2, // 4 Mobs, players, crabs, etc - HighImpassable = 1 << 3, // 8 Things on top of tables and things that block tall/large mobs. - LowImpassable = 1 << 4, // 16 For things that can fit under a table or squeeze under an airlock - GhostImpassable = 1 << 5, // 32 Things impassible by ghosts/observers, ie blessed tiles or forcefields - BulletImpassable = 1 << 6, // 64 Can be hit by bullets - InteractImpassable = 1 << 7, // 128 Blocks interaction/InRangeUnobstructed - DoorPassable = 1 << 8, // 256 Allows door to close over top, Like blast doors over conveyors for disposals rooms/cargo. + None = 0, + Opaque = 1 << 0, // 1 Blocks light, can be hit by lasers + Impassable = 1 << 1, // 2 Walls, objects impassable by any means + BlobImpassable = 1 << 2, // 2 Blob Tiles + MidImpassable = 1 << 3, // 4 Mobs, players, crabs, etc + HighImpassable = 1 << 4, // 8 Things on top of tables and things that block tall/large mobs. + LowImpassable = 1 << 5, // 16 For things that can fit under a table or squeeze under an airlock + GhostImpassable = 1 << 6, // 32 Things impassible by ghosts/observers, ie blessed tiles or forcefields + BlobGhostImpassable = 1 << 7, // 32 blob + BulletImpassable = 1 << 8, // 64 Can be hit by bullets + InteractImpassable = 1 << 9, // 128 Blocks interaction/InRangeUnobstructed + DoorPassable = 1 << 10, // 256 Allows door to close over top, Like blast doors over conveyors for disposals rooms/cargo. MapGrid = MapGridHelpers.CollisionGroup, // Map grids, like shuttles. This is the actual grid itself, not the walls or other entities connected to the grid. @@ -30,30 +32,36 @@ public enum CollisionGroup AllMask = -1, // Humanoids, etc. - MobMask = Impassable | HighImpassable | MidImpassable | LowImpassable, + MobMask = Impassable | HighImpassable | MidImpassable | LowImpassable | BlobImpassable, MobLayer = Opaque | BulletImpassable, // Mice, drones - SmallMobMask = Impassable | LowImpassable, + SmallMobMask = Impassable | LowImpassable | BlobImpassable, SmallMobLayer = Opaque | BulletImpassable, // Birds/other small flyers - FlyingMobMask = Impassable | HighImpassable, + FlyingMobMask = Impassable | HighImpassable | BlobImpassable, FlyingMobLayer = Opaque | BulletImpassable, + // Blob Mobs + BlobMobMask = Impassable | HighImpassable | LowImpassable, + BlobMobLayer = Opaque | BulletImpassable, + // Blob flyers + FlyingBlobMobMask = Impassable | HighImpassable, + FlyingBlobMobLayer = Opaque | BulletImpassable, // Mechs - LargeMobMask = Impassable | HighImpassable | MidImpassable | LowImpassable, + LargeMobMask = Impassable | HighImpassable | MidImpassable | LowImpassable | BlobImpassable, LargeMobLayer = Opaque | HighImpassable | MidImpassable | LowImpassable | BulletImpassable, // Machines, computers - MachineMask = Impassable | MidImpassable | LowImpassable, + MachineMask = Impassable | MidImpassable | LowImpassable | BlobImpassable, MachineLayer = Opaque | MidImpassable | LowImpassable | BulletImpassable, ConveyorMask = Impassable | MidImpassable | LowImpassable | DoorPassable, // Tables that SmallMobs can go under - TableMask = Impassable | MidImpassable, + TableMask = Impassable | MidImpassable | BlobImpassable, TableLayer = MidImpassable, // Tabletop machines, windoors, firelocks - TabletopMachineMask = Impassable | HighImpassable, + TabletopMachineMask = Impassable | HighImpassable | BlobImpassable, // Tabletop machines TabletopMachineLayer = Opaque | HighImpassable | BulletImpassable, @@ -66,14 +74,16 @@ public enum CollisionGroup // Soap, spills SlipLayer = MidImpassable | LowImpassable, - ItemMask = Impassable | HighImpassable, - ThrownItem = Impassable | HighImpassable | BulletImpassable, + ItemMask = Impassable | HighImpassable | BlobImpassable, + ThrownItem = Impassable | HighImpassable | BulletImpassable | BlobImpassable, WallLayer = Opaque | Impassable | HighImpassable | MidImpassable | LowImpassable | BulletImpassable | InteractImpassable, + BlobTileLayer = Opaque | BulletImpassable, GlassLayer = Impassable | HighImpassable | MidImpassable | LowImpassable | BulletImpassable | InteractImpassable, HalfWallLayer = MidImpassable | LowImpassable, // Statue, monument, airlock, window FullTileMask = Impassable | HighImpassable | MidImpassable | LowImpassable | InteractImpassable, + BlobTileMask = BlobImpassable, // FlyingMob can go past FullTileLayer = Opaque | HighImpassable | MidImpassable | LowImpassable | BulletImpassable | InteractImpassable, diff --git a/Content.Shared/Popups/SharedPopupSystem.cs b/Content.Shared/Popups/SharedPopupSystem.cs index aeb85de2f59..537e6516709 100644 --- a/Content.Shared/Popups/SharedPopupSystem.cs +++ b/Content.Shared/Popups/SharedPopupSystem.cs @@ -178,6 +178,7 @@ public enum PopupType : byte /// but is not life-threatening. /// Large, - LargeCaution + LargeCaution, + LargeGreen } } diff --git a/Resources/Audio/Animals/Blob/attackblob.ogg b/Resources/Audio/Animals/Blob/attackblob.ogg new file mode 100644 index 00000000000..3283e5187ca Binary files /dev/null and b/Resources/Audio/Animals/Blob/attackblob.ogg differ diff --git a/Resources/Audio/Animals/Blob/blobattack.ogg b/Resources/Audio/Animals/Blob/blobattack.ogg new file mode 100644 index 00000000000..1c87fa64361 Binary files /dev/null and b/Resources/Audio/Animals/Blob/blobattack.ogg differ diff --git a/Resources/Locale/en-US/blob/blob.ftl b/Resources/Locale/en-US/blob/blob.ftl new file mode 100644 index 00000000000..816717e109b --- /dev/null +++ b/Resources/Locale/en-US/blob/blob.ftl @@ -0,0 +1,123 @@ +# Popups +blob-target-normal-blob-invalid = Wrong blob type, select a normal blob. +blob-target-factory-blob-invalid = Wrong blob type, select a factory blob. +blob-target-node-blob-invalid = Wrong blob type, select a node blob. +blob-target-close-to-resource = Too close to another resource blob. +blob-target-nearby-not-node = No node or resource blob nearby. +blob-target-close-to-node = Too close to another node. +blob-target-already-produce-blobbernaut = This factory has already produced a blobbernaut. +blob-cant-split = You can not split the blob core. +blob-not-have-nodes = You have no nodes. +blob-not-enough-resources = Not enough resources. +blob-help = Only God can help you. +blob-swap-chem = In development. +blob-mob-attack-blob = You can not attack a blob. +blob-get-resource = +{ $point } +blob-spent-resource = -{ $point } +blobberaut-not-on-blob-tile = You are dying while not on blob tiles. +blobberaut-factory-destroy = You are dying because your factory blob was destroyed. +carrier-blob-alert = You have { $second } seconds left before transformation. + +blob-mob-zombify-second-start = { $pod } starts turning you into a zombie. +blob-mob-zombify-third-start = { $pod } starts turning { $target } into a zombie. + +blob-mob-zombify-second-end = { $pod } turns you into a zombie. +blob-mob-zombify-third-end = { $pod } turns { $target } into a zombie. + +# UI +blob-chem-swap-ui-window-name = Swap chemicals +blob-chem-reactivespines-info = Reactive Spines + Deals 25 brute damage. +blob-chem-blazingoil-info = Blazing Oil + Deals 15 burn damage and lights targets on fire. + Makes you vulnerable to water. +blob-chem-regenerativemateria-info = Regenerative Materia + Deals 6 brute damage and 15 toxin damage. + The blob core regenerates health 10 times faster than normal and generates 1 extra resource. +blob-chem-explosivelattice-info = Explosive Lattice + Deals 5 burn damage and explodes the target, dealing 10 brute damage. + Spores explode on death. + You become immune to explosions. + You take 50% more damage from burns and electrical shock. +blob-chem-electromagneticweb-info = Electromagnetic Web + Deals 20 burn damage, 20% chance to cause an EMP pulse when attacking. + Blob tiles cause an EMP pulse when destroyed. + You take 25% more brute and heat damage. + +# Announcment +blob-alert-recall-shuttle = The emergency shuttle can not be sent while there is a level 5 biohazard present on the station. +blob-alert-detect = Confirmed outbreak of level 5 biohazard aboard the station. All personnel must contain the outbreak. The emergency shuttles can not be sent due to contamination risks. +blob-alert-critical = Biohazard level critical, nuclear authentication codes have been sent to the station. Central Command orders any remaining personnel to activate the self-destruction mechanism. + +# Actions +blob-create-factory-action-name = Place Factory Blob (60) +blob-create-factory-action-desc = Turns selected normal blob into a factory blob, which will produce up to 3 spores and a blobbernaut if placed next to a core or a node. +blob-create-resource-action-name = Place Resource Blob (40) +blob-create-resource-action-desc = Turns selected normal blob into a resource blob which will generates resources if placed next to a core or a node. +blob-create-node-action-name = Place Node Blob (50) +blob-create-node-action-desc = Turns selected normal blob into a node blob. + A node blob will activate effects of factory and resource blobs, heal other blobs and slowly expand, destroying walls and creating normal blobs. +blob-produce-blobbernaut-action-name = Produce a Blobbernaut (60) +blob-produce-blobbernaut-action-desc = Creates a blobbernaut on the selected factory. Each factory can only do this once. The blobbernaut will take damage outside of blob tiles and heal when close to nodes. +blob-split-core-action-name = Split Core (100) +blob-split-core-action-desc = You can only do this once. Turns selected node into an independent core that will act on its own. +blob-swap-core-action-name = Relocate Core (80) +blob-swap-core-action-desc = Swaps the location of your core and the selected node. +blob-teleport-to-core-action-name = Jump to Core (0) +blob-teleport-to-core-action-desc = Teleports you to your Blob Core. +blob-teleport-to-node-action-name = Jump to Node (0) +blob-teleport-to-node-action-desc = Teleports you to a random blob node. +blob-help-action-name = Help +blob-help-action-desc = Get basic information about playing as blob. +blob-swap-chem-action-name = Swap chemicals (40) +blob-swap-chem-action-desc = Lets you swap your current chemical to one of 4 randomized options. +blob-carrier-transform-to-blob-action-name = Transform into a blob +blob-carrier-transform-to-blob-action-desc = Instantly destoys your body and creates a blob core. Make sure to stand on a floor tile, otherwise you will simply disappear. + +# Ghost role +blob-carrier-role-name = Blob carrier +blob-carrier-role-desc = A blob-infected creature. +blob-carrier-role-rules = You are an antagonist. You have 4 minutes before you transform into a blob. + Use this time to find a safe spot on the station. Keep in mind that you will be very weak right after the transformation. + +# Verbs +blob-pod-verb-zombify = Zombify +blob-verb-upgrade-to-strong = Upgrade to Strong Blob +blob-verb-upgrade-to-reflective = Upgrade to Reflective Blob +blob-verb-remove-blob-tile = Remove Blob + +# Alerts +blob-resource-alert-name = Core Resources +blob-resource-alert-desc = Your resources produced by the core and resource blobs. Use them to expand and create special blobs. +blob-health-alert-name = Core Health +blob-health-alert-desc = Your core's health. You will die if it reaches zero. + +# Greeting +blob-role-greeting = + You are blob - a parasitic space creature capable of destroying entire stations. + Your goal is to survive and grow as large as possible. + You are almost invulnerable to physical damage, but heat can still hurt you. + Use Alt+LMB to upgrade normal blob tiles to strong blob and strong blob to reflective blob. + Make sure to place resource blobs to generate resources. + Keep in mind that resource blobs and factories will only work when next to node blobs or cores. +blob-zombie-greeting = You were infected and raised by a blob spore. Now you must help the blob take over the station. + +# End round +blob-round-end-result = {$blobCount -> +[one] There was one blob. +*[other] There were {$blobCount} blobs. +} + +blob-user-was-a-blob = [color=gray]{$user}[/color] was a blob. +blob-user-was-a-blob-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color]) was a blob. +blob-was-a-blob-named = [color=White]{$name}[/color] was a blob. + +preset-blob-objective-issuer-blob = [color=#33cc00]Blob[/color] + +blob-user-was-a-blob-with-objectives = [color=gray]{$user}[/color] was a blob who had the following objectives: +blob-user-was-a-blob-with-objectives-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color]) was a blob who had the following objectives: +blob-was-a-blob-with-objectives-named = [color=White]{$name}[/color] was a blob who had the following objectives: + +# Objectivies +objective-condition-blob-capture-title = Take over the station +objective-condition-blob-capture-description = Your only goal is to take over the whole station. You need to have at least {$count} blob tiles. diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 8f2c7481c36..59aaa7c2671 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -224,3 +224,9 @@ ghost-role-information-syndicate-reinforcement-rules = Normal syndicate antagoni ghost-role-information-syndicate-monkey-reinforcement-name = Syndicate Monkey Agent ghost-role-information-syndicate-monkey-reinforcement-description = Someone needs reinforcements. You, a trained monkey, will help them. ghost-role-information-syndicate-monkey-reinforcement-rules = Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. + +ghost-role-information-blobbernaut-name = Blobbernaut +ghost-role-information-blobbernaut-description = You are a Blobbernaut. You must defend blob core. + +ghost-role-information-blob-name = Blob +ghost-role-information-blob-description = You are a Blob. You must consume the station. diff --git a/Resources/Locale/en-US/prototypes/roles/antags.ftl b/Resources/Locale/en-US/prototypes/roles/antags.ftl index 6379cfb000f..3d0c8fe448d 100644 --- a/Resources/Locale/en-US/prototypes/roles/antags.ftl +++ b/Resources/Locale/en-US/prototypes/roles/antags.ftl @@ -36,3 +36,6 @@ roles-antag-terminator-objective = Kill the target at all costs, the future depe roles-antag-changeling-name = Changeling roles-antag-changeling-objective = Use your special alien abilites to aid in completing your objectives. + +roles-antag-blob-name = Blob +roles-antag-blob-objective = Take over the station. diff --git a/Resources/Locale/ru-RU/blob/blob.ftl b/Resources/Locale/ru-RU/blob/blob.ftl new file mode 100644 index 00000000000..d8b05553aa6 --- /dev/null +++ b/Resources/Locale/ru-RU/blob/blob.ftl @@ -0,0 +1,123 @@ +# Popups +blob-target-normal-blob-invalid = Неподходящий тип блоба, необходимо выбрать нормального блоба. +blob-target-factory-blob-invalid = Неподходящий тип блоба, необходимо выбрать фабрику. +blob-target-node-blob-invalid = Неподходящий тип блоба, необходимо выбрать узел. +blob-target-close-to-resource = Слишком близко к другому ресурсному тайлу. +blob-target-nearby-not-node = Рядом нету узла или ядра. +blob-target-close-to-node = Слишком близко к другому узлу. +blob-target-already-produce-blobbernaut = Данная фабрика уже произвела блоббернаута. +blob-cant-split = Вы не можете разделить ядро. +blob-not-have-nodes = У вас нету узлов. +blob-not-enough-resources = Не хватает ресурсов для действия. +blob-help = Вам поможет только бог. +blob-swap-chem = В разработке. +blob-mob-attack-blob = Вы не можете атаковать блоба. +blob-get-resource = +{ $point } +blob-spent-resource = -{ $point } +blobberaut-not-on-blob-tile = Вы умираете без тайлов блоба под ногами. +blobberaut-factory-destroy = Ваша фабрика была разрушена, вы умираете. +carrier-blob-alert = У вас осталось { $second } секунд до превращения. + +blob-mob-zombify-second-start = { $pod } начинает превращать вас в зомби +blob-mob-zombify-third-start = { $pod } начинает превращать { $target } в зомби + +blob-mob-zombify-second-end = { $pod } превращает вас в зомби +blob-mob-zombify-third-end = { $pod } превращает { $target } в зомби + +# UI +blob-chem-swap-ui-window-name = Смена химиката +blob-chem-reactivespines-info = Реактивные шипы + Наносит 25 единиц брут урона. +blob-chem-blazingoil-info = Пылающее масло + Наносит 15 урона ожогами и поджигает цели. + Делает вас уязвимым к воде. +blob-chem-regenerativemateria-info = Регенеративная Материя + Наносит 15 единиц урона ядами. + Ядро востанавливает здоровье в 10 раз быстрее и дает на 1 очко больше. +blob-chem-explosivelattice-info = Взрывная решетка + Наносит 5 единиц урона ожогами и взрывает цель, нанося 10 брут урона. + Споры при смерти взрываются. + Вы получаете имунитет к взрывам. + Вы получаете на 50% больше урона ожогами и электричеством. +blob-chem-electromagneticweb-info = Электромагнитная паутина + Наносит 20 урона ожогами, 20% шанс вызывать ЭМИ разряд при атаке. + Любая уничтоженая плитка гарантировано вызовет ЭМИ. + Вы получаете на 25% больше урона теплом и брутом. + +# Announcment +blob-alert-recall-shuttle = Эвакуационный шатл не может быть отправлен на станцию пока существует биологическая угроза 5 уровня. +blob-alert-detect = На станции была обнаружена биологическая угроза 5 уровня, обьявлена изоляция станции. +blob-alert-critical = Биологическая угроза достигла критической массы, вам отправлены коды от ядерной боеголовки, вы должны немедленно взорвать станцию. + +# Actions +blob-create-factory-action-name = Создать блоб фабрику (60) +blob-create-factory-action-desc = Превращает выбраного нормального блоба в фабрику, которая способна произвести 3 споры и блоббернаута, если рядом есть узел или ядро. +blob-create-resource-action-name = Создать ресурсный блоб (40) +blob-create-resource-action-desc = Превращает выбраного нормального блоба в ресурсного блоба который будет производить ресурсы если рядом есть узлы или ядро. +blob-produce-blobbernaut-action-name = Произвести блоббернаута на фабрике (60) +blob-produce-blobbernaut-action-desc = Производит на выбраной фабрике единожды блоббернаута который будет получать урон вне тайлов блоба и лечиться рядом с узлами. +blob-split-core-action-name = Разделить ядро (100) +blob-split-core-action-desc = Единоразово позволяет превратить выбраный узел в самостоятельное ядро которое будет развиваться независимо от вас. +blob-swap-core-action-name = Переместить ядро (80) +blob-swap-core-action-desc = Производит рокировку вашего ядра с выбраным узлом. +blob-teleport-to-core-action-name = Телепортироваться к ядру (0) +blob-teleport-to-core-action-desc = Телепортирует вашу камеру к вашему ядру. +blob-teleport-to-node-action-name = Телепортироваться у случайному узлу (0) +blob-teleport-to-node-action-desc = Телепортирует вашу камеру к одному из ваших узлов. +blob-create-node-action-name = Создать блоб узел (50) +blob-create-node-action-desc = Превращает выбраного нормального блоба в блоб узел. + Узел будет активировать эфекты других блобов, лечить и расширяться в пределах своего действия уничтожая стены и создавая нормальные блобы. +blob-help-action-name = Помощь +blob-help-action-desc = Получите базовую информацию по игра за блоба. +blob-swap-chem-action-name = Сменить химикат блоба (40) +blob-swap-chem-action-desc = Позволяет вам сменить текущий химикат на один из 4 случайных. +blob-carrier-transform-to-blob-action-name = Превратиться в блоба +blob-carrier-transform-to-blob-action-desc = Мгновенно разрывает ваше тело и создает ядро блоба. Учтите что если под вами не будет тайлов - вы просто исчезнете. + +# Ghost role +blob-carrier-role-name = Носитель блоба +blob-carrier-role-desc = Сущность зараженная "блобом". +blob-carrier-role-rules = Вы антагонист. У вас есть 4 минуты перед тем как вы превратитесь в блоба. + Найдите за это время укромное место для стартовой точки заражения станции, ведь вы очень слабы в первые минуты после создания ядра. + +# Verbs +blob-pod-verb-zombify = Зомбировать +blob-verb-upgrade-to-strong = Улучшить до сильного блоба +blob-verb-upgrade-to-reflective = Улучшить до отражающего блоба +blob-verb-remove-blob-tile = Убрать блоба + +# Alerts +blob-resource-alert-name = Ресурсы ядра +blob-resource-alert-desc = Ваши ресурсы которые производят ресурсные блобы и само ядро, требуются для разрастанция и особых блобов. +blob-health-alert-name = Здоровье ядра +blob-health-alert-desc = Здоровье вашего ядра. Если оно опустится до 0 вы умрёте. + +# Greeting +blob-role-greeting = + Вы блоб - космический паразит который захватывает станции. + Ваша цель - стать как можно больше не дав себя уничтожить. + Используйте горячие клавиши Alt+LMB чтобы улучшать обычные плитки до сильных а сильные до отражающих. + Позаботьтесь о получении ресурсов с блобов ресурсов. + Вы практически неуязвимы к физическим повреждениям, но опасайтесь теплового урона. + Учтите что особые клетки блоба работают только возле узлов или ядра. +blob-zombie-greeting = Вы были заражены спорой блоба которая вас воскресила, теперь вы действуете в интересах блоба. + +# End round +blob-round-end-result = {$blobCount -> +[one] Был один блоб. +*[other] Было {$blobCount} блобов. +} + +blob-user-was-a-blob = [color=gray]{$user}[/color] был блобом. +blob-user-was-a-blob-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color]) был блобом. +blob-was-a-blob-named = [color=White]{$name}[/color] был блобом. + +preset-blob-objective-issuer-blob = [color=#33cc00]Блоб[/color] + +blob-user-was-a-blob-with-objectives = [color=gray]{$user}[/color] был блобом и имел следующие цели: +blob-user-was-a-blob-with-objectives-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color]) был блобом и имел следующие цели: +blob-was-a-blob-with-objectives-named = [color=White]{$name}[/color] был блобом и имел следующие цели: + +# Objectivies +objective-condition-blob-capture-title = Захватить станцию +objective-condition-blob-capture-description = Ваша единственная цель - полное и безоговорочное поглощение станции. Вам необходимо владеть как минимум {$count} тайлами блоба. diff --git a/Resources/Prototypes/Actions/blob.yml b/Resources/Prototypes/Actions/blob.yml new file mode 100644 index 00000000000..1d1639b08bd --- /dev/null +++ b/Resources/Prototypes/Actions/blob.yml @@ -0,0 +1,93 @@ +- type: worldTargetAction + id: CreateBlobFactory + icon: Interface/Actions/blobFactory.png + name: blob-create-factory-action-name + description: blob-create-factory-action-desc + serverEvent: !type:BlobCreateFactoryActionEvent + range: 200 + checkCanAccess: false + +- type: worldTargetAction + id: CreateBlobResource + icon: Interface/Actions/blobResource.png + name: blob-create-resource-action-name + description: blob-create-resource-action-desc + serverEvent: !type:BlobCreateResourceActionEvent + range: 200 + checkCanAccess: false + +- type: worldTargetAction + id: CreateBlobNode + icon: Interface/Actions/blobNode.png + name: blob-create-node-action-name + description: blob-create-node-action-desc + serverEvent: !type:BlobCreateNodeActionEvent + range: 200 + checkCanAccess: false + +- type: worldTargetAction + id: CreateBlobbernaut + icon: Interface/Actions/blobBlobbernaut.png + name: blob-produce-blobbernaut-action-name + description: blob-produce-blobbernaut-action-desc + serverEvent: !type:BlobCreateBlobbernautActionEvent + range: 200 + checkCanAccess: false + +- type: worldTargetAction + id: SplitBlobCore + icon: Interface/Actions/blobSplit.png + name: blob-split-core-action-name + description: blob-split-core-action-desc + serverEvent: !type:BlobSplitCoreActionEvent + range: 200 + checkCanAccess: false + +- type: worldTargetAction + id: SwapBlobCore + icon: Interface/Actions/blobSwap.png + name: blob-swap-core-action-name + description: blob-swap-core-action-desc + serverEvent: !type:BlobSwapCoreActionEvent + range: 200 + checkCanAccess: false + +- type: instantAction + id: TeleportBlobToCore + icon: Interface/Actions/blobToCore.png + name: blob-teleport-to-core-action-name + description: blob-teleport-to-core-action-desc + serverEvent: !type:BlobToCoreActionEvent + #checkCanAccess: false + +- type: instantAction + id: TeleportBlobToNode + icon: Interface/Actions/blobToNode.png + name: blob-teleport-to-node-action-name + description: blob-teleport-to-node-action-desc + serverEvent: !type:BlobToNodeActionEvent + #checkCanAccess: false + +- type: instantAction + id: HelpBlob + icon: Interface/Actions/blobHelp.png + name: blob-help-action-name + description: blob-help-action-desc + serverEvent: !type:BlobHelpActionEvent + #checkCanAccess: false + +- type: instantAction + id: SwapBlobChem + icon: Interface/Actions/blobChemSwap.png + name: blob-swap-chem-action-name + description: blob-swap-chem-action-desc + serverEvent: !type:BlobSwapChemActionEvent + #checkCanAccess: false + +- type: instantAction + id: TransformToBlob + icon: Interface/Actions/blobToCore.png + name: blob-carrier-transform-to-blob-action-name + description: blob-carrier-transform-to-blob-action-desc + serverEvent: !type:TransformToBlobActionEvent + #checkCanAccess: false diff --git a/Resources/Prototypes/Alerts/blob.yml b/Resources/Prototypes/Alerts/blob.yml new file mode 100644 index 00000000000..0636c43b004 --- /dev/null +++ b/Resources/Prototypes/Alerts/blob.yml @@ -0,0 +1,92 @@ +- type: alert + id: BlobResource + icons: + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point0 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point1 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point2 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point3 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point4 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point5 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point6 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point7 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point8 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point9 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point10 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point11 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point12 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point13 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point14 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point15 + - sprite: /Textures/Interface/Alerts/blob_resource.rsi + state: point16 + name: blob-resource-alert-name + description: blob-resource-alert-desc + minSeverity: 0 + maxSeverity: 16 + +- type: alert + id: BlobHealth + category: Health + icons: + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point0 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point1 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point2 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point3 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point4 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point5 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point6 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point7 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point8 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point9 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point10 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point11 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point12 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point13 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point14 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point15 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point16 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point17 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point18 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point19 + - sprite: /Textures/Interface/Alerts/blob_health.rsi + state: point20 + name: blob-health-alert-name + description: blob-health-alert-desc + minSeverity: 0 + maxSeverity: 20 diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml index 114422404f5..4aa954ffd29 100644 --- a/Resources/Prototypes/Damage/modifier_sets.yml +++ b/Resources/Prototypes/Damage/modifier_sets.yml @@ -189,6 +189,57 @@ Heat: 1.5 Shock: 1.2 +- type: damageModifierSet + id: Arachnid # Don't do too well with high temperatures, venomous (well some kinds anyways) and have an exo-skeleton (so probably harder to stab but easier to... break?) + coefficients: + Blunt: 1.15 + Piercing: 1.15 + Slash: 0.85 + Heat: 1.25 + Poison: 0.8 + +- type: damageModifierSet + id: BaseBlob # ReactiveSpines + coefficients: + Blunt: 0.25 + Slash: 0.25 + Piercing: 0.25 + Heat: 2.00 + +- type: damageModifierSet + id: ElectromagneticWebBlob # Explosive Lattice + coefficients: + Blunt: 0.50 + Slash: 0.50 + Piercing: 0.50 + Heat: 2.50 + Shock: 0.50 + +- type: damageModifierSet + id: ExplosiveLatticeBlob # Explosive Lattice + coefficients: + Blunt: 0.25 + Slash: 0.25 + Piercing: 0.25 + Heat: 3.00 + Shock: 1.50 + +- type: damageModifierSet + id: BlobMob # Blob mobs + coefficients: + Blunt: 0.5 + Slash: 0.5 + Piercing: 0.5 + Heat: 1.5 + +- type: damageModifierSet + id: BlobReflective # If it reflects lasers, then it must be fragile? + coefficients: + Blunt: 0.9 + Slash: 0.9 + Piercing: 0.9 + Heat: 1.5 + - type: damageModifierSet id: Moth # Slightly worse at everything but cold coefficients: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/blob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/blob.yml new file mode 100644 index 00000000000..53c9de198e8 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/blob.yml @@ -0,0 +1,626 @@ +- type: entity + id: SpawnPointGhostBlob + name: Blob + suffix: DONTMAP, spawner + parent: MarkerBase + components: + - type: BlobSpawner + - type: GhostRole + name: ghost-role-information-blob-name + description: ghost-role-information-blob-description + rules: You are an antagonist, destroy the station! + - type: GhostTakeoverAvailable + - type: Sprite + sprite: Markers/jobs.rsi + layers: + - state: green + - sprite: Mobs/Aliens/blob.rsi + state: blob_nuke_overlay + + +- type: entity + id: MobBlobPod + name: Blob pod + parent: SimpleSpaceMobBase + components: + - type: TriggerOnMobstateChange + mobState: + - Dead + - type: SmokeOnTrigger + time: 5 + smokeColor: Green + spreadAmount: 20 +# smokeReagents: +# - ReagentId: TearAcid +# Quantity: 3 + sound: /Audio/Effects/smoke.ogg + - type: Damageable + damageContainer: Blob + damageModifierSet: BlobMob + - type: Clothing + quickEquip: false + sprite: Mobs/Aliens/blob.rsi + equippedPrefix: blobPod + slots: + - HEAD + - type: Tag + tags: + - HidesHair + - BlobMob + - type: IngestionBlocker + - type: IdentityBlocker + - type: BlobPod + - type: DoAfter + - type: Physics + bodyType: KinematicController + - type: InteractionOutline + - type: Actions + - type: Alerts + - type: InputMover + - type: Examiner + - type: MobMover + - type: HTN + rootTask: + task: BlobPodCompound + - type: BlobMob + - type: ReplacementAccent + accent: genericAggressive + - type: CombatMode + - type: NpcFactionMember + factions: + - Blob + - type: MeleeWeapon + hidden: true + soundHit: + path: /Audio/Effects/bite.ogg + angle: 0 + animation: WeaponArcSmash + damage: + types: + Piercing: 5 + - type: DamageStateVisuals + states: + Alive: + Base: blobpod + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.25 + density: 10 + mask: + - FlyingBlobMobMask + layer: + - FlyingBlobMobLayer + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 50 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: MobThresholds + thresholds: + 0: Alive + 50: Dead + - type: MovementSpeedModifier + baseWalkSpeed: 3 + baseSprintSpeed: 4 + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/blob.rsi + state: blobpod + + +- type: entity + id: MobBlobBlobbernaut + name: Blobbernaut + parent: SimpleSpaceMobBase + components: + - type: Damageable + damageContainer: Blob + damageModifierSet: BlobMob + - type: Tag + tags: + - BlobMob + - type: GhostRole + name: ghost-role-information-blobbernaut-name + description: ghost-role-information-blobbernaut-description + rules: You are an antagonist, defend your blob core! + - type: GhostTakeoverAvailable + - type: Blobbernaut + - type: Physics + - type: InputMover + - type: MobMover + - type: BlobMob + - type: ReplacementAccent + accent: genericAggressive + - type: CombatMode + - type: HTN + rootTask: + task: SimpleHostileCompound + blackboard: + NavSmash: !type:Bool + true + - type: NpcFactionMember + factions: + - Blob + - type: MobState + allowedStates: + - Alive + - Dead + - type: MeleeWeapon + hidden: true + soundHit: + path: /Audio/Effects/bite.ogg + angle: 0 + animation: WeaponArcSmash + damage: + types: + Blunt: 15 + Structural: 50 + - type: DamageStateVisuals + states: + Alive: + Base: blobbernaut + Dead: + Base: blobbernaut_dead + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 200 + mask: + - BlobMobMask + layer: + - BlobMobLayer + - type: MobThresholds + thresholds: + 0: Alive + 200: Dead + - type: MovementSpeedModifier + baseWalkSpeed: 2.5 + baseSprintSpeed: 3 + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: Mobs + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blobbernaut + - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + state: nautdamage + shader: unshaded + +- type: entity + id: BaseBlob + abstract: true + name: base blob + placement: + mode: SnapgridCenter + snap: + - Wall + components: + - type: Flashable + - type: Reactive + groups: + Flammable: [Touch] + Extinguish: [Touch] + - type: ExplosionResistance + damageCoefficient: 0.3 + - type: Damageable + damageContainer: Blob + damageModifierSet: BaseBlob + - type: Appearance + - type: Physics + - type: Clickable + - type: Transform + anchored: true + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + layer: + - BlobTileLayer + density: 1000 + +- type: entity + parent: BaseBlob + id: NormalBlobTile + name: Normal Blob + components: + - type: Temperature + heatDamage: + types: + Heat: 5 + coldDamage: {} + ColdDamageThreshold: 0 + - type: Flammable + fireSpread: true + cold: + types: {} + damage: + types: + Heat: 1 + - type: BlobTile + tileType: Normal + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 25 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: BlobTiles + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blob +# Vanila vs new type, idk +# - type: SlowContacts +# walkSpeedModifier: 0.3 +# sprintSpeedModifier: 0.3 +# ignoreWhitelist: +# tags: +# - BlobMob +# - type: DamageContacts +# damage: +# types: +# Poison: 1.5 +# Piercing: 1.5 +# ignoreWhitelist: +# tags: +# - BlobMob +# - type: Fixtures +# fixtures: +# fix1: +# density: 7 +# shape: +# !type:PhysShapeAabb +# bounds: "-0.5,-0.5,0.5,0.5" +# layer: +# - BlobTileLayer +# mask: +# - BlobTileMask + + + +- type: entity + parent: BaseBlob + id: CoreBlobTile + name: Core Blob + save: false + components: + - type: Temperature + heatDamage: + types: + Heat: 5 + coldDamage: {} + ColdDamageThreshold: 0 + - type: Flammable + fireSpread: true + cold: + types: {} + damage: + types: + Heat: 1 + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 400 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: BlobTile + tileType: Core + - type: BlobCore + - type: BlobNode + - type: BlobResource + pointsPerPulsed: 5 + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: BlobTiles + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blob_core + - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + state: blob_core_glow +# shader: unshaded + - state: blob_core_overlay + + +- type: entity + parent: CoreBlobTile + id: CoreBlobTileGhostRole + suffix: Ghost Role + components: + - type: GhostRole + name: ghost-role-information-blob-name + description: ghost-role-information-blob-description + rules: You are an antagonist, destroy the station! + reregister: false + - type: GhostTakeoverAvailable + + +- type: entity + parent: BaseBlob + id: FactoryBlobTile + name: Factory Blob + components: + - type: Temperature + heatDamage: + types: + Heat: 5 + coldDamage: {} + ColdDamageThreshold: 0 + - type: Flammable + fireSpread: true + cold: + types: {} + damage: + types: + Heat: 1 + - type: BlobTile + tileType: Factory + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 200 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: BlobFactory + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: BlobTiles + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blob_factory + - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + state: blob_factory_glow +# shader: unshaded + + +- type: entity + parent: BaseBlob + id: ResourceBlobTile + name: Resource Blob + components: + - type: Temperature + heatDamage: + types: + Heat: 5 + coldDamage: {} + ColdDamageThreshold: 0 + - type: Flammable + fireSpread: true + cold: + types: {} + damage: + types: + Heat: 1 + - type: BlobTile + tileType: Resource + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 60 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: BlobResource + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: BlobTiles + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blob_resource + - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + state: blob_resource_glow +# shader: unshaded + + +- type: entity + parent: BaseBlob + id: NodeBlobTile + name: Node Blob + components: + - type: Temperature + heatDamage: + types: + Heat: 5 + coldDamage: {} + ColdDamageThreshold: 0 + - type: Flammable + fireSpread: true + cold: + types: {} + damage: + types: + Heat: 1 + - type: BlobTile + tileType: Node + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 200 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: BlobNode + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: BlobTiles + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blob_node + - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + state: blob_node_glow +# shader: unshaded + - state: blob_node_overlay + + +- type: entity + parent: BaseBlob + id: StrongBlobTile + name: StrongBlobTile + components: + - type: BlobTile + tileType: Strong + - type: Airtight + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 150 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: BlobTiles + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blob_glow + - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + state: blob_glow +# shader: unshaded + + +- type: entity + parent: BaseBlob + id: ReflectiveBlobTile + name: ReflectiveBlobTile + components: + - type: Damageable + damageContainer: Blob + damageModifierSet: BlobReflective + - type: BlobTile + tileType: Reflective + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Reflect + reflectProb: 0.9 + spread: 10 + reflects: + - Energy + - type: Airtight + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + drawdepth: BlobTiles + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: blob_shield +# - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] +# state: blob_shield_damaged +# shader: unshaded + + +#- type: entity +# parent: BaseStructure +# id: BlobBorder +# name: debug wall +# noSpawn: true +# suffix: DONTMAP +# placement: +# mode: SnapgridCenter +# snap: +# - Wall +# components: +# - type: BlobBorder +# - type: Fixtures +# fixtures: +# fix1: +# shape: +# !type:PhysShapeAabb +# bounds: "-0.5,-0.5,0.5,0.5" +# mask: +# - BlobGhostImpassable +# layer: +# - BlobGhostImpassable +# - type: PlacementReplacement +# key: walls +# - type: Visibility +# layer: 5 +# - type: Sprite +# drawdepth: Walls +# sprite: Structures/Walls/debug.rsi +# - type: Icon +# state: full +# sprite: Structures/Walls/debug.rsi +# - type: IconSmooth +# key: walls +# base: debug + +- type: entity + id: MobObserverBlob + name: Blob observer + noSpawn: true + components: + - type: UserInterface + interfaces: + - key: enum.BlobChemSwapUiKey.Key + type: BlobChemSwapBoundUserInterface + - type: Actions + - type: Alerts + - type: BlobObserver + - type: Visibility + layer: 2 + - type: ContentEye + - type: MindContainer + - type: Clickable + - type: InteractionOutline + - type: Physics + bodyType: KinematicController + fixedRotation: true + - type: GhostRole + name: ghost-role-information-blob-name + description: ghost-role-information-blob-description + rules: You are an antagonist, destroy the station! + - type: GhostTakeoverAvailable + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 15 + mask: + - GhostImpassable + - type: InputMover + - type: Appearance + - type: Eye + - type: Input + context: "ghost" + - type: Examiner + skipChecks: true + - type: Sprite + sprite: Mobs/Aliens/blob.rsi + color: "#fc8403" + state: marker + noRot: true + drawdepth: Ghosts + - type: MovementSpeedModifier + baseSprintSpeed: 8 + baseWalkSpeed: 5 + - type: MovementIgnoreGravity + - type: Tag + tags: + - BypassInteractionRangeChecks diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index aea3dd6dfae..3c4ad39304f 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -101,6 +101,24 @@ - type: RandomSpawnRule prototype: SpawnPointGhostDragon +- type: entity + id: BlobSpawn + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 15 + duration: 1 + earliestStart: 40 + minimumPlayers: 20 + - type: BlobSpawnRule + carrierBlobProtos: + - MobMouse + - MobMouse1 + - MobMouse2 + playersPerCarrierBlob: 30 + maxCarrierBlob: 3 + - type: entity parent: BaseGameRule id: NinjaSpawn diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 141866cde7a..eebc2deeb58 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -80,6 +80,13 @@ components: - type: PiratesRule +- type: entity + id: Blob + parent: BaseGameRule + noSpawn: true + components: + - type: BlobRule + - type: entity id: Traitor parent: BaseGameRule diff --git a/Resources/Prototypes/NPCs/blob_pod.yml b/Resources/Prototypes/NPCs/blob_pod.yml new file mode 100644 index 00000000000..bc79c3726b3 --- /dev/null +++ b/Resources/Prototypes/NPCs/blob_pod.yml @@ -0,0 +1,31 @@ +- type: htnCompound + id: BlobPodCompound + branches: + - tasks: + - !type:HTNCompoundTask + task: MeleeCombatCompound + - tasks: + - !type:HTNCompoundTask + task: ZombifyCompound + - tasks: + - !type:HTNCompoundTask + task: IdleCompound + +- type: htnCompound + id: ZombifyCompound + branches: + - tasks: + # TODO: Kill this shit + - !type:HTNPrimitiveTask + operator: !type:PickBlobPodZombifyTargetOperator + pathfindInPlanning: true + targetKey: MovementTarget + zombifyKey: ZombifyTarget + rangeKey: IdleRange + - !type:HTNPrimitiveTask + operator: !type:MoveToOperator + pathfindInPlanning: false + targetKey: MovementTarget + - !type:HTNPrimitiveTask + operator: !type:BlobPodZombifyOperator + zombifyKey: ZombifyTarget diff --git a/Resources/Prototypes/Objectives/blobObjectives.yml b/Resources/Prototypes/Objectives/blobObjectives.yml new file mode 100644 index 00000000000..d3ff180af42 --- /dev/null +++ b/Resources/Prototypes/Objectives/blobObjectives.yml @@ -0,0 +1,7 @@ +- type: objective + id: BlobCaptureObjective + issuer: blob + requirements: + - !type:BlobRequirement {} + conditions: + - !type:BlobCaptureCondition {} diff --git a/Resources/Prototypes/Roles/Antags/blob.yml b/Resources/Prototypes/Roles/Antags/blob.yml new file mode 100644 index 00000000000..28d6683d535 --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/blob.yml @@ -0,0 +1,6 @@ +- type: antag + id: Blob + name: roles-antag-blob-name + antagonist: true + setPreference: true + objective: roles-antag-blob-objective diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml index 3dfb35c7a69..799a76ea372 100644 --- a/Resources/Prototypes/ai_factions.yml +++ b/Resources/Prototypes/ai_factions.yml @@ -6,7 +6,7 @@ - Xeno - PetsNT - Zombie - - Revolutionary + - Blob - type: npcFaction id: NanoTrasen @@ -15,6 +15,7 @@ - Syndicate - Xeno - Zombie + - Blob - Revolutionary - type: npcFaction @@ -32,6 +33,7 @@ - SimpleHostile - Zombie - Xeno + - Blob - type: npcFaction id: SimpleHostile @@ -41,6 +43,7 @@ - Passive - PetsNT - Zombie + - Blob - Revolutionary - type: npcFaction @@ -54,6 +57,7 @@ - Xeno - PetsNT - Zombie + - Blob - type: npcFaction id: Xeno @@ -64,6 +68,7 @@ - PetsNT - Zombie - Revolutionary + - Blob - type: npcFaction id: Zombie @@ -74,6 +79,7 @@ - Syndicate - Passive - PetsNT + - Blob - Revolutionary - type: npcFaction @@ -83,3 +89,20 @@ - Zombie - SimpleHostile - Dragon +<<<<<<< HEAD + +- type: npcFaction + id: Blob + hostile: + - NanoTrasen + - Syndicate + - Passive + - SimpleHostile + - Xeno + - SimpleNeutral + - PetsNT + - Revolutionary + - Zombie + - Dragon +======= +>>>>>>> parent of c17fc8be22 (blob) diff --git a/Resources/Prototypes/explosion.yml b/Resources/Prototypes/explosion.yml index a768dead6da..a9188c60a8b 100644 --- a/Resources/Prototypes/explosion.yml +++ b/Resources/Prototypes/explosion.yml @@ -133,3 +133,14 @@ # STOP # BEFORE YOU ADD MORE EXPLOSION TYPES CONSIDER IF AN EXISTING ONE IS SUITABLE # ADDING NEW ONES IS PROHIBITIVELY EXPENSIVE + +- type: explosion + id: Blob + damagePerIntensity: + groups: + Brute: 10 + tileBreakChance: [0] + tileBreakIntensity: [0] + lightColor: Red + texturePath: /Textures/Effects/fire.rsi + fireStates: 3 diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 84ab2c0e451..938f2521450 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -27,6 +27,9 @@ - type: Tag id: ATVKeys +- type: Tag + id: BlobMob + - type: Tag id: Baguette diff --git a/Resources/Textures/Effects/chemsmoke.rsi/chemsmoke.png b/Resources/Textures/Effects/chemsmoke.rsi/chemsmoke.png index e85d4463347..8ca20f9303a 100644 Binary files a/Resources/Textures/Effects/chemsmoke.rsi/chemsmoke.png and b/Resources/Textures/Effects/chemsmoke.rsi/chemsmoke.png differ diff --git a/Resources/Textures/Interface/Actions/blobBlobbernaut.png b/Resources/Textures/Interface/Actions/blobBlobbernaut.png new file mode 100644 index 00000000000..14702a49bba Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobBlobbernaut.png differ diff --git a/Resources/Textures/Interface/Actions/blobChemSwap.png b/Resources/Textures/Interface/Actions/blobChemSwap.png new file mode 100644 index 00000000000..acd30464a9e Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobChemSwap.png differ diff --git a/Resources/Textures/Interface/Actions/blobFactory.png b/Resources/Textures/Interface/Actions/blobFactory.png new file mode 100644 index 00000000000..83b98d363b2 Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobFactory.png differ diff --git a/Resources/Textures/Interface/Actions/blobHelp.png b/Resources/Textures/Interface/Actions/blobHelp.png new file mode 100644 index 00000000000..e5839fc0f7c Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobHelp.png differ diff --git a/Resources/Textures/Interface/Actions/blobNode.png b/Resources/Textures/Interface/Actions/blobNode.png new file mode 100644 index 00000000000..b12fbd97220 Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobNode.png differ diff --git a/Resources/Textures/Interface/Actions/blobResource.png b/Resources/Textures/Interface/Actions/blobResource.png new file mode 100644 index 00000000000..b1573bcf3ed Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobResource.png differ diff --git a/Resources/Textures/Interface/Actions/blobSplit.png b/Resources/Textures/Interface/Actions/blobSplit.png new file mode 100644 index 00000000000..5472aca22d8 Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobSplit.png differ diff --git a/Resources/Textures/Interface/Actions/blobSwap.png b/Resources/Textures/Interface/Actions/blobSwap.png new file mode 100644 index 00000000000..3b574a57988 Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobSwap.png differ diff --git a/Resources/Textures/Interface/Actions/blobToCore.png b/Resources/Textures/Interface/Actions/blobToCore.png new file mode 100644 index 00000000000..bec064966f1 Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobToCore.png differ diff --git a/Resources/Textures/Interface/Actions/blobToNode.png b/Resources/Textures/Interface/Actions/blobToNode.png new file mode 100644 index 00000000000..1785af5c1cc Binary files /dev/null and b/Resources/Textures/Interface/Actions/blobToNode.png differ diff --git a/Resources/Textures/Interface/Actions/createNormalBlob.png b/Resources/Textures/Interface/Actions/createNormalBlob.png new file mode 100644 index 00000000000..4edbcad49d1 Binary files /dev/null and b/Resources/Textures/Interface/Actions/createNormalBlob.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/meta.json b/Resources/Textures/Interface/Alerts/blob_health.rsi/meta.json new file mode 100644 index 00000000000..c54cd12c957 --- /dev/null +++ b/Resources/Textures/Interface/Alerts/blob_health.rsi/meta.json @@ -0,0 +1,74 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by VigersRay", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "point0" + }, + { + "name": "point1" + }, + { + "name": "point2" + }, + { + "name": "point3" + }, + { + "name": "point4" + }, + { + "name": "point5" + }, + { + "name": "point6" + }, + { + "name": "point7" + }, + { + "name": "point8" + }, + { + "name": "point9" + }, + { + "name": "point10" + }, + { + "name": "point11" + }, + { + "name": "point12" + }, + { + "name": "point13" + }, + { + "name": "point14" + }, + { + "name": "point15" + }, + { + "name": "point16" + }, + { + "name": "point17" + }, + { + "name": "point18" + }, + { + "name": "point19" + }, + { + "name": "point20" + } + ] +} diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point0.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point0.png new file mode 100644 index 00000000000..59d80f5b1c3 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point0.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point1.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point1.png new file mode 100644 index 00000000000..2663d1239a1 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point1.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point10.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point10.png new file mode 100644 index 00000000000..57555881208 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point10.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point11.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point11.png new file mode 100644 index 00000000000..1c513c013ba Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point11.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point12.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point12.png new file mode 100644 index 00000000000..460caabe08b Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point12.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point13.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point13.png new file mode 100644 index 00000000000..8b12c0ec1ef Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point13.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point14.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point14.png new file mode 100644 index 00000000000..62daf63e238 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point14.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point15.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point15.png new file mode 100644 index 00000000000..7d442a07796 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point15.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point16.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point16.png new file mode 100644 index 00000000000..e8c99b8b85b Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point16.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point17.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point17.png new file mode 100644 index 00000000000..af52612c93c Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point17.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point18.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point18.png new file mode 100644 index 00000000000..3bbce65f5e6 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point18.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point19.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point19.png new file mode 100644 index 00000000000..3224b1f432d Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point19.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point2.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point2.png new file mode 100644 index 00000000000..819b19fab3f Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point2.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point20.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point20.png new file mode 100644 index 00000000000..50a89b55ed8 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point20.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point3.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point3.png new file mode 100644 index 00000000000..eaf2e15b701 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point3.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point4.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point4.png new file mode 100644 index 00000000000..8fb27afc903 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point4.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point5.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point5.png new file mode 100644 index 00000000000..430c86529ea Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point5.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point6.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point6.png new file mode 100644 index 00000000000..66d2c39e45e Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point6.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point7.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point7.png new file mode 100644 index 00000000000..8415155ea79 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point7.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point8.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point8.png new file mode 100644 index 00000000000..aff4dee6416 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point8.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_health.rsi/point9.png b/Resources/Textures/Interface/Alerts/blob_health.rsi/point9.png new file mode 100644 index 00000000000..8ab16a712da Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_health.rsi/point9.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/meta.json b/Resources/Textures/Interface/Alerts/blob_resource.rsi/meta.json new file mode 100644 index 00000000000..5b674ca87ac --- /dev/null +++ b/Resources/Textures/Interface/Alerts/blob_resource.rsi/meta.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by VigersRay", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "point0" + }, + { + "name": "point1" + }, + { + "name": "point2" + }, + { + "name": "point3" + }, + { + "name": "point4" + }, + { + "name": "point5" + }, + { + "name": "point6" + }, + { + "name": "point7" + }, + { + "name": "point8" + }, + { + "name": "point9" + }, + { + "name": "point10" + }, + { + "name": "point11" + }, + { + "name": "point12" + }, + { + "name": "point13" + }, + { + "name": "point14" + }, + { + "name": "point15" + }, + { + "name": "point16" + } + ] +} diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point0.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point0.png new file mode 100644 index 00000000000..98b3cbb5c85 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point0.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point1.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point1.png new file mode 100644 index 00000000000..99aa9d2826b Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point1.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point10.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point10.png new file mode 100644 index 00000000000..b5d6a576ccd Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point10.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point11.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point11.png new file mode 100644 index 00000000000..8dad72c78e4 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point11.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point12.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point12.png new file mode 100644 index 00000000000..2a4fba10230 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point12.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point13.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point13.png new file mode 100644 index 00000000000..c2a957bdfe7 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point13.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point14.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point14.png new file mode 100644 index 00000000000..8ba43522754 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point14.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point15.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point15.png new file mode 100644 index 00000000000..00a190cc007 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point15.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point16.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point16.png new file mode 100644 index 00000000000..d358b3c530e Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point16.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point2.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point2.png new file mode 100644 index 00000000000..e91994aa0ed Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point2.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point3.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point3.png new file mode 100644 index 00000000000..e71e8950631 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point3.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point4.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point4.png new file mode 100644 index 00000000000..867bbcd6994 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point4.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point5.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point5.png new file mode 100644 index 00000000000..01b466e482e Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point5.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point6.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point6.png new file mode 100644 index 00000000000..f15d1f973ff Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point6.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point7.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point7.png new file mode 100644 index 00000000000..94bcd78533f Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point7.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point8.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point8.png new file mode 100644 index 00000000000..4457332b297 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point8.png differ diff --git a/Resources/Textures/Interface/Alerts/blob_resource.rsi/point9.png b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point9.png new file mode 100644 index 00000000000..adf9404d4d3 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/blob_resource.rsi/point9.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blank_blob.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blank_blob.png new file mode 100644 index 00000000000..4c59d3a28cc Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blank_blob.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob.png new file mode 100644 index 00000000000..f9160bdcc24 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blobPod-equipped-HELMET.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blobPod-equipped-HELMET.png new file mode 100644 index 00000000000..f2735632831 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blobPod-equipped-HELMET.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core.png new file mode 100644 index 00000000000..6dbe9a6f15f Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core_glow.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core_glow.png new file mode 100644 index 00000000000..8483809a97c Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core_glow.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core_overlay.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core_overlay.png new file mode 100644 index 00000000000..4394b3ca2a0 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_core_overlay.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_damaged.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_damaged.png new file mode 100644 index 00000000000..d9be2b67296 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_damaged.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_factory.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_factory.png new file mode 100644 index 00000000000..b6f5f4264be Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_factory.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_factory_glow.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_factory_glow.png new file mode 100644 index 00000000000..9da2ec67224 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_factory_glow.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_glow.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_glow.png new file mode 100644 index 00000000000..cefb89197dc Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_glow.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_glow_damaged.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_glow_damaged.png new file mode 100644 index 00000000000..91fe1aeb725 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_glow_damaged.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_head.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_head.png new file mode 100644 index 00000000000..550047e0a37 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_head.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_idle_glow.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_idle_glow.png new file mode 100644 index 00000000000..340a1640939 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_idle_glow.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node.png new file mode 100644 index 00000000000..2c97d9fafc4 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node_glow.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node_glow.png new file mode 100644 index 00000000000..abcea89c1ae Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node_glow.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node_overlay.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node_overlay.png new file mode 100644 index 00000000000..1f901a80d68 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_node_overlay.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_nuke_overlay.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_nuke_overlay.png new file mode 100644 index 00000000000..10f95787789 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_nuke_overlay.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_resource.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_resource.png new file mode 100644 index 00000000000..b6f5f4264be Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_resource.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_resource_glow.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_resource_glow.png new file mode 100644 index 00000000000..3cfed09c279 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_resource_glow.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_shield.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_shield.png new file mode 100644 index 00000000000..a41976b4488 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_shield.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_shield_damaged.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_shield_damaged.png new file mode 100644 index 00000000000..f69d8d976df Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_shield_damaged.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blob_spore_temp.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_spore_temp.png new file mode 100644 index 00000000000..8c9c59fca52 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blob_spore_temp.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut.png new file mode 100644 index 00000000000..971fdc1d540 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_dead.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_dead.png new file mode 100644 index 00000000000..0b6f79df64c Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_dead.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_death.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_death.png new file mode 100644 index 00000000000..194f4b8b1ed Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_death.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_produce.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_produce.png new file mode 100644 index 00000000000..cc87b43e672 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blobbernaut_produce.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/blobpod.png b/Resources/Textures/Mobs/Aliens/blob.rsi/blobpod.png new file mode 100644 index 00000000000..dc49821e5e2 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/blobpod.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/block.png b/Resources/Textures/Mobs/Aliens/blob.rsi/block.png new file mode 100644 index 00000000000..d6ef6a58be3 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/block.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/block2.png b/Resources/Textures/Mobs/Aliens/blob.rsi/block2.png new file mode 100644 index 00000000000..6351274933c Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/block2.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/corehealth.png b/Resources/Textures/Mobs/Aliens/blob.rsi/corehealth.png new file mode 100644 index 00000000000..65cd03d7544 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/corehealth.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/marker.png b/Resources/Textures/Mobs/Aliens/blob.rsi/marker.png new file mode 100644 index 00000000000..6d4e9cf865f Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/marker.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/meta.json b/Resources/Textures/Mobs/Aliens/blob.rsi/meta.json new file mode 100644 index 00000000000..10a06621f70 --- /dev/null +++ b/Resources/Textures/Mobs/Aliens/blob.rsi/meta.json @@ -0,0 +1,927 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "blob_spore_temp", + "directions": 4 + }, + { + "name": "blobPod-equipped-HELMET", + "directions": 4 + }, + { + "name": "blobpod", + "delays": [ + [ + 1, + 0.3, + 0.5, + 0.3 + ] + ] + }, + { + "name": "blob_head" + }, + { + "name": "blob", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_damaged", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_shield", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_shield_damaged", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_core", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "blob_node", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_factory", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_resource", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_glow", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_glow_damaged", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_idle_glow", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_core_glow", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "blob_node_glow", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_factory_glow", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blob_resource_glow", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blobbernaut_produce", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.15, + 0.15, + 0.15, + 0.15 + ] + ] + }, + { + "name": "blobbernaut", + "directions": 4 + }, + { + "name": "blobbernaut_death", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.15, + 0.15, + 0.15, + 0.1 + ] + ] + }, + { + "name": "blobbernaut_dead" + }, + { + "name": "nautdamage", + "directions": 4, + "delays": [ + [ + 0.075, + 0.075, + 0.075, + 0.075, + 0.2, + 0.075, + 0.075, + 0.075, + 0.075 + ], + [ + 0.075, + 0.075, + 0.075, + 0.075, + 0.2, + 0.075, + 0.075, + 0.075, + 0.075 + ], + [ + 0.075, + 0.075, + 0.075, + 0.075, + 0.2, + 0.075, + 0.075, + 0.075, + 0.075 + ], + [ + 0.075, + 0.075, + 0.075, + 0.075, + 0.2, + 0.075, + 0.075, + 0.075, + 0.075 + ] + ] + }, + { + "name": "blob_core_overlay", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "blob_nuke_overlay" + }, + { + "name": "blob_node_overlay", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "blank_blob" + }, + { + "name": "ui_help" + }, + { + "name": "ui_tocore" + }, + { + "name": "ui_tonode" + }, + { + "name": "ui_node" + }, + { + "name": "ui_factory" + }, + { + "name": "ui_resource" + }, + { + "name": "ui_chemswap" + }, + { + "name": "ui_swap" + }, + { + "name": "block" + }, + { + "name": "block2" + }, + { + "name": "ui_blobbernaut" + }, + { + "name": "nauthealth" + }, + { + "name": "corehealth" + }, + { + "name": "marker", + "delays": [ + [ + 1, + 1 + ] + ] + }, + { + "name": "ui_split" + } + ] +} diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/nautdamage.png b/Resources/Textures/Mobs/Aliens/blob.rsi/nautdamage.png new file mode 100644 index 00000000000..050470ef8f0 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/nautdamage.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/nauthealth.png b/Resources/Textures/Mobs/Aliens/blob.rsi/nauthealth.png new file mode 100644 index 00000000000..01242c74bd2 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/nauthealth.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_blobbernaut.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_blobbernaut.png new file mode 100644 index 00000000000..14702a49bba Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_blobbernaut.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_chemswap.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_chemswap.png new file mode 100644 index 00000000000..acd30464a9e Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_chemswap.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_factory.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_factory.png new file mode 100644 index 00000000000..83b98d363b2 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_factory.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_help.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_help.png new file mode 100644 index 00000000000..e5839fc0f7c Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_help.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_node.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_node.png new file mode 100644 index 00000000000..b12fbd97220 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_node.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_resource.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_resource.png new file mode 100644 index 00000000000..b1573bcf3ed Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_resource.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_split.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_split.png new file mode 100644 index 00000000000..5472aca22d8 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_split.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_swap.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_swap.png new file mode 100644 index 00000000000..3b574a57988 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_swap.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_tocore.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_tocore.png new file mode 100644 index 00000000000..bec064966f1 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_tocore.png differ diff --git a/Resources/Textures/Mobs/Aliens/blob.rsi/ui_tonode.png b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_tonode.png new file mode 100644 index 00000000000..1785af5c1cc Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/blob.rsi/ui_tonode.png differ