Cult update (#220)

* - tweak: Cult door not bump openable.

* - tweak: Better summoning and Narsie

* - tweak: Construct update.

* - tweak: Eldrich blade fits in suit storage.

* - tweak: More spell limit.

* - fix: Fix pylon desc.

* - tweak: Teleport works on cuffed targets.

* - tweak: More popups if target is holy.

* - fix: No rune drawing using fingers.

* - tweak: Better pylon placement & less pylon healing range.

* - tweak: More blood rites charge.

* - fix: Fix max spell amount.

* - tweak: Less cult door and wall health.

* - fix: Constructs are dead IC.

* - add: Revive rune now notifies player.

* - add: Narsie summon rune eui.

* - fix: Fix narsie summon sound not playing for reapers.

* - tweak: Whatever.

* - add: Conceal presence spell.

* - tweak: Tweakz.

* - add: Blood spear.

* - add: Blood boil barrage.

* - tweak: Artificer flies.

* - tweak: Blood bolt color tweaks.

* - tweak: Runic door is bump openable again.

* - fix: Fix concealed door outline.

* - add: Update concealable name and desc.

* - tweak: Remove the unremoveable.

* - tweak: Gift ignore.

* - add: Organs regenerate on rejuvenate.

* - tweak: Brainless cultist is fine.

* - add: Added more fun.

* - add: Add rune descriptions.

* - fix: Fixes.

* - tweak: Blood rites now uses verb.

* - tweak: Bring it back.
This commit is contained in:
Aviu00
2024-03-22 17:23:33 +09:00
committed by GitHub
parent 1337e4d26e
commit 74ef19d6a6
109 changed files with 1831 additions and 182 deletions

View File

@@ -123,10 +123,10 @@ public abstract class SharedTentacleGun : EntitySystem
foreach (var activeItem in _handsSystem.EnumerateHeld(args.Embedded))
{
if(!TryComp<PhysicsComponent>(activeItem, out var physicsComponent))
return;
continue;
var coords = Transform(args.Embedded).Coordinates;
_handsSystem.TryDrop(args.Embedded, coords);
_handsSystem.TryDrop(args.Embedded, activeItem, coords);
var force = physicsComponent.Mass * 2.5f / 2;

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Content.Shared._White.Cult.Components;
using Content.Shared.Examine;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
@@ -7,12 +8,15 @@ using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Localizations;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Content.Shared.Hands.EntitySystems;
public abstract partial class SharedHandsSystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!; // WD
private void InitializeInteractions()
{
SubscribeAllEvent<RequestSetHandEvent>(HandleSetHand);
@@ -95,7 +99,17 @@ public abstract partial class SharedHandsSystem : EntitySystem
private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid netEntity)
{
if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHand != null)
// WD EDIT START
{
if (HasComp<BoltBarrageComponent>(hands.ActiveHandEntity))
{
if (_net.IsServer)
QueueDel(hands.ActiveHandEntity.Value);
return false;
}
TryDrop(session.AttachedEntity.Value, hands.ActiveHand, coords, handsComp: hands);
}
// WD EDIT END
// always send to server.
return false;

View File

@@ -1,5 +1,6 @@
using Content.Shared.Damage.Prototypes;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Shared.Weapons.Melee.Components;
@@ -8,6 +9,7 @@ namespace Content.Shared.Weapons.Melee.Components;
/// Plays the specified sound upon receiving damage of the specified type.
/// </summary>
[RegisterComponent]
[NetworkedComponent, AutoGenerateComponentState] // WD
public sealed partial class MeleeSoundComponent : Component
{
/// <summary>
@@ -16,6 +18,7 @@ public sealed partial class MeleeSoundComponent : Component
/// </summary>
[DataField("soundGroups",
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<SoundSpecifier, DamageGroupPrototype>))]
[AutoNetworkedField] // WD
public Dictionary<string, SoundSpecifier>? SoundGroups;
/// <summary>
@@ -24,10 +27,12 @@ public sealed partial class MeleeSoundComponent : Component
/// </summary>
[DataField("soundTypes",
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<SoundSpecifier, DamageTypePrototype>))]
[AutoNetworkedField] // WD
public Dictionary<string, SoundSpecifier>? SoundTypes;
/// <summary>
/// Sound that plays if no damage is done.
/// </summary>
[AutoNetworkedField] // WD
[DataField("noDamageSound")] public SoundSpecifier? NoDamageSound;
}

View File

