diff --git a/Content.Client/ADT/Heretic/HereticRitualRuneBoundUserInterface.cs b/Content.Client/ADT/Heretic/HereticRitualRuneBoundUserInterface.cs new file mode 100644 index 00000000000..38b3b49aeb6 --- /dev/null +++ b/Content.Client/ADT/Heretic/HereticRitualRuneBoundUserInterface.cs @@ -0,0 +1,39 @@ +using Content.Client.ADT.Heretic.UI; +using Content.Shared.ADT.Heretic.Components; +using Content.Shared.Heretic.Prototypes; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; + +namespace Content.Client.ADT.Heretic; + +public sealed class HereticRitualRuneBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IClyde _displayManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + + private HereticRitualRuneRadialMenu? _hereticRitualMenu; + + public HereticRitualRuneBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _hereticRitualMenu = this.CreateWindow(); + _hereticRitualMenu.SetEntity(Owner); + _hereticRitualMenu.SendHereticRitualRuneMessageAction += SendHereticRitualMessage; + + var vpSize = _displayManager.ScreenSize; + _hereticRitualMenu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize); + } + + private void SendHereticRitualMessage(ProtoId protoId) + { + SendMessage(new HereticRitualMessage(protoId)); + } +} diff --git a/Content.Client/ADT/Heretic/UI/HereticRitualRuneRadialMenu.xaml b/Content.Client/ADT/Heretic/UI/HereticRitualRuneRadialMenu.xaml new file mode 100644 index 00000000000..425ba588c12 --- /dev/null +++ b/Content.Client/ADT/Heretic/UI/HereticRitualRuneRadialMenu.xaml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/Content.Client/ADT/Heretic/UI/HereticRitualRuneRadialMenu.xaml.cs b/Content.Client/ADT/Heretic/UI/HereticRitualRuneRadialMenu.xaml.cs new file mode 100644 index 00000000000..4ab95b8f9e4 --- /dev/null +++ b/Content.Client/ADT/Heretic/UI/HereticRitualRuneRadialMenu.xaml.cs @@ -0,0 +1,101 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.Heretic; +using Content.Shared.Heretic.Prototypes; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using System.Numerics; + +namespace Content.Client.ADT.Heretic.UI; + +public sealed partial class HereticRitualRuneRadialMenu : RadialMenu +{ + [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + private readonly SpriteSystem _spriteSystem; + + public event Action>? SendHereticRitualRuneMessageAction; + + public EntityUid Entity { get; set; } + + public HereticRitualRuneRadialMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + _spriteSystem = _entitySystem.GetEntitySystem(); + } + + public void SetEntity(EntityUid uid) + { + Entity = uid; + RefreshUI(); + } + + private void RefreshUI() + { + var main = FindControl("Main"); + if (main == null) + return; + + var player = _playerManager.LocalEntity; + + if (!_entityManager.TryGetComponent(player, out var heretic)) + return; + + foreach (var ritual in heretic.KnownRituals) + { + if (!_prototypeManager.TryIndex(ritual, out var ritualPrototype)) + continue; + + var button = new HereticRitualMenuButton + { + StyleClasses = { "RadialMenuButton" }, + SetSize = new Vector2(64, 64), + ToolTip = Loc.GetString(ritualPrototype.Name), + ProtoId = ritualPrototype.ID + }; + + var texture = new TextureRect + { + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Texture = _spriteSystem.Frame0(ritualPrototype.Icon), + TextureScale = new Vector2(2f, 2f) + }; + + button.AddChild(texture); + main.AddChild(button); + } + + AddHereticRitualMenuButtonOnClickAction(main); + } + + private void AddHereticRitualMenuButtonOnClickAction(RadialContainer mainControl) + { + if (mainControl == null) + return; + + foreach(var child in mainControl.Children) + { + var castChild = child as HereticRitualMenuButton; + + if (castChild == null) + continue; + + castChild.OnButtonUp += _ => + { + SendHereticRitualRuneMessageAction?.Invoke(castChild.ProtoId); + Close(); + }; + } + } + + public sealed class HereticRitualMenuButton : RadialMenuTextureButton + { + public ProtoId ProtoId { get; set; } + } +} diff --git a/Content.Client/ADT/Heretic/UI/LivingHeartMenu.xaml b/Content.Client/ADT/Heretic/UI/LivingHeartMenu.xaml new file mode 100644 index 00000000000..fd06facd081 --- /dev/null +++ b/Content.Client/ADT/Heretic/UI/LivingHeartMenu.xaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/Content.Client/ADT/Heretic/UI/LivingHeartMenu.xaml.cs b/Content.Client/ADT/Heretic/UI/LivingHeartMenu.xaml.cs new file mode 100644 index 00000000000..a85077584ca --- /dev/null +++ b/Content.Client/ADT/Heretic/UI/LivingHeartMenu.xaml.cs @@ -0,0 +1,97 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.Heretic; +using Robust.Client.Player; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using System.Numerics; + +namespace Content.Client.ADT.Heretic.UI; + +public sealed partial class LivingHeartMenu : RadialMenu +{ + [Dependency] private readonly EntityManager _ent = default!; + [Dependency] private readonly IPrototypeManager _prot = default!; + [Dependency] private readonly IPlayerManager _player = default!; + + public EntityUid Entity { get; private set; } + + public event Action? SendActivateMessageAction; + + public LivingHeartMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + } + + public void SetEntity(EntityUid ent) + { + Entity = ent; + UpdateUI(); + } + + private void UpdateUI() + { + var main = FindControl("Main"); + if (main == null) return; + + var player = _player.LocalEntity; + + if (!_ent.TryGetComponent(player, out var heretic)) + return; + + foreach (var target in heretic.SacrificeTargets) + { + if (target == null) continue; + + var ent = _ent.GetEntity(target); + if (ent == null) + continue; + + var button = new EmbeddedEntityMenuButton + { + StyleClasses = { "RadialMenuButton" }, + SetSize = new Vector2(64, 64), + ToolTip = _ent.TryGetComponent(ent.Value, out var md) ? md.EntityName : "Unknown", + NetEntity = (NetEntity) target, + }; + + var texture = new SpriteView(ent.Value, _ent) + { + OverrideDirection = Direction.South, + VerticalAlignment = VAlignment.Center, + SetSize = new Vector2(64, 64), + VerticalExpand = true, + Stretch = SpriteView.StretchMode.Fill, + }; + button.AddChild(texture); + + main.AddChild(button); + } + AddAction(main); + } + + private void AddAction(RadialContainer main) + { + if (main == null) + return; + + foreach (var child in main.Children) + { + var castChild = child as EmbeddedEntityMenuButton; + if (castChild == null) + continue; + + castChild.OnButtonUp += _ => + { + SendActivateMessageAction?.Invoke(castChild.NetEntity); + Close(); + }; + } + } + + public sealed class EmbeddedEntityMenuButton : RadialMenuTextureButton + { + public NetEntity NetEntity; + } +} diff --git a/Content.Client/ADT/Heretic/UI/LivingHeartMenuBoundUserInterface.cs b/Content.Client/ADT/Heretic/UI/LivingHeartMenuBoundUserInterface.cs new file mode 100644 index 00000000000..d39372539d0 --- /dev/null +++ b/Content.Client/ADT/Heretic/UI/LivingHeartMenuBoundUserInterface.cs @@ -0,0 +1,34 @@ +using Content.Shared.Heretic; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.UserInterface; + +namespace Content.Client.ADT.Heretic.UI; + +public sealed partial class LivingHeartMenuBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IClyde _displayManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + + [NonSerialized] private LivingHeartMenu? _menu; + + public LivingHeartMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _menu = this.CreateWindow(); + _menu.SetEntity(Owner); + _menu.SendActivateMessageAction += SendMessage; + _menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / _displayManager.ScreenSize); + } + + private void SendMessage(NetEntity netent) + { + base.SendMessage(new EventHereticLivingHeartActivate() { Target = netent }); + } +} diff --git a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Ash.cs b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Ash.cs index 2bcae414d19..36e2127f301 100644 --- a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Ash.cs +++ b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Ash.cs @@ -51,9 +51,11 @@ private void OnVolcano(Entity ent, ref EventHereticVolcanoBlas return; var ignoredTargets = new List(); + // all ghouls are immune to heretic shittery foreach (var e in EntityQuery()) ignoredTargets.Add(e.Owner); + // all heretics with the same path are also immune foreach (var e in EntityQuery()) if (e.CurrentPath == ent.Comp.CurrentPath) diff --git a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Flesh.cs b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Flesh.cs index cec3512d4e3..8976a576eb2 100644 --- a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Flesh.cs +++ b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Flesh.cs @@ -86,6 +86,9 @@ private void OnFleshSurgery(Entity ent, ref EventHereticFleshS } private void OnFleshSurgeryDoAfter(Entity ent, ref EventHereticFleshSurgeryDoAfter args) { + if (args.Cancelled) + return; + if (args.Target == null) // shouldn't really happen. just in case return; diff --git a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Void.cs b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Void.cs index 18c0e36959c..4b821e3ff5b 100644 --- a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Void.cs +++ b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Void.cs @@ -90,7 +90,7 @@ private void OnVoidPull(Entity ent, ref HereticVoidPullEvent a // damage closest ones foreach (var pookie in topPriority) if (TryComp(pookie, out var dmg)) - _dmg.SetAllDamage(pookie, dmg, dmg.TotalDamage + .5f); + _dmg.SetAllDamage(pookie, dmg, dmg.TotalDamage + .2f); // stun close-mid range foreach (var pookie in midPriority) diff --git a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.cs b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.cs index 52d924463c4..9cbf5bc64ce 100644 --- a/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.cs +++ b/Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.cs @@ -27,6 +27,10 @@ using Robust.Shared.Map; using Content.Shared.StatusEffect; using Content.Shared.Throwing; +using Content.Server.Station.Systems; +using Content.Shared.Localizations; +using Robust.Shared.Audio; +using Content.Shared.Mobs.Components; namespace Content.Server.Heretic.Abilities; @@ -56,6 +60,9 @@ public sealed partial class HereticAbilitySystem : EntitySystem [Dependency] private readonly PhysicsSystem _phys = default!; [Dependency] private readonly SharedStunSystem _stun = default!; [Dependency] private readonly ThrowingSystem _throw = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly IMapManager _mapMan = default!; private List GetNearbyPeople(Entity ent, float range) { @@ -86,6 +93,9 @@ public override void Initialize() SubscribeLocalEvent(OnStore); SubscribeLocalEvent(OnMansusGrasp); + SubscribeLocalEvent(OnLivingHeart); + SubscribeLocalEvent(OnLivingHeartActivate); + SubscribeLocalEvent(OnMansusLink); SubscribeLocalEvent(OnMansusLinkDoafter); @@ -105,9 +115,6 @@ private bool TryUseAbility(EntityUid ent, BaseActionEvent args) // check if any magic items are worn if (TryComp(ent, out var hereticComp) && actionComp.RequireMagicItem && !hereticComp.Ascended) { - if (hereticComp.CodexActive) - return true; - var ev = new CheckMagicItemEvent(); RaiseLocalEvent(ent, ev); @@ -159,6 +166,59 @@ private void OnMansusGrasp(Entity ent, ref EventHereticMansusG ent.Comp.MansusGraspActive = true; args.Handled = true; } + private void OnLivingHeart(Entity ent, ref EventHereticLivingHeart args) + { + if (!TryUseAbility(ent, args)) + return; + + if (!TryComp(ent, out var uic)) + return; + + if (ent.Comp.SacrificeTargets.Count == 0) + { + _popup.PopupEntity(Loc.GetString("heretic-livingheart-notargets"), ent, ent); + args.Handled = true; + return; + } + + _ui.OpenUi((ent, uic), HereticLivingHeartKey.Key, ent); + args.Handled = true; + } + private void OnLivingHeartActivate(Entity ent, ref EventHereticLivingHeartActivate args) + { + var loc = string.Empty; + + var target = GetEntity(args.Target); + if (target == null) + return; + + if (!TryComp(target, out var mobstate)) + return; + var state = mobstate.CurrentState; + + var xquery = GetEntityQuery(); + var targetStation = _station.GetOwningStation(target); + var ownStation = _station.GetOwningStation(ent); + + var isOnStation = targetStation != null && targetStation == ownStation; + + var ang = Angle.Zero; + if (_mapMan.TryFindGridAt(_transform.GetMapCoordinates(Transform(ent)), out var grid, out var _)) + ang = Transform(grid).LocalRotation; + + var vector = _transform.GetWorldPosition((EntityUid) target, xquery) - _transform.GetWorldPosition(ent, xquery); + var direction = (vector.ToWorldAngle() - ang).GetDir(); + + var locdir = ContentLocalizationManager.FormatDirection(direction).ToLower(); + var locstate = state.ToString().ToLower(); + + if (isOnStation) + loc = Loc.GetString("heretic-livingheart-onstation", ("state", locstate), ("direction", locdir)); + else loc = Loc.GetString("heretic-livingheart-offstation", ("state", locstate), ("direction", locdir)); + + _popup.PopupEntity(loc, ent, ent, PopupType.Medium); + _aud.PlayPvs(new SoundPathSpecifier("/Audio/ADT/Heretic/heartbeat.ogg"), ent, AudioParams.Default.WithVolume(-3f)); + } private void OnMansusLink(Entity ent, ref EventHereticMansusLink args) { @@ -190,6 +250,9 @@ private void OnMansusLink(Entity ent, ref EventHereticMansusLink } private void OnMansusLinkDoafter(Entity ent, ref HereticMansusLinkDoAfter args) { + if (args.Cancelled) + return; + var reciever = EnsureComp(args.Target); var transmitter = EnsureComp(args.Target); var radio = EnsureComp(args.Target); diff --git a/Content.Server/ADT/Heretic/EntitySystems/AristocratSystem.cs b/Content.Server/ADT/Heretic/EntitySystems/AristocratSystem.cs index c0060d42af3..f5d4b8cb44e 100644 --- a/Content.Server/ADT/Heretic/EntitySystems/AristocratSystem.cs +++ b/Content.Server/ADT/Heretic/EntitySystems/AristocratSystem.cs @@ -61,7 +61,7 @@ private void Cycle(Entity ent) continue; if (TryComp(look, out var temp)) - _temp.ChangeHeat(look, -100f, true, temp); + _temp.ChangeHeat(look, -200f, true, temp); _statusEffect.TryAddStatusEffect(look, "Muted", TimeSpan.FromSeconds(5), true); diff --git a/Content.Server/ADT/Heretic/EntitySystems/HereticKnowledgeSystem.cs b/Content.Server/ADT/Heretic/EntitySystems/HereticKnowledgeSystem.cs index bc6030e8a21..f4fa55a893c 100644 --- a/Content.Server/ADT/Heretic/EntitySystems/HereticKnowledgeSystem.cs +++ b/Content.Server/ADT/Heretic/EntitySystems/HereticKnowledgeSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Heretic; using Content.Shared.Popups; using Robust.Shared.Prototypes; +using Content.Shared.ADT.Heretic.Components; namespace Content.Server.Heretic.EntitySystems; @@ -31,6 +32,8 @@ public void AddKnowledge(EntityUid uid, HereticComponent comp, ProtoId comp.PathStage) + // make sure we only progress when buying current path knowledge + if (data.Stage > comp.PathStage && data.Path == comp.CurrentPath) comp.PathStage = data.Stage; if (!silent) @@ -64,6 +68,8 @@ public void RemoveKnowledge(EntityUid uid, HereticComponent comp, ProtoId e.Key, e => e.Value) ?? new(); var requiredTags = rit.RequiredTags?.ToDictionary(e => e.Key, e => e.Value) ?? new(); foreach (var behavior in behaviors) @@ -71,22 +74,11 @@ public bool TryDoRitual(EntityUid performer, EntityUid platform, ProtoId= 0) - toDelete.Add(look); - } - } - // check for matching tags foreach (var tag in requiredTags) { + if (_container.IsEntityInContainer(look)) + continue; if (!TryComp(look, out var tags)) continue; var ltags = tags.Tags; @@ -102,11 +94,6 @@ public bool TryDoRitual(EntityUid performer, EntityUid platform, ProtoId 0) - missingList.Add(name.Key); - // add missing tags foreach (var tag in requiredTags) if (tag.Value > 0) @@ -167,6 +154,7 @@ public override void Initialize() SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnRitualChosenMessage); } private void OnInteract(Entity ent, ref InteractHandEvent args) @@ -180,22 +168,22 @@ private void OnInteract(Entity ent, ref InteractHand return; } - if (heretic.ChosenRitual == null) - heretic.ChosenRitual = heretic.KnownRituals[0]; + _uiSystem.OpenUi(ent.Owner, HereticRitualRuneUiKey.Key, args.User); + } - else if (heretic.ChosenRitual != null) - { - var index = heretic.KnownRituals.FindIndex(m => m == heretic.ChosenRitual) + 1; + private void OnRitualChosenMessage(Entity ent, ref HereticRitualMessage args) + { + var user = args.Actor; - if (index >= heretic.KnownRituals.Count) - index = 0; + if (!TryComp(user, out var heretic)) + return; - heretic.ChosenRitual = heretic.KnownRituals[index]; - } + heretic.ChosenRitual = args.ProtoId; var ritualName = Loc.GetString(GetRitual(heretic.ChosenRitual).Name); - _popup.PopupEntity(Loc.GetString("heretic-ritual-switch", ("name", ritualName)), args.User, args.User); + _popup.PopupEntity(Loc.GetString("heretic-ritual-switch", ("name", ritualName)), user, user); } + private void OnInteractUsing(Entity ent, ref InteractUsingEvent args) { if (!TryComp(args.User, out var heretic)) @@ -214,6 +202,7 @@ private void OnInteractUsing(Entity ent, ref Interac return; _audio.PlayPvs(RitualSuccessSound, ent, AudioParams.Default.WithVolume(-3f)); + _popup.PopupEntity(Loc.GetString("heretic-ritual-success"), ent, args.User); Spawn("HereticRuneRitualAnimation", Transform(ent).Coordinates); } diff --git a/Content.Server/ADT/Heretic/EntitySystems/HereticSystem.cs b/Content.Server/ADT/Heretic/EntitySystems/HereticSystem.cs index 4b4410b8ee0..0678bd6dcee 100644 --- a/Content.Server/ADT/Heretic/EntitySystems/HereticSystem.cs +++ b/Content.Server/ADT/Heretic/EntitySystems/HereticSystem.cs @@ -13,6 +13,16 @@ using Content.Server.Atmos.Components; using Content.Shared.Damage; using Content.Server.Heretic.Components; +using Content.Server.Antag; +using Robust.Shared.Random; +using System.Linq; +using Content.Shared.Humanoid; +using Robust.Server.Player; +using Content.Server.Revolutionary.Components; +using Content.Shared.Random.Helpers; +using Content.Shared.Roles.Jobs; +using Robust.Shared.Prototypes; +using Content.Shared.Roles; namespace Content.Server.Heretic.EntitySystems; @@ -23,6 +33,10 @@ public sealed partial class HereticSystem : EntitySystem [Dependency] private readonly HereticKnowledgeSystem _knowledge = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; + [Dependency] private readonly IRobustRandom _rand = default!; + [Dependency] private readonly IPlayerManager _playerMan = default!; + [Dependency] private readonly IPrototypeManager _prot = default!; private float _timer = 0f; private float _passivePointCooldown = 20f * 60f; @@ -32,10 +46,15 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnCompInit); - SubscribeLocalEvent(OnMagicItemExamine); + + SubscribeLocalEvent(OnUpdateTargets); + SubscribeLocalEvent(OnRerollTargets); SubscribeLocalEvent(OnAscension); + SubscribeLocalEvent(OnBeforeDamage); SubscribeLocalEvent(OnDamage); + + SubscribeLocalEvent(OnMagicItemExamine); } public override void Update(float frameTime) @@ -77,34 +96,82 @@ private void OnCompInit(Entity ent, ref ComponentInit args) foreach (var knowledge in ent.Comp.BaseKnowledge) _knowledge.AddKnowledge(ent, ent.Comp, knowledge); - } - private void OnMagicItemExamine(Entity ent, ref ExaminedEvent args) - { - if (!HasComp(args.Examiner)) - return; - - args.PushMarkup(Loc.GetString("heretic-magicitem-examine")); + RaiseLocalEvent(ent, new EventHereticRerollTargets()); } - private void OnBeforeDamage(Entity ent, ref BeforeDamageChangedEvent args) + #region Internal events (target reroll, ascension, etc.) + + private void OnUpdateTargets(Entity ent, ref EventHereticUpdateTargets args) { - // ignore damage from heretic stuff - if (args.Origin.HasValue && HasComp(args.Origin)) - args.Cancelled = true; + ent.Comp.SacrificeTargets = ent.Comp.SacrificeTargets + .Where(target => TryGetEntity(target, out var tent) && Exists(tent)) + .ToList(); + Dirty(ent); // update client } - private void OnDamage(Entity ent, ref DamageModifyEvent args) + + private void OnRerollTargets(Entity ent, ref EventHereticRerollTargets args) { - if (!ent.Comp.Ascended) - return; + // welcome to my linq smorgasbord of doom + // have fun figuring that out - switch (ent.Comp.CurrentPath) + var targets = _antag.GetAliveConnectedPlayers(_playerMan.Sessions) + .Where(ics => ics.AttachedEntity.HasValue && HasComp(ics.AttachedEntity)); + + var eligibleTargets = new List(); + foreach (var target in targets) + eligibleTargets.Add(target.AttachedEntity!.Value); // it can't be null because see .Where(HasValue) + + // no heretics or other baboons + eligibleTargets = eligibleTargets.Where(t => !HasComp(t) && !HasComp(t)).ToList(); + + var pickedTargets = new List(); + + var predicates = new List>(); + + // pick one command staff + predicates.Add(t => HasComp(t)); + + // pick one secoff + predicates.Add(t => + _prot.TryIndex("Security", out var dept) // can we get sec jobs? + && _mind.TryGetMind(t, out var mindid, out _) // does it have a mind? + && TryComp(mindid, out var jobc) && jobc.Prototype.HasValue // does it have a job? + && dept.Roles.Contains(jobc.Prototype!.Value)); // is that job being shitsec? + + // pick one person from the same department + predicates.Add(t => + _mind.TryGetMind(t, out var tmind, out _) && _mind.TryGetMind(ent, out var ownmind, out _) // get minds + && TryComp(tmind, out var tjob) && tjob.Prototype.HasValue // get jobs + && TryComp(ownmind, out var ownjob) && ownjob.Prototype.HasValue + && _prot.EnumeratePrototypes() // compare jobs for all + .Where(d => + d.Roles.Contains(tjob.Prototype.Value) + && d.Roles.Contains(ownjob.Prototype.Value)) // true = same department + .ToList().Count != 0); + + foreach (var predicate in predicates) { - case "Ash": - // nullify heat damage because zased - args.Damage.DamageDict["Heat"] = 0; - break; + var list = eligibleTargets.Where(predicate).ToList(); + + if (list.Count == 0) + continue; + + // pick and take + var picked = _rand.PickAndTake(list); + pickedTargets.Add(picked); } + + // add whatever more until satisfied + for (int i = 0; i <= ent.Comp.MaxTargets - pickedTargets.Count; i++) + if (eligibleTargets.Count > 0) + pickedTargets.Add(_rand.PickAndTake(eligibleTargets)); + + // leave only unique entityuids + pickedTargets = pickedTargets.Distinct().ToList(); + + ent.Comp.SacrificeTargets = pickedTargets.ConvertAll(t => GetNetEntity(t)).ToList(); + Dirty(ent); // update client } // notify the crew of how good the person is and play the cool sound :godo: @@ -153,4 +220,44 @@ private void OnAscension(Entity ent, ref EventHereticAscension break; } } + + #endregion + + #region External events (damage, etc.) + + private void OnBeforeDamage(Entity ent, ref BeforeDamageChangedEvent args) + { + // ignore damage from heretic stuff + if (args.Origin.HasValue && HasComp(args.Origin)) + args.Cancelled = true; + } + private void OnDamage(Entity ent, ref DamageModifyEvent args) + { + if (!ent.Comp.Ascended) + return; + + switch (ent.Comp.CurrentPath) + { + case "Ash": + // nullify heat damage because zased + args.Damage.DamageDict["Heat"] = 0; + break; + } + } + + #endregion + + + + #region Miscellaneous + + private void OnMagicItemExamine(Entity ent, ref ExaminedEvent args) + { + if (!HasComp(args.Examiner)) + return; + + args.PushMarkup(Loc.GetString("heretic-magicitem-examine")); + } + + #endregion } diff --git a/Content.Server/ADT/Heretic/Ritual/CustomBehavior.Sacrifice.cs b/Content.Server/ADT/Heretic/Ritual/CustomBehavior.Sacrifice.cs index 8ce3f7a660a..a43b7d61013 100644 --- a/Content.Server/ADT/Heretic/Ritual/CustomBehavior.Sacrifice.cs +++ b/Content.Server/ADT/Heretic/Ritual/CustomBehavior.Sacrifice.cs @@ -29,6 +29,11 @@ namespace Content.Server.Heretic.Ritual; /// [DataField] public float Max = 1; + /// + /// Should we count only targets? + /// + [DataField] public bool OnlyTargets = false; + // this is awful but it works so i'm not complaining protected SharedMindSystem _mind = default!; protected HereticSystem _heretic = default!; @@ -62,8 +67,9 @@ public override bool Execute(RitualData args, out string? outstr) // get all the dead ones foreach (var look in lookup) { - if (!args.EntityManager.TryGetComponent(look, out var mobstate) - || !args.EntityManager.HasComponent(look)) + if (!args.EntityManager.TryGetComponent(look, out var mobstate) // only mobs + || !args.EntityManager.HasComponent(look) // only humans + || (OnlyTargets && !hereticComp.SacrificeTargets.Contains(args.EntityManager.GetNetEntity(look)))) // only targets continue; if (mobstate.CurrentState == Shared.Mobs.MobState.Dead) @@ -72,7 +78,7 @@ public override bool Execute(RitualData args, out string? outstr) if (uids.Count < Min) { - outstr = Loc.GetString("heretic-ritual-fail-sacrifice"); + outstr = Loc.GetString("heretic-ritual-fail-sacrifice-ineligible"); return false; } @@ -115,5 +121,6 @@ public override void Finalize(RitualData args) // reset it because it refuses to work otherwise. uids = new(); + args.EntityManager.EventBus.RaiseLocalEvent(args.Performer, new EventHereticUpdateTargets()); } } diff --git a/Content.Server/ADT/Magic/ChainFireballComponent.cs b/Content.Server/ADT/Magic/ChainFireballComponent.cs index 0947fb6dfdb..a629f7170f6 100644 --- a/Content.Server/ADT/Magic/ChainFireballComponent.cs +++ b/Content.Server/ADT/Magic/ChainFireballComponent.cs @@ -3,15 +3,10 @@ namespace Content.Server.Magic; [RegisterComponent] public sealed partial class ChainFireballComponent : Component { - /// - /// The added chance of the ball disappearing (in %) - /// - [DataField] public float DisappearChanceDelta = 0.5f; - /// /// The chance of the ball disappearing (in %) /// - [DataField] public float DisappearChance = 0f; + [DataField] public float DisappearChance = 0.05f; public List IgnoredTargets = new(); } diff --git a/Content.Server/ADT/Magic/ChainFireballSystem.cs b/Content.Server/ADT/Magic/ChainFireballSystem.cs index a2544d726df..5909b28a94e 100644 --- a/Content.Server/ADT/Magic/ChainFireballSystem.cs +++ b/Content.Server/ADT/Magic/ChainFireballSystem.cs @@ -30,7 +30,8 @@ private void OnHit(Entity ent, ref ProjectileHitEvent ar if (_random.Prob(ent.Comp.DisappearChance)) return; - Spawn(ent, ent.Comp.IgnoredTargets); + // spawn new fireball on target + Spawn(args.Target, ent.Comp.IgnoredTargets); QueueDel(ent); } @@ -43,7 +44,7 @@ public bool Spawn(EntityUid source, List ignoredTargets) foreach (var look in lookup) { if (ignoredTargets.Contains(look) - || !HasComp(look)) // ignore non mobs whatsoever + || !HasComp(look)) // ignore non mobs continue; mobs.Add(look); @@ -60,17 +61,14 @@ public bool Spawn(EntityUid source, EntityUid target, List ignoredTar { return SpawnFireball(source, target, ignoredTargets); } - private bool SpawnFireball(EntityUid uid, EntityUid target, List ignoredTargets) + public bool SpawnFireball(EntityUid uid, EntityUid target, List ignoredTargets) { var ball = Spawn("FireballChain", Transform(uid).Coordinates); + + // set ignore list if it wasn't set already if (TryComp(ball, out var sfc)) - { sfc.IgnoredTargets = sfc.IgnoredTargets.Count > 0 ? sfc.IgnoredTargets : ignoredTargets; - if (TryComp(uid, out var usfc)) - sfc.DisappearChance = usfc.DisappearChance + sfc.DisappearChanceDelta; - } - // launch it towards the target var fromCoords = Transform(uid).Coordinates; var toCoords = Transform(target).Coordinates; @@ -86,7 +84,7 @@ private bool SpawnFireball(EntityUid uid, EntityUid target, List igno var direction = toCoords.ToMapPos(EntityManager, _transform) - spawnCoords.ToMapPos(EntityManager, _transform); - _gun.ShootProjectile(ball, direction, userVelocity, uid, ball); + _gun.ShootProjectile(ball, direction, userVelocity, uid, uid); return true; } diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs index 23e77c21028..bbb38f01558 100644 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -78,6 +78,20 @@ public int GetTotalPlayerCount(IList pool) return count; } + // goob edit + public List GetAliveConnectedPlayers(IList pool) + { + var l = new List(); + foreach (var session in pool) + { + if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) + continue; + l.Add(session); + } + return l; + } + // goob edit end + /// /// Gets the number of antagonists that should be present for a given antag definition based on the provided pool. /// A null pool will simply use the player count. diff --git a/Content.Shared/ADT/Heretic/Components/HereticComponent.cs b/Content.Shared/ADT/Heretic/Components/HereticComponent.cs index 7422a662a8b..dbf23d7cceb 100644 --- a/Content.Shared/ADT/Heretic/Components/HereticComponent.cs +++ b/Content.Shared/ADT/Heretic/Components/HereticComponent.cs @@ -14,13 +14,23 @@ public sealed partial class HereticComponent : Component "BreakOfDawn", "HeartbeatOfMansus", "AmberFocus", - "CodexCicatrix", - "CloakOfShadow" + "LivingHeart", }; #endregion - [DataField, AutoNetworkedField] public bool Ascended = false; + [DataField, AutoNetworkedField] public List> KnownRituals = new(); + [DataField] public ProtoId? ChosenRitual; + + /// + /// Contains the list of targets that are eligible for sacrifice. + /// + [DataField, AutoNetworkedField] public List SacrificeTargets = new(); + + /// + /// How much targets can a heretic have? + /// + [DataField, AutoNetworkedField] public int MaxTargets = 5; // hardcoded paths because i hate it // "Ash", "Lock", "Flesh", "Void", "Blade", "Rust" @@ -34,19 +44,13 @@ public sealed partial class HereticComponent : Component /// [DataField, AutoNetworkedField] public int PathStage = 0; - [DataField, AutoNetworkedField] public List> KnownRituals = new(); - [DataField] public ProtoId? ChosenRitual; + [DataField, AutoNetworkedField] public bool Ascended = false; /// /// Used to prevent double casting mansus grasp. /// [ViewVariables(VVAccess.ReadOnly)] public bool MansusGraspActive = false; - /// - /// Doubles the eldritch influence if true. - /// - [ViewVariables(VVAccess.ReadOnly)] public bool CodexActive = false; - /// /// Indicates if a heretic is able to cast advanced spells. /// Requires wearing focus, codex cicatrix, hood or anything else that allows him to do so. diff --git a/Content.Shared/ADT/Heretic/Components/HereticRitualEvents.cs b/Content.Shared/ADT/Heretic/Components/HereticRitualEvents.cs new file mode 100644 index 00000000000..a883af4fd0c --- /dev/null +++ b/Content.Shared/ADT/Heretic/Components/HereticRitualEvents.cs @@ -0,0 +1,22 @@ +using Content.Shared.Heretic.Prototypes; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.ADT.Heretic.Components; + +[Serializable, NetSerializable] +public sealed class HereticRitualMessage : BoundUserInterfaceMessage +{ + public ProtoId ProtoId; + + public HereticRitualMessage(ProtoId protoId) + { + ProtoId = protoId; + } +} + +[Serializable, NetSerializable] +public enum HereticRitualRuneUiKey : byte +{ + Key +} diff --git a/Content.Shared/ADT/Heretic/Heretic.Abilites.cs b/Content.Shared/ADT/Heretic/Heretic.Abilites.cs index 1f1ef18e7fa..2b458adc7b9 100644 --- a/Content.Shared/ADT/Heretic/Heretic.Abilites.cs +++ b/Content.Shared/ADT/Heretic/Heretic.Abilites.cs @@ -66,6 +66,15 @@ public sealed partial class CheckMagicItemEvent : HandledEntityEventArgs, IInven // basic public sealed partial class EventHereticOpenStore : InstantActionEvent { } public sealed partial class EventHereticMansusGrasp : InstantActionEvent { } +public sealed partial class EventHereticLivingHeart : InstantActionEvent { } // opens ui +[Serializable, NetSerializable] public sealed partial class EventHereticLivingHeartActivate : BoundUserInterfaceMessage // triggers the logic +{ + public NetEntity? Target { get; set; } +} +[Serializable, NetSerializable] public enum HereticLivingHeartKey : byte +{ + Key +} // for mobs public sealed partial class EventHereticMansusLink : EntityTargetActionEvent { } diff --git a/Content.Shared/ADT/Heretic/Prototypes/Heretic.Data.cs b/Content.Shared/ADT/Heretic/Prototypes/Heretic.Data.cs index 5bd113a4df4..549d077f136 100644 --- a/Content.Shared/ADT/Heretic/Prototypes/Heretic.Data.cs +++ b/Content.Shared/ADT/Heretic/Prototypes/Heretic.Data.cs @@ -1,12 +1,12 @@ using Content.Shared.Tag; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.Heretic.Prototypes; -[Serializable, NetSerializable] +[Serializable, NetSerializable, DataDefinition] [Prototype("hereticKnowledge")] -[DataDefinition] public sealed partial class HereticKnowledgePrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; @@ -27,9 +27,8 @@ public sealed partial class HereticKnowledgePrototype : IPrototype [DataField] public List? ActionPrototypes; } -[Serializable, NetSerializable] +[Serializable, NetSerializable, DataDefinition] [Prototype("hereticRitual")] -[DataDefinition] public sealed partial class HereticRitualPrototype : IPrototype, ICloneable { [IdDataField] public string ID { get; private set; } = default!; @@ -64,6 +63,11 @@ public sealed partial class HereticRitualPrototype : IPrototype, ICloneable /// [DataField] public ProtoId? OutputKnowledge; + /// + /// Icon for ritual in radial menu. + /// + [DataField] public SpriteSpecifier Icon = new SpriteSpecifier.Rsi(new("ADT/Heretic/amber_focus.rsi"), "icon"); + /// Please use this instead of editing the prototype. Shit WILL break if you don't. public object Clone() { @@ -76,9 +80,12 @@ public object Clone() CustomBehaviors = CustomBehaviors, Output = Output, OutputEvent = OutputEvent, - OutputKnowledge = OutputKnowledge + OutputKnowledge = OutputKnowledge, + Icon = Icon }; } } [Serializable, NetSerializable, DataDefinition] public sealed partial class EventHereticAscension : EntityEventArgs { } +[Serializable, NetSerializable, DataDefinition] public sealed partial class EventHereticRerollTargets : EntityEventArgs { } +[Serializable, NetSerializable, DataDefinition] public sealed partial class EventHereticUpdateTargets : EntityEventArgs { } diff --git a/Resources/Audio/ADT/Heretic/Ambience/Antag/Heretic/attributions.yml b/Resources/Audio/ADT/Heretic/Ambience/Antag/Heretic/attributions.yml new file mode 100644 index 00000000000..904fa35f354 --- /dev/null +++ b/Resources/Audio/ADT/Heretic/Ambience/Antag/Heretic/attributions.yml @@ -0,0 +1,4 @@ +- files: ["ascend_ash.ogg", "ascend_blade.ogg", "ascend_flesh.ogg", "ascend_knock.ogg", "ascend_rust.ogg", "ascend_void.ogg", "heretic_gain.ogg", "heretic_gain_intense.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from /tg/station" + source: "https://github.com/tgstation/tgstation/tree/master/sound/music/antag/heretic" \ No newline at end of file diff --git a/Resources/Audio/ADT/Heretic/attributions.yml b/Resources/Audio/ADT/Heretic/attributions.yml new file mode 100644 index 00000000000..571127c8b52 --- /dev/null +++ b/Resources/Audio/ADT/Heretic/attributions.yml @@ -0,0 +1,9 @@ +- files: ["blind.ogg", "castsummon.ogg", "disintegrate.ogg", "hereticknock.ogg", "repulse.ogg", "voidblink.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from /tg/station" + source: "https://github.com/tgstation/tgstation/tree/master/sound/effects/magic" + +- files: ["heartbeat.ogg"] + license: "CC-BY-SA-3.0" + copyright: "made by whateverusername0" + source: "https://knowyourmeme.com/memes/source-i-made-it-up" \ No newline at end of file diff --git a/Resources/Audio/ADT/Heretic/heartbeat.ogg b/Resources/Audio/ADT/Heretic/heartbeat.ogg new file mode 100644 index 00000000000..acd8dbeb82b Binary files /dev/null and b/Resources/Audio/ADT/Heretic/heartbeat.ogg differ diff --git a/Resources/Locale/ru-RU/ADT/Heretic/abilities/heretic.ftl b/Resources/Locale/ru-RU/ADT/Heretic/abilities/heretic.ftl index 021619e29ad..8d48956375e 100644 --- a/Resources/Locale/ru-RU/ADT/Heretic/abilities/heretic.ftl +++ b/Resources/Locale/ru-RU/ADT/Heretic/abilities/heretic.ftl @@ -17,6 +17,11 @@ heretic-manselink-fail-nomind = У этого существа отсутств heretic-manselink-start = Вы начинаете связывать разум существа со своим. heretic-manselink-start-target = Вы чувствуете, как ваш разум куда-то утягивает... +heretic-livingheart-notargets = Нет доступных целей. Посетите руну. +heretic-livingheart-offstation = {$state} в направлении к {$direction}у! +heretic-livingheart-onstation = {$state} в направелии к {$direction}у! +heretic-livingheart-unknown = Он... не в этой реальности. + ## speech heretic-speech-mansusgrasp = R'CH T'H TR'TH! diff --git a/Resources/Locale/ru-RU/ADT/Heretic/rituals.ftl b/Resources/Locale/ru-RU/ADT/Heretic/rituals.ftl index 70d53c412e1..c84715508f1 100644 --- a/Resources/Locale/ru-RU/ADT/Heretic/rituals.ftl +++ b/Resources/Locale/ru-RU/ADT/Heretic/rituals.ftl @@ -8,13 +8,14 @@ heretic-ritual-noritual = Ритуал не выбран! heretic-ritual-basic-sacrifice = Сердцебиение Мансуса heretic-ritual-basic-focus = Янтарная Призма -heretic-ritual-basic-codex = Кодекс Цикатрикс +heretic-ritual-basic-heart = Беспокойное сердцебиение heretic-ritual-fail-sacrifice = Нет тела для жертвоприношения. heretic-ritual-fail-reagentpuddle = Не имеется следующего реагента: {$reagentname}. heretic-ritual-fail-temperature-hot = Здесь слишком жарко. heretic-ritual-fail-temperature-cold = Здесь недостаточно холодно. heretic-ritual-fail-sacrifice-ash = Недостаточно мертвых или горящих. +heretic-ritual-fail-sacrifice-ineligible = Руна отказывается принять эту жертву. ## side quests heretic-ritual-side-knowledge = Ритуал Знания diff --git a/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/basic.ftl b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/basic.ftl new file mode 100644 index 00000000000..fd1525f3153 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/basic.ftl @@ -0,0 +1,11 @@ +action-heretic-store-name = Открыть магазин знаний +action-heretic-store-desc = Откройте магазин знаний. + +action-heretic-grasp-name = Хватка мансуса +action-heretic-grasp-desc = Направьте силу Старых Богов через свою руку. + +action-heretic-heart-name = Живое сердце +action-heretic-heart-desc = Используйте свое сердце, чтобы определить местоположение жертвенных целей. + +action-heretic-link-name = Связь мансуса +action-heretic-link-desc = Пронзите реальность и соедините умы друг с другом. Радиоклавиша по умолчанию - \ No newline at end of file diff --git a/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_ash.ftl b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_ash.ftl new file mode 100644 index 00000000000..9781cc88ecd --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_ash.ftl @@ -0,0 +1,14 @@ +action-heretic-ash-passage-name = Пепельный Сдвиг +action-heretic-ash-passage-desc = Заклинание малой дальности, позволяющее беспрепятственно проходить сквозь стены. + +action-heretic-ash-blast-name = Вулканический Взрыв +action-heretic-ash-blast-desc = Зарядите огненную вспышку, которая проносится между ближайшими целями, поджигая их. + +action-heretic-ash-rebirth-name = Пламенное Перерождение +action-heretic-ash-rebirth-desc = Заклинание, которое гасит вас и лишает жизненных сил ближайших язычников, охваченных пламенем. + +action-heretic-ash-oath-name = Клятва пламени +action-heretic-ash-oath-desc = В течение минуты вы будете пассивно создавать вокруг себя огненное кольцо. + +action-heretic-ash-cascade-name = Огненный каскад +action-heretic-ash-cascade-desc = Поджигает воздух вокруг вас. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_flesh.ftl b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_flesh.ftl new file mode 100644 index 00000000000..d70021ad702 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_flesh.ftl @@ -0,0 +1,5 @@ +action-heretic-flesh-surgery-name = Хирургия плоти +action-heretic-flesh-surgery-desc = Удалите случайный орган жертвы. + +action-heretic-flesh-uncoil-name = ВЫСВОБОЖДЕНИЕ РЕАЛЬНОСТИ +action-heretic-flesh-uncoil-desc = Превратитесь в эльдрический ужас. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_void.ftl b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_void.ftl new file mode 100644 index 00000000000..0e003be4a65 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/prototypes/Actions/heretic/path_void.ftl @@ -0,0 +1,8 @@ +action-heretic-void-blast-name = Пусотный выстрел +action-heretic-void-blast-desc = Выстрелите перед собой конусом льда. + +action-heretic-void-phase-name = Пустотный рывой +action-heretic-void-phase-desc = Сделайте рывок сквозь пустоту, сбивая с ног всех вокруг. + +action-heretic-void-pull-name = Тяга к пустоте +action-heretic-void-pull-desc = Притягивайте и повреждайте ближайших язычников. \ No newline at end of file diff --git a/Resources/Prototypes/ADT/Entities/Personalization/Objects/personalization_items.yml b/Resources/Prototypes/ADT/Entities/Personalization/Objects/personalization_items.yml index ec8fb3e73b6..3b3900a00b9 100644 --- a/Resources/Prototypes/ADT/Entities/Personalization/Objects/personalization_items.yml +++ b/Resources/Prototypes/ADT/Entities/Personalization/Objects/personalization_items.yml @@ -983,104 +983,103 @@ sound: path: /Audio/Voice/Reptilian/reptilian_scream.ogg - # Спальник для Бобо -- type: entity - id: ADTBedSleepingBagBobo - parent: BaseDeployFoldable - name: sleeping bag - description: This is a special cocoon blanket that keeps a person warm during sleep. - components: - - type: Item - size: Small - - type: Sprite - drawdepth: SmallObjects - sprite: ADT/Structures/Furniture/meshok.rsi - layers: - - state: duffel_bag - map: ["unfoldedLayer", "enum.StorageVisualLayers.Base"] - - state: duffel_bag_folded - map: ["foldedLayer"] - visible: false - - state: open_overlay - map: ["enum.StorageVisualLayers.Door"] - - type: Icon - sprite: ADT/Structures/Furniture/meshok.rsi - state: duffel_bag - - type: Tag - tags: - - BodyBag - - type: Clickable - - type: InteractionOutline - - type: MovedByPressure - - type: Physics - bodyType: Dynamic - - type: Transform - noRot: true - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeAabb - bounds: "-0.25,-0.4,0.25,0.1" - density: 20 - mask: - - ItemMask - - type: EntityStorage - capacity: 1 - isCollidableWhenOpen: true - closeSound: - path: /Audio/Misc/zip.ogg - openSound: - path: /Audio/Misc/zip.ogg - - type: EntityStorageLayingDownOverride - - type: Appearance - - type: EntityStorageVisuals - stateDoorOpen: open_overlay - - type: GenericVisualizer - visuals: - enum.FoldedVisuals.State: - foldedLayer: - True: {visible: true} - False: {visible: false} - unfoldedLayer: - True: {visible: false} - False: {visible: true} - - type: Pullable - - type: AntiRottingContainer - - type: ItemSlots - - type: ContainerContainer - containers: - entity_storage: !type:Container - paper_label: !type:ContainerSlot - - type: StaticPrice - price: 50 - # св-ва кровати: - - type: HealOnBuckle - damage: - types: - Poison: -0.1 - Blunt: -0.1 - - type: Strap - position: Down - rotation: -90 - # Слоты - - type: Clothing - quickEquip: false - slots: - - back - -- type: entity - id: ADTBedSleepingBagBoboFolded - name: sleeping bag fold - description: A plastic bag designed for the storage and transportation of cadavers to stop body decomposition. - parent: ADTBedSleepingBagBobo - suffix: folded - components: - - type: Foldable - folded: true - - type: Item - size: Normal - - type: Strap +# # Спальник для Бобо +# - type: entity +# id: ADTBedSleepingBagBobo +# parent: BaseDeployFoldable +# name: sleeping bag +# description: This is a special cocoon blanket that keeps a person warm during sleep. +# components: +# - type: Item +# size: Small +# - type: Sprite +# drawdepth: SmallObjects +# sprite: ADT/Structures/Furniture/meshok.rsi +# layers: +# - state: duffel_bag +# map: ["unfoldedLayer", "enum.StorageVisualLayers.Base"] +# - state: duffel_bag_folded +# map: ["foldedLayer"] +# visible: false +# - state: open_overlay +# map: ["enum.StorageVisualLayers.Door"] +# - type: Icon +# sprite: ADT/Structures/Furniture/meshok.rsi +# state: duffel_bag +# - type: Tag +# tags: +# - BodyBag +# - type: Clickable +# - type: InteractionOutline +# - type: MovedByPressure +# - type: Physics +# bodyType: Dynamic +# - type: Transform +# noRot: true +# - type: Fixtures +# fixtures: +# fix1: +# shape: +# !type:PhysShapeAabb +# bounds: "-0.25,-0.4,0.25,0.1" +# density: 20 +# mask: +# - ItemMask +# - type: EntityStorage +# capacity: 1 +# isCollidableWhenOpen: true +# closeSound: +# path: /Audio/Misc/zip.ogg +# openSound: +# path: /Audio/Misc/zip.ogg +# - type: EntityStorageLayingDownOverride +# - type: Appearance +# - type: EntityStorageVisuals +# stateDoorOpen: open_overlay +# - type: GenericVisualizer +# visuals: +# enum.FoldedVisuals.State: +# foldedLayer: +# True: {visible: true} +# False: {visible: false} +# unfoldedLayer: +# True: {visible: false} +# False: {visible: true} +# - type: Pullable +# - type: AntiRottingContainer +# - type: ItemSlots +# - type: ContainerContainer +# containers: +# entity_storage: !type:Container +# paper_label: !type:ContainerSlot +# - type: StaticPrice +# price: 50 +# # св-ва кровати: +# - type: HealOnBuckle +# damage: +# types: +# Poison: -0.1 +# Blunt: -0.1 +# - type: Strap +# position: Down +# rotation: -90 +# # Слоты +# - type: Clothing +# quickEquip: false +# slots: +# - back + +# - type: entity +# id: ADTBedSleepingBagBoboFolded +# name: sleeping bag fold +# description: A plastic bag designed for the storage and transportation of cadavers to stop body decomposition. +# parent: ADTBedSleepingBagBobo +# suffix: folded +# components: +# - type: Foldable +# folded: true +# - type: Item +# size: Normal - type: entity parent: BasePlushie diff --git a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/basic.yml b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/basic.yml index bbb79a22edf..329d3d1f171 100644 --- a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/basic.yml +++ b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/basic.yml @@ -1,8 +1,8 @@ # actions - type: entity id: ActionHereticOpenStore - name: Open Knowledge Store - description: Open the Knowledge Store. + name: action-heretic-store-name + description: action-heretic-store-desc categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -16,8 +16,8 @@ - type: entity id: ActionHereticMansusGrasp - name: Mansus Grasp - description: Channel the power of the Old Gods through your grip. + name: action-heretic-grasp-name + description: action-heretic-grasp-desc categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -30,10 +30,26 @@ - type: HereticAction requireMagicItem: false +- type: entity + id: ActionHereticLivingHeart + name: action-heretic-heart-name + description: action-heretic-heart-desc + categories: [ HideSpawnMenu ] + components: + - type: InstantAction + useDelay: 2 + itemIconStyle: NoItem + icon: + sprite: ADT/Heretic/abilities_heretic.rsi + state: living_heart + event: !type:EventHereticLivingHeart {} + - type: HereticAction + requireMagicItem: false + - type: entity id: ActionHereticMansusLink name: Manse Link - description: This spell allows you to pierce through reality and connect minds to one another via your Mansus Link. + description: Pierce through reality and connect minds to one another. Default radio key is :z categories: [ HideSpawnMenu ] components: - type: EntityTargetAction diff --git a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_ash.yml b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_ash.yml index 1ef52d43100..24367b28e76 100644 --- a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_ash.yml +++ b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_ash.yml @@ -1,7 +1,7 @@ - type: entity id: ActionHereticJaunt - name: Ashen Passage - description: A short range spell that allows you to pass unimpeded through walls. + name: action-heretic-ash-passage-name + description: action-heretic-ash-passage-desc categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -17,8 +17,8 @@ - type: entity id: ActionHereticVolcanoBlast - name: Volcanic Blast - description: Charge up a blast of fire that chains between nearby targets, setting them ablaze. + name: action-heretic-ash-blast-name + description: action-heretic-ash-blast-desc categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -34,8 +34,8 @@ - type: entity id: ActionHereticNightwatcherRebirth - name: Nightwatcher's Rebirth - description: A spell that extinguishes you and drains nearby heathens engulfed in flames of their life force. + name: action-heretic-ash-rebirth-name + description: action-heretic-ash-rebirth-desc categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -51,8 +51,8 @@ - type: entity id: ActionHereticAscension1 - name: Oath of Flame - description: For a minute, you will passively create a ring of fire around you. + name: action-heretic-ash-oath-name + description: action-heretic-ash-oath-desc categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -68,8 +68,8 @@ - type: entity id: ActionHereticAscension2 - name: Fire Cascade - description: Heats the air around you. + name: action-heretic-ash-cascade-name + description: action-heretic-ash-cascade-desc categories: [ HideSpawnMenu ] components: - type: InstantAction diff --git a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_flesh.yml b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_flesh.yml index afc0e8a66af..2c326681290 100644 --- a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_flesh.yml +++ b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_flesh.yml @@ -1,7 +1,7 @@ - type: entity id: ActionHereticFleshSurgery - name: flesh surgery - description: Remove a random organ from someone, or heal your teammates. + name: action-heretic-flesh-surgery-name + description: action-heretic-flesh-surgery-desc components: - type: EntityTargetAction useDelay: 30 @@ -15,8 +15,8 @@ - type: entity id: ActionPolymorphHereticHorror - name: REALITY UNCOIL - description: Transform into an eldritch horror. + name: action-heretic-flesh-uncoil-name + description: action-heretic-flesh-uncoil-desc components: - type: InstantAction useDelay: 60 diff --git a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_void.yml b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_void.yml index 3d9547ac32d..d0cc0d1d756 100644 --- a/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_void.yml +++ b/Resources/Prototypes/ADT/Heretic/Actions/Heretic/path_void.yml @@ -1,7 +1,7 @@ - type: entity id: ActionHereticVoidBlast - name: Void Blast - description: Fire off a cone of ice in front of you. + name: action-heretic-void-blast-name + description: action-heretic-void-blast-desc categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -17,8 +17,8 @@ - type: entity id: ActionHereticVoidPhase - name: Void Phase - description: Shift through the void, knocking down everyone around you. + name: action-heretic-void-phase-name + description: action-heretic-void-phase-desc categories: [ HideSpawnMenu ] components: - type: WorldTargetAction @@ -36,8 +36,8 @@ - type: entity id: ActionHereticVoidPull - name: Void Pull - description: Pull and damage nearby heathens. + name: action-heretic-void-pull-name + description: action-heretic-void-pull-desc categories: [ HideSpawnMenu ] components: - type: InstantAction diff --git a/Resources/Prototypes/ADT/Heretic/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/ADT/Heretic/Entities/Clothing/OuterClothing/armor.yml index f467c3753c3..54af67e0736 100644 --- a/Resources/Prototypes/ADT/Heretic/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/ADT/Heretic/Entities/Clothing/OuterClothing/armor.yml @@ -17,6 +17,7 @@ Piercing: 0.50 Heat: 0.50 Caustic: 0.50 + staminaModifier: 0.5 - type: ExplosionResistance damageCoefficient: 0.25 - type: ToggleableClothing diff --git a/Resources/Prototypes/ADT/Heretic/Entities/Objects/Weapons/Melee/heretic_blades.yml b/Resources/Prototypes/ADT/Heretic/Entities/Objects/Weapons/Melee/heretic_blades.yml index 4750601e04a..31ba63ac2c7 100644 --- a/Resources/Prototypes/ADT/Heretic/Entities/Objects/Weapons/Melee/heretic_blades.yml +++ b/Resources/Prototypes/ADT/Heretic/Entities/Objects/Weapons/Melee/heretic_blades.yml @@ -15,6 +15,8 @@ damage: types: Slash: 17.5 + soundHit: + path: /Audio/Weapons/bladeslice.ogg - type: Item size: Normal sprite: ADT/Heretic/Blades/blade_blade-inhand.rsi @@ -40,6 +42,8 @@ types: Heat: 10 Slash: 7.5 + soundHit: + path: /Audio/Weapons/bladeslice.ogg - type: entity parent: HereticBladeBase @@ -112,3 +116,5 @@ types: Cold: 10 Slash: 7.5 + soundHit: + path: /Audio/Weapons/bladeslice.ogg diff --git a/Resources/Prototypes/ADT/Heretic/Entities/Structures/Specific/Heretic/ritual_rune.yml b/Resources/Prototypes/ADT/Heretic/Entities/Structures/Specific/Heretic/ritual_rune.yml index d24300b0bad..a543dfcddbe 100644 --- a/Resources/Prototypes/ADT/Heretic/Entities/Structures/Specific/Heretic/ritual_rune.yml +++ b/Resources/Prototypes/ADT/Heretic/Entities/Structures/Specific/Heretic/ritual_rune.yml @@ -11,6 +11,10 @@ sprite: ADT/Heretic/ritual_rune.rsi state: icon - type: Clickable + - type: UserInterface + interfaces: + enum.HereticRitualRuneUiKey.Key: + type: HereticRitualRuneBoundUserInterface - type: entity id: HereticRuneRitualDrawAnimation diff --git a/Resources/Prototypes/ADT/Heretic/Heretic/heretic_knowledge.yml b/Resources/Prototypes/ADT/Heretic/Heretic/heretic_knowledge.yml index b1817559e5e..68ad60b4e74 100644 --- a/Resources/Prototypes/ADT/Heretic/Heretic/heretic_knowledge.yml +++ b/Resources/Prototypes/ADT/Heretic/Heretic/heretic_knowledge.yml @@ -16,17 +16,11 @@ - AmberFocus - type: hereticKnowledge - id: CodexCicatrix - # ritualPrototypes: - -- type: hereticKnowledge - id: CloakOfShadow - # actionPrototypes: - -- type: hereticKnowledge - id: RitualOfKnowledge + id: LivingHeart ritualPrototypes: - - RitualOfKnowledge + - LivingHeart + actionPrototypes: + - ActionHereticLivingHeart # ash path - type: hereticKnowledge @@ -43,6 +37,8 @@ - type: hereticKnowledge id: AshenShift + path: Ash + sideKnowledge: true stage: 3 actionPrototypes: - ActionHereticJaunt @@ -61,6 +57,8 @@ - type: hereticKnowledge id: MaskOfMadness + path: Ash + sideKnowledge: true stage: 6 ritualPrototypes: - MaskOfMadness @@ -72,6 +70,8 @@ - type: hereticKnowledge id: NightwatcherRebirth + path: Ash + sideKnowledge: true stage: 8 actionPrototypes: - ActionHereticNightwatcherRebirth @@ -106,6 +106,8 @@ - type: hereticKnowledge id: ImperfectRitual + path: Flesh + sideKnowledge: true stage: 3 ritualPrototypes: - ImperfectRitual @@ -124,6 +126,8 @@ - type: hereticKnowledge id: RawRitual + path: Flesh + sideKnowledge: true stage: 6 ritualPrototypes: - RawRitual @@ -135,6 +139,8 @@ - type: hereticKnowledge id: LonelyRitual + path: Flesh + sideKnowledge: true stage: 8 ritualPrototypes: - LonelyRitual @@ -168,6 +174,8 @@ - type: hereticKnowledge id: AristocratWay + path: Void + sideKnowledge: true stage: 3 event: !type:HereticAristocratWayEvent @@ -185,6 +193,8 @@ - type: hereticKnowledge id: VoidPhase + path: Void + sideKnowledge: true stage: 6 actionPrototypes: - ActionHereticVoidPhase @@ -196,6 +206,8 @@ - type: hereticKnowledge id: VoidPull + path: Void + sideKnowledge: true stage: 8 actionPrototypes: - ActionHereticVoidPull @@ -214,8 +226,15 @@ event: !type:HereticAscensionVoidEvent # side paths +- type: hereticKnowledge + id: RitualOfKnowledge + sideKnowledge: true + ritualPrototypes: + - RitualOfKnowledge + - type: hereticKnowledge id: ArmorerRitual + sideKnowledge: true stage: 3 ritualPrototypes: - ArmorerRitual diff --git a/Resources/Prototypes/ADT/Heretic/Heretic/heretic_rituals.yml b/Resources/Prototypes/ADT/Heretic/Heretic/heretic_rituals.yml index 6b503653915..d408c7c5c49 100644 --- a/Resources/Prototypes/ADT/Heretic/Heretic/heretic_rituals.yml +++ b/Resources/Prototypes/ADT/Heretic/Heretic/heretic_rituals.yml @@ -1,23 +1,49 @@ -# basic +### basic + +# Heartbeat of the Mansus (sacrifice) - type: hereticRitual id: Sacrifice name: heretic-ritual-basic-sacrifice + icon: + sprite: Mobs/Species/Human/organs.rsi + state: heart-on customBehaviors: - !type:RitualSacrificeBehavior + onlyTargets: true +# Amber Focus - type: hereticRitual id: AmberFocus name: heretic-ritual-basic-focus + icon: + sprite: ADT/Heretic/amber_focus.rsi + state: icon requiredTags: GlassShard: 1 Eyes: 1 output: ClothingNeckAmberFocus: 1 -# ash path +# Relentless Heartbeat (sacrifice target reroll) +- type: hereticRitual + id: LivingHeart + name: heretic-ritual-basic-heart + requiredTags: + Heart: 1 + Pen: 1 + Paper: 1 + outputEvent: !type:EventHereticRerollTargets + + + +### ash path + - type: hereticRitual id: BladeAsh name: heretic-ritual-ash-blade + icon: + sprite: ADT/Heretic/Blades/blade_ash.rsi + state: icon requiredTags: Knife: 1 Matchstick: 1 @@ -27,6 +53,9 @@ - type: hereticRitual id: MaskOfMadness name: heretic-ritual-ash-mask + icon: + sprite: ADT/Heretic/mad_mask.rsi + state: icon requiredTags: GasMask: 1 Candle: 4 @@ -37,6 +66,9 @@ - type: hereticRitual id: AscensionAsh name: heretic-ritual-ash-ascend + icon: + sprite: ADT/Heretic/abilities_heretic.rsi + state: ashlord_rite2 customBehaviors: - !type:RitualAshAscendBehavior min: 3 @@ -44,10 +76,16 @@ outputKnowledge: AshlordRite outputEvent: !type:EventHereticAscension -# flesh path + + +### flesh path + - type: hereticRitual id: BladeFlesh name: heretic-ritual-flesh-blade + icon: + sprite: ADT/Heretic/Blades/blade_flesh.rsi + state: icon customBehaviors: - !type:RitualReagentPuddleBehavior reagent: Blood @@ -59,14 +97,20 @@ - type: hereticRitual id: ImperfectRitual name: heretic-ritual-flesh-ghoul + icon: + sprite: Mobs/Species/Skeleton/parts.rsi + state: full customBehaviors: - !type:RitualMuteGhoulifyBehavior - requiredEntityNames: - poppy: 1 + requiredTags: + Poppy: 1 - type: hereticRitual id: RawRitual name: heretic-ritual-flesh-prophet + icon: + sprite: ADT/Heretic/eldritch_mobs.rsi + state: raw_prophet customBehaviors: - !type:RitualReagentPuddleBehavior reagent: Blood @@ -79,6 +123,9 @@ - type: hereticRitual id: LonelyRitual name: heretic-ritual-flesh-stalker + icon: + sprite: ADT/Heretic/eldritch_mobs.rsi + state: stalker customBehaviors: - !type:RitualReagentPuddleBehavior reagent: Blood @@ -93,6 +140,9 @@ - type: hereticRitual id: AscensionFlesh name: heretic-ritual-flesh-ascend + icon: + sprite: ADT/Heretic/abilities_heretic.rsi + state: final_hymn customBehaviors: - !type:RitualSacrificeBehavior min: 4 @@ -100,10 +150,16 @@ outputKnowledge: PriestFinalHymn outputEvent: !type:EventHereticAscension -# void path + + +### void path + - type: hereticRitual id: BladeVoid name: heretic-ritual-void-blade + icon: + sprite: ADT/Heretic/Blades/blade_void.rsi + state: icon customBehaviors: - !type:RitualTemperatureBehavior minThreshold: 0 # ~= -1 celcius. or sub zero temperatures @@ -115,6 +171,9 @@ - type: hereticRitual id: AscensionVoid name: heretic-ritual-void-ascend + icon: + sprite: Interface/Alerts/temperature.rsi + state: cold3 customBehaviors: - !type:RitualSacrificeBehavior min: 3 @@ -124,16 +183,25 @@ outputKnowledge: WaltzAtTheEndOfTime outputEvent: !type:EventHereticAscension -# side path + + +### side knowledge + - type: hereticRitual id: RitualOfKnowledge name: heretic-ritual-side-knowledge + icon: + sprite: Objects/Misc/books.rsi + state: book_icon customBehaviors: - !type:RitualKnowledgeBehavior - type: hereticRitual id: ArmorerRitual name: heretic-ritual-side-armor + icon: + sprite: ADT/Heretic/eldritch_armor.rsi + state: icon requiredTags: Table: 1 GasMask: 1 diff --git a/Resources/Prototypes/ADT/Heretic/radio_channels.yml b/Resources/Prototypes/ADT/Heretic/radio_channels.yml index 1ce51f47345..73f3afe0255 100644 --- a/Resources/Prototypes/ADT/Heretic/radio_channels.yml +++ b/Resources/Prototypes/ADT/Heretic/radio_channels.yml @@ -1,7 +1,7 @@ - type: radioChannel id: Mansus name: chat-radio-mansus - keycode: 'z' + keycode: 'з' frequency: 80085 color: "#237500" longRange: true \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml index cc7ed82bd9b..0f73d674734 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml @@ -202,6 +202,7 @@ tags: - ClownMask - WhitelistChameleon + - Mask # goob-edit - type: HideLayerClothing slots: - Snout diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index bec4dd73653..232f6e5a67f 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -199,6 +199,8 @@ # Goobstation - changelings & heretics enum.StoreUiKey.Key: type: StoreBoundUserInterface + enum.HereticLivingHeartKey.Key: # goob edit - heretics + type: LivingHeartMenuBoundUserInterface - type: Puller - type: Speech speechSounds: Alto diff --git a/Resources/Textures/ADT/Heretic/abilities_heretic.rsi/living_heart.png b/Resources/Textures/ADT/Heretic/abilities_heretic.rsi/living_heart.png new file mode 100644 index 00000000000..d9c7a52e554 Binary files /dev/null and b/Resources/Textures/ADT/Heretic/abilities_heretic.rsi/living_heart.png differ diff --git a/Resources/Textures/ADT/Heretic/abilities_heretic.rsi/meta.json b/Resources/Textures/ADT/Heretic/abilities_heretic.rsi/meta.json index 4d82d395496..cc6869ab3c2 100644 --- a/Resources/Textures/ADT/Heretic/abilities_heretic.rsi/meta.json +++ b/Resources/Textures/ADT/Heretic/abilities_heretic.rsi/meta.json @@ -84,6 +84,9 @@ }, { "name": "immovable_rod" + }, + { + "name": "living_heart" } ] }