@@ -0,0 +1,48 @@
using System.Linq;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Rejuvenate;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Shared._White.Body;
public sealed class BodyRejuvenateSystem : EntitySystem
{
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BodyComponent, RejuvenateEvent>(OnRejuvenate);
}
private void OnRejuvenate(Entity<BodyComponent> ent, ref RejuvenateEvent args)
{
if (ent.Comp.Prototype == null)
return;
var prototype = _prototypeManager.Index(ent.Comp.Prototype.Value);
var protoSlots = prototype.Slots.Values.ToList();
foreach (var (id, component) in _body.GetBodyChildren(ent.Owner, ent.Comp))
{
foreach (var organSlot in component.Organs.Values)
{
if (!_container.TryGetContainer(id, SharedBodySystem.GetOrganContainerId(organSlot.Id),
out var container))
continue;
if (container.Count > 0)
continue;
var organ = protoSlots.Where(x => x.Organs.ContainsKey(organSlot.Id))
.Select(x => x.Organs[organSlot.Id]).FirstOrDefault();
TrySpawnInContainer(organ, id, SharedBodySystem.GetOrganContainerId(organSlot.Id), out _);
}
}
}
}

View File

@@ -30,10 +30,23 @@ public sealed partial class CultSummonCombatEquipmentTargetActionEvent : EntityT
{
}
public sealed partial class CultConcealPresenceWorldActionEvent : WorldTargetActionEvent
[Virtual]
public partial class CultConcealPresenceInstantActionEvent : InstantActionEvent
{
}
public sealed partial class CultConcealInstantActionEvent : CultConcealPresenceInstantActionEvent
{
}
public sealed partial class CultRevealInstantActionEvent : CultConcealPresenceInstantActionEvent
{
}
public sealed partial class CultBloodRitesInstantActionEvent : InstantActionEvent
{
}
public sealed partial class CultBloodSpearRecallInstantActionEvent : InstantActionEvent
{
}

View File

@@ -0,0 +1,17 @@
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Shared._White.Cult.Components;
[RegisterComponent]
public sealed partial class BloodSpearComponent : Component
{
[ViewVariables]
public Entity<CultistComponent>? User;
[DataField]
public EntProtoId Action;
[DataField]
public SoundSpecifier ShatterSound = new SoundCollectionSpecifier("GlassBreak");
}

View File

@@ -0,0 +1,7 @@
namespace Content.Shared._White.Cult.Components;
[RegisterComponent]
public sealed partial class BoltBarrageComponent : Component
{
public bool Unremoveable = true;
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.Actions;
using Robust.Shared.Utility;
namespace Content.Shared._White.Cult.Components;
[RegisterComponent]
public sealed partial class ConcealPresenceSpellComponent : Component
{
[ViewVariables]
public bool Revealing;
[DataField(required: true), NonSerialized]
public InstantActionEvent? ConcealEvent;
[DataField(required: true), NonSerialized]
public InstantActionEvent? RevealEvent;
[DataField]
public SpriteSpecifier? ConcealIcon;
[DataField]
public SpriteSpecifier? RevealIcon;
}

View File

@@ -0,0 +1,48 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared._White.Cult.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ConcealableComponent : Component
{
[ViewVariables, AutoNetworkedField]
public bool Concealed;
[DataField]
public bool ExaminableWhileConcealed;
[DataField]
public bool IconSmooth;
[DataField]
public bool InteractionOutline;
[DataField]
public ResPath? ConcealedSprite;
[DataField]
public ResPath? RevealedSprite;
[DataField]
public bool ChangeMeta;
[DataField]
public string ConcealedName = string.Empty;
[DataField]
public string ConcealedDesc = string.Empty;
[DataField]
public string RevealedName = string.Empty;
[DataField]
public string RevealedDesc = string.Empty;
}
[Serializable, NetSerializable]
public enum ConcealableAppearance
{
Concealed
}

View File

@@ -9,8 +9,8 @@ public sealed partial class CultEmpowerComponent : Component
[DataField("isRune")]
public bool IsRune;
public int MaxAllowedCultistActions = 4;
public int MinRequiredCultistActions = 1;
public int MaxAllowedCultistActions = 5;
public int MinRequiredCultistActions = 2;
}
[Serializable, NetSerializable]

View File

@@ -1,6 +1,9 @@
using System.Threading;
using Content.Shared.FixedPoint;
using Content.Shared.Mind;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared._White.Cult.Components;
@@ -8,7 +11,7 @@ namespace Content.Shared._White.Cult.Components;
/// This is used for tagging a mob as a cultist.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CultistComponent : Component
public sealed partial class CultistComponent : ShowCultHudComponent
{
[DataField("greetSound", customTypeSerializer: typeof(SoundSpecifierTypeSerializer))]
public SoundSpecifier? CultistGreetSound = new SoundPathSpecifier("/Audio/CultSounds/fart.ogg");
@@ -21,12 +24,17 @@ public sealed partial class CultistComponent : Component
[AutoNetworkedField]
public List<NetEntity?> SelectedEmpowers = new();
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 RitesBloodAmount = FixedPoint2.Zero;
public static string SummonCultDaggerAction = "InstantActionSummonCultDagger";
public static string BloodRitesAction = "InstantActionBloodRites";
public static string EmpPulseAction = "InstantActionEmpPulse";
public static string ConcealPresenceAction = "InstantActionConcealPresence";
public static string CultTwistedConstructionAction = "ActionCultTwistedConstruction";
public static string CultTeleportAction = "ActionCultTeleport";
@@ -40,6 +48,22 @@ public sealed partial class CultistComponent : Component
public static List<string> CultistActions = new()
{
SummonCultDaggerAction, BloodRitesAction, CultTwistedConstructionAction, CultTeleportAction,
CultSummonCombatEquipmentAction, CultStunAction, EmpPulseAction, CultShadowShacklesAction
CultSummonCombatEquipmentAction, CultStunAction, EmpPulseAction, ConcealPresenceAction, CultShadowShacklesAction
};
[DataField("bloodRites", customTypeSerializer: typeof(PrototypeIdListSerializer<CultistFactoryProductionPrototype>))]
public List<string> BloodRites = new ()
{
"FactoryCultBloodSpear",
"FactoryCultBloodBarrage"
};
[ViewVariables, NonSerialized]
public Entity<BloodSpearComponent>? BloodSpear;
[ViewVariables, NonSerialized]
public EntityUid? BloodSpearActionEntity;
[ViewVariables, NonSerialized]
public Entity<MindComponent>? OriginalMind;
}

View File

@@ -0,0 +1,8 @@
using Robust.Shared.GameStates;
namespace Content.Shared._White.Cult.Components;
[Virtual, RegisterComponent, NetworkedComponent]
public partial class ShowCultHudComponent : Component
{
}

View File

@@ -17,6 +17,9 @@ public sealed class CultistFactoryProductionPrototype : IPrototype
[DataField("name", required: true)]
public string Name = default!;
[DataField]
public int BloodCost;
}
[Serializable, NetSerializable]

View File

@@ -0,0 +1,80 @@
using Content.Shared._White.Cult.Components;
using Content.Shared.Actions;
using Content.Shared.Hands;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
namespace Content.Shared._White.Cult.Systems;
public sealed class BloodSpearSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly INetManager _net = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BloodSpearComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<BloodSpearComponent, GotEquippedHandEvent>(OnEquip);
SubscribeLocalEvent<BloodSpearComponent, ThrowDoHitEvent>(OnThrowDoHit);
}
private void OnThrowDoHit(Entity<BloodSpearComponent> ent, ref ThrowDoHitEvent args)
{
if (!TryComp(args.Target, out StatusEffectsComponent? status))
return;
if(!_stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(6), true, status))
return;
if (_net.IsClient)
return;
_audio.PlayPvs(ent.Comp.ShatterSound, Transform(ent).Coordinates);
QueueDel(ent);
}
private void OnEquip(Entity<BloodSpearComponent> ent, ref GotEquippedHandEvent args)
{
if (!TryComp(args.User, out CultistComponent? cultist))
return;
Entity<CultistComponent> user = (args.User, cultist);
if (cultist.BloodSpear == ent && ent.Comp.User == user)
return;
if (ent.Comp.User != null)
DetachSpearFromUser(ent.Comp.User.Value);
DetachSpearFromUser(user);
AttachSpearToUser(user, ent);
}
public void DetachSpearFromUser(Entity<CultistComponent> user)
{
_actionsSystem.RemoveAction(user, user.Comp.BloodSpearActionEntity);
user.Comp.BloodSpearActionEntity = null;
if (user.Comp.BloodSpear != null)
user.Comp.BloodSpear.Value.Comp.User = null;
user.Comp.BloodSpear = null;
}
public void AttachSpearToUser(Entity<CultistComponent> user, Entity<BloodSpearComponent> spear)
{
_actionsSystem.AddAction(user, ref user.Comp.BloodSpearActionEntity, spear.Comp.Action);
user.Comp.BloodSpear = spear;
spear.Comp.User = user;
}
private void OnRemove(Entity<BloodSpearComponent> ent, ref ComponentRemove args)
{
if (ent.Comp.User != null)
DetachSpearFromUser(ent.Comp.User.Value);
}
}

View File

@@ -0,0 +1,91 @@
using System.Linq;
using Content.Shared._White.Cult.Components;
using Content.Shared.Ghost;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Timing;
namespace Content.Shared._White.Cult.Systems;
public sealed class BoltBarrageSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BoltBarrageComponent, AttemptShootEvent>(OnShootAttempt);
SubscribeLocalEvent<BoltBarrageComponent, GunShotEvent>(OnGunShot);
SubscribeLocalEvent<BoltBarrageComponent, DroppedEvent>(OnDrop);
SubscribeLocalEvent<BoltBarrageComponent, UnequippedHandEvent>(OnUnequipHand);
SubscribeLocalEvent<BoltBarrageComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
SubscribeLocalEvent<BoltBarrageComponent, OnEmptyGunShotEvent>(OnEmptyShot);
}
private void OnUnequipHand(Entity<BoltBarrageComponent> ent, ref UnequippedHandEvent args)
{
if (_net.IsServer && ent.Comp.Unremoveable)
QueueDel(ent);
}
private void OnRemoveAttempt(Entity<BoltBarrageComponent> ent, ref ContainerGettingRemovedAttemptEvent args)
{
if (!_timing.ApplyingState && ent.Comp.Unremoveable)
args.Cancel();
}
private void OnEmptyShot(Entity<BoltBarrageComponent> ent, ref OnEmptyGunShotEvent args)
{
if (_net.IsServer)
QueueDel(ent);
}
private void OnDrop(Entity<BoltBarrageComponent> ent, ref DroppedEvent args)
{
if (_net.IsServer)
QueueDel(ent);
}
private void OnGunShot(Entity<BoltBarrageComponent> ent, ref GunShotEvent args)
{
if (!TryComp(args.User, out HandsComponent? hands))
return;
foreach (var hand in _hands.EnumerateHands(args.User, hands))
{
if (!hand.IsEmpty)
continue;
ent.Comp.Unremoveable = false;
_hands.SetActiveHand(args.User, hand, hands);
_hands.TryPickup(args.User, ent, hand, false, false, hands);
ent.Comp.Unremoveable = true;
return;
}
}
private void OnShootAttempt(Entity<BoltBarrageComponent> ent, ref AttemptShootEvent args)
{
if (!HasComp<CultistComponent>(args.User) && !HasComp<GhostComponent>(args.User))
{
args.Cancelled = true;
args.Message = Loc.GetString("bolt-barrage-component-not-cultist");
return;
}
if (_hands.EnumerateHands(args.User).Any(hand => hand.IsEmpty))
return;
args.Cancelled = true;
args.Message = Loc.GetString("bolt-barrage-component-no-empty-hand");
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared._White.Cult.Components;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
namespace Content.Shared._White.Cult.Systems;
public sealed class ConcealableSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ConcealableComponent, ExamineAttemptEvent>(OnExamine);
SubscribeLocalEvent<ConcealableComponent, InteractionAttemptEvent>(OnInteract);
}
private void OnInteract(Entity<ConcealableComponent> ent, ref InteractionAttemptEvent args)
{
if (ent.Comp is {Concealed: true, ExaminableWhileConcealed: false})
args.Cancel();
}
private void OnExamine(Entity<ConcealableComponent> ent, ref ExamineAttemptEvent args)
{
if (ent.Comp is {Concealed: true, ExaminableWhileConcealed: false})
args.Cancel();
}
}
public sealed class ConcealEvent : EntityEventArgs
{
public bool Conceal;
public ConcealEvent(bool conceal)
{
Conceal = conceal;
}
}

View File

@@ -49,6 +49,15 @@ public sealed class CultItemSystem : EntitySystem
_popupSystem.PopupClient(Loc.GetString("cult-item-component-pickup-fail", ("name", Name(uid))), uid, args.User);
}
public bool CanThrow(EntityUid player, EntityUid throwEnt)
{
if (!HasComp<CultItemComponent>(throwEnt) || CanUse(player))
return true;
_popupSystem.PopupEntity(Loc.GetString("cult-item-component-throw-fail"), player, player);
return false;
}
private bool CanUse(EntityUid? uid)
{
return HasComp<CultistComponent>(uid) || HasComp<GhostComponent>(uid);

View File

@@ -0,0 +1,15 @@
using Content.Shared.Eui;
using Robust.Shared.Serialization;
namespace Content.Shared._White.Cult.UI;
[Serializable, NetSerializable]
public sealed class ApocalypseRuneDrawMessage : EuiMessageBase
{
public readonly bool Accepted;
public ApocalypseRuneDrawMessage(bool accepted)
{
Accepted = accepted;
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Serialization;
namespace Content.Shared._White.Cult.UI;
[Serializable, NetSerializable]
public enum BloodRitesUi
{
Key
}

View File

@@ -12,10 +12,10 @@ public enum ListViewSelectorUiKey
[Serializable, NetSerializable]
public class ListViewBUIState : BoundUserInterfaceState
{
public List<string> Items { get; set; }
public List<EntProtoId> Items { get; set; }
public bool IsUsingPrototypes { get; set; }
public ListViewBUIState(List<string> items, bool isUsingPrototypes)
public ListViewBUIState(List<EntProtoId> items, bool isUsingPrototypes)
{
Items = items;
IsUsingPrototypes = isUsingPrototypes;