From a092bad476d28de67266f1edd121348a97a2f548 Mon Sep 17 00:00:00 2001 From: Rainfey Date: Tue, 9 Jan 2024 12:58:35 +0000 Subject: [PATCH 01/67] Initial Commit --- Content.Server/Vampire/VampireSystem.cs | 396 ++++++++++++++++++ .../Vampire/Components/CoffinComponent.cs | 17 + .../Vampire/Components/VampireComponent.cs | 76 ++++ .../Components/VampireHealingComponent.cs | 20 + Content.Shared/Vampire/VampireEvents.cs | 18 + Resources/Locale/en-US/Vampires/vampires.ftl | 12 + Resources/Prototypes/Actions/vampire.yml | 13 + .../Structures/Storage/Crates/crates.yml | 7 + .../actions_vampire.rsi/drainblood.png | Bin 0 -> 1390 bytes .../Actions/actions_vampire.rsi/meta.json | 14 + 10 files changed, 573 insertions(+) create mode 100644 Content.Server/Vampire/VampireSystem.cs create mode 100644 Content.Shared/Vampire/Components/CoffinComponent.cs create mode 100644 Content.Shared/Vampire/Components/VampireComponent.cs create mode 100644 Content.Shared/Vampire/Components/VampireHealingComponent.cs create mode 100644 Content.Shared/Vampire/VampireEvents.cs create mode 100644 Resources/Locale/en-US/Vampires/vampires.ftl create mode 100644 Resources/Prototypes/Actions/vampire.yml create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/drainblood.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs new file mode 100644 index 00000000000..104ee9b254f --- /dev/null +++ b/Content.Server/Vampire/VampireSystem.cs @@ -0,0 +1,396 @@ +using Content.Server.Actions; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Rotting; +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Interaction; +using Content.Server.Nutrition.EntitySystems; +using Content.Server.Popups; +using Content.Server.Storage.Components; +using Content.Server.Storage.EntitySystems; +using Content.Server.Temperature.Components; +using Content.Shared.Atmos.Rotting; +using Content.Shared.Buckle.Components; +using Content.Shared.Chemistry.Components; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.FixedPoint; +using Content.Shared.Humanoid; +using Content.Shared.Interaction; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Vampire; +using Content.Shared.Vampire.Components; +using Robust.Server.Containers; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.Server.Vampire; + +public sealed partial class VampireSystem : EntitySystem +{ + [Dependency] private readonly SolutionContainerSystem _solution = default!; + [Dependency] private readonly FoodSystem _food = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly ContainerSystem _container = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly IMapManager _mapMan = default!; + [Dependency] private readonly MapSystem _mapSystem = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly EntityStorageSystem _entityStorage = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly ActionsSystem _action = default!; + [Dependency] private readonly BloodstreamSystem _blood = default!; + [Dependency] private readonly RottingSystem _rotting = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnInteractWithHumanoid, before: new[] { typeof(InteractionPopupSystem) }); + SubscribeLocalEvent(DrinkDoAfter); + + SubscribeLocalEvent(OnInsertedIntoContainer); + SubscribeLocalEvent(OnRemovedFromContainer); + SubscribeLocalEvent(OnVampireStateChanged); + SubscribeLocalEvent(OnUsePower); + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) + { + if (component.FangsExtended && args.IsInDetailsRange) + args.AddMarkup($"{Environment.NewLine}{Loc.GetString("vampire-fangs-extended-examine")}", 3); + } + + + private void OnUsePower(EntityUid uid, VampireComponent component, VampireUsePowerEvent arg) + { + Entity vampire = (uid, component); + + if (!component.VampirePowers.TryGetValue(arg.Type, out var def)) + return; + + if (def.ActivationCost > 0) + { + if (GetBlood(vampire) < def.ActivationCost) + { + _popup.PopupEntity(Loc.GetString("vampire-not-enough-blood"), uid, uid, Shared.Popups.PopupType.MediumCaution); + return; + } + + AddBlood(vampire, -def.ActivationCost); + } + + if (def.ActivationEffect != null) + Spawn(def.ActivationEffect, _transform.GetMapCoordinates(Transform(vampire.Owner))); + + if (def.ActivationSound != null) + _audio.PlayPvs(def.ActivationSound, uid); + + switch (arg.Type) + { + case VampirePower.ToggleFangs: + { + ToggleFangs(vampire); + break; + } + case VampirePower.DeathsEmbrace: + { + TryMoveToCoffin(vampire); + break; + } + + default: + break; + } + } + + /// + /// Ensure the player has a blood storage container, is immune to pressure, o2 and low temperatures + /// + private void OnComponentInit(EntityUid uid, VampireComponent component, ComponentInit args) + { + _solution.EnsureSolution(uid, component.BloodContainer); + RemComp(uid); + RemComp(uid); + + if (TryComp(uid, out var temperatureComponent)) + temperatureComponent.ColdDamageThreshold = 0; + + component.SpaceDamage = new(_prototypeManager.Index("Burn"), FixedPoint2.New(5)); + + UpdateAbilities((uid, component)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator< VampireComponent>(); + while (query.MoveNext(out var vampireUid, out var vampireComponent)) + { + if (TryComp(vampireUid, out var vampireHealingComponent)) + { + if (vampireHealingComponent.NextHealTick > 0) + { + vampireHealingComponent.NextHealTick -= frameTime; + continue; + } + + vampireHealingComponent.NextHealTick = vampireHealingComponent.HealTickInterval.TotalSeconds; + DoCoffinHeal(vampireUid, vampireHealingComponent); + } + + if (IsInSpace(vampireUid)) + { + if (vampireComponent.NextSpaceDamageTick > 0) + { + vampireComponent.NextSpaceDamageTick -= frameTime; + } + else + { + vampireComponent.NextSpaceDamageTick = vampireComponent.SpaceDamageInterval.TotalSeconds; + DoSpaceDamage(vampireUid, vampireComponent); + } + } + } + } + + private void UpdateAbilities(Entity vampire) + { + var availableBlood = GetBlood(vampire); + foreach (var power in vampire.Comp.VampirePowers) + { + var def = power.Value; + if (vampire.Comp.UnlockedPowers.Contains(power.Key)) + continue; + + if (def.UnlockCost <= availableBlood) + UnlockAbility(vampire, power.Key); + } + } + private void UnlockAbility(Entity vampire, VampirePower power) + { + vampire.Comp.UnlockedPowers.Add(power); + if (vampire.Comp.VampirePowers.TryGetValue(power, out var def) && def.ActionPrototype != null) + { + _action.AddAction(vampire.Owner, ref def.Action, def.ActionPrototype, vampire.Owner); + } + //Play unlock sound + //Show popup + } + + #region Deaths Embrace + private void OnVampireStateChanged(EntityUid uid, VampireComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + OnUsePower(uid, component, new() { Type = VampirePower.DeathsEmbrace }); + } + private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, EntGotInsertedIntoContainerMessage args) + { + if (TryComp(args.Container.Owner, out var coffinComp)) + { + component.HomeCoffin = args.Container.Owner; + var comp = new VampireHealingComponent { Damage = coffinComp.Damage }; + AddComp(args.Entity, comp); + } + } + private void OnRemovedFromContainer(EntityUid uid, VampireComponent component, EntGotRemovedFromContainerMessage args) + { + RemCompDeferred(args.Entity); + } + private bool TryMoveToCoffin(Entity vampire) + { + if (!vampire.Comp.HomeCoffin.HasValue) + return false; + + if (!TryComp(vampire.Comp.HomeCoffin, out var coffinEntityStorage)) + return false; + + if (!_entityStorage.CanInsert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage)) + return false; + + _entityStorage.CloseStorage(vampire.Comp.HomeCoffin.Value, coffinEntityStorage); + + return _entityStorage.Insert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage); + } + #endregion + + private void DoCoffinHeal(EntityUid vampireUid, VampireHealingComponent healingComponent) + { + if (!_container.TryGetOuterContainer(vampireUid, Transform(vampireUid), out var container)) + return; + + if (!HasComp(container.Owner)) + return; + + _damageableSystem.TryChangeDamage(vampireUid, healingComponent.Damage, true, origin: container.Owner); + + if (!TryComp(vampireUid, out var mobStateComponent)) + return; + + if (!_mobState.IsDead(vampireUid, mobStateComponent)) + return; + + if (!_mobThreshold.TryGetThresholdForState(vampireUid, MobState.Dead, out var threshold)) + return; + + if (!TryComp(vampireUid, out var damageableComponent)) + return; + + if (damageableComponent.TotalDamage < threshold * 0.75) + { + _mobState.ChangeMobState(vampireUid, MobState.Critical, mobStateComponent, container.Owner); + } + } + + #region Blood Drinking + private void ToggleFangs(Entity vampire) + { + vampire.Comp.FangsExtended = !vampire.Comp.FangsExtended; + var popupText = Loc.GetString(vampire.Comp.FangsExtended ? "vampire-fangs-extended" : "vampire-fangs-retracted"); + _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); + } + + private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) + { + if (args.Handled) + return; + + if (args.Target == args.User) + return; + + args.Handled = TryDrink(args.Target, args.User); + } + + private bool TryDrink(EntityUid target, EntityUid drinker, VampireComponent? vampireComponent = null) + { + if (!Resolve(drinker, ref vampireComponent, false)) + return false; + + //Do a precheck + if (!vampireComponent.FangsExtended) + return false; + + if (!_interaction.InRangeUnobstructed(drinker, target, popup: true)) + return false; + + if (_food.IsMouthBlocked(target, drinker)) + return false; + + if (_rot) + var doAfterEventArgs = new DoAfterArgs(EntityManager, drinker, vampireComponent.BloodDrainDelay, + new VampireDrinkBloodEvent(), + eventTarget: drinker, + target: target, + used: target) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + MovementThreshold = 0.01f, + DistanceThreshold = 1.0f, + NeedHand = false, + Hidden = true + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + return true; + } + + private void DrinkDoAfter(Entity entity, ref VampireDrinkBloodEvent args) + { + if (!args.Target.HasValue) + return; + + if (_food.IsMouthBlocked(args.Target.Value, entity)) + return; + + if (!entity.Comp.FangsExtended) + return; + + if (!TryComp(args.Target, out var targetBloodstream)) + return; + + if (targetBloodstream.BloodSolution == null) + return; + + if (!entity.Comp.VampirePowers.TryGetValue(VampirePower.DrinkBlood, out var def)) + return; + + //Ensure there is enough blood to drain + if (targetBloodstream.BloodSolution?.Comp.Solution.Volume < def.ActivationCost) + { + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-empty"), entity.Owner, entity.Owner, Shared.Popups.PopupType.SmallCaution); + return; + } + + if (!_blood.TryModifyBloodLevel(args.Target.Value, def.ActivationCost)) + return; + + AddBlood(entity, -def.ActivationCost); + + _audio.PlayPvs(def.ActivationSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); + + //Update abilities, add new unlocks + UpdateAbilities(entity); + + args.Repeat = true; + } + #endregion + + private void DoSpaceDamage(EntityUid vampireUid, VampireComponent component) + { + _damageableSystem.TryChangeDamage(vampireUid, component.SpaceDamage, true, origin: vampireUid); + } + private bool IsInSpace(EntityUid vampireUid) + { + var vampireTransform = Transform(vampireUid); + var vampirePosition = _transform.GetMapCoordinates(vampireTransform); + + if (!_mapMan.TryFindGridAt(vampirePosition, out _, out var grid)) + return true; + + if (!_mapSystem.TryGetTileRef(vampireUid, grid, vampireTransform.Coordinates, out var tileRef)) + return true; + + return tileRef.Tile.IsEmpty; + } + + private FixedPoint2? GetBlood(Entity vampire) + { + if (!_solution.TryGetSolution(vampire.Owner, vampire.Comp.BloodContainer, out var vampireBloodContainer)) + return null; + + return vampireBloodContainer.Value.Comp.Solution.Volume; + } + private void AddBlood(Entity vampire, Solution solution) { + AddBlood(vampire, solution.Volume); + } + private void AddBlood(Entity vampire, FixedPoint2 quantity) + { + if (!_solution.TryGetSolution(vampire.Owner, vampire.Comp.BloodContainer, out var vampireBloodContainer)) + return; + + //Convert everything into 'blood' + //No point restricting people to only feeding on people with their blood type + var solution = new Solution("blood", quantity); + _solution.TryAddSolution(vampireBloodContainer.Value, solution); + } +} diff --git a/Content.Shared/Vampire/Components/CoffinComponent.cs b/Content.Shared/Vampire/Components/CoffinComponent.cs new file mode 100644 index 00000000000..b957304649c --- /dev/null +++ b/Content.Shared/Vampire/Components/CoffinComponent.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Content.Shared.Damage; + +namespace Content.Shared.Vampire.Components +{ + [RegisterComponent] + public sealed partial class CoffinComponent : Component + { + [DataField("damage", required: true)] + [ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier Damage = default!; + } +} diff --git a/Content.Shared/Vampire/Components/VampireComponent.cs b/Content.Shared/Vampire/Components/VampireComponent.cs new file mode 100644 index 00000000000..41024f476b0 --- /dev/null +++ b/Content.Shared/Vampire/Components/VampireComponent.cs @@ -0,0 +1,76 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Shared.Audio; +using Robust.Shared.Serialization; +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared.Vampire.Components; + +[RegisterComponent] +public sealed partial class VampireComponent : Component +{ + public readonly string BloodContainer = "blood@vampire"; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool FangsExtended = false; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier SpaceDamage = default!; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan SpaceDamageInterval = TimeSpan.FromSeconds(1); + + public double NextSpaceDamageTick = 0f; + + public EntityUid? HomeCoffin = default!; + + //Abilities + public HashSet UnlockedPowers = new(); + + //Blood Drinking + public TimeSpan BloodDrainDelay = TimeSpan.FromSeconds(2); + public readonly SoundSpecifier DrinkSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); + + + public FrozenDictionary VampirePowers = new Dictionary + { + { VampirePower.ToggleFangs, new("ActionToggleFangs", null, null, 0, 0) }, + { VampirePower.Glare, new("ActionVampireGlare", null, null, 0, 0) }, + { VampirePower.DeathsEmbrace, new(null, "Smoke", new SoundPathSpecifier("/Audio/Effects/explosionsmallfar.ogg"), 100, 200) }, + { VampirePower.DrinkBlood, new(null, null, new SoundPathSpecifier("/Audio/Items/drink.ogg"), 0, -30) } //Activation cost is how much we drain from the victim + }.ToFrozenDictionary(); +} + +public sealed class VampirePowerDef +{ + public readonly string? ActionPrototype; + public readonly string? ActivationEffect; + public readonly SoundSpecifier? ActivationSound; + public readonly int ActivationCost; + public readonly int UnlockCost; + public EntityUid? Action; + + public VampirePowerDef(string? actionPrototype, string? activationEffect, SoundSpecifier? activationSound, int unlockCost, int activationCost) + { + this.ActionPrototype = actionPrototype; + this.ActivationEffect = activationEffect; + this.ActivationSound = activationSound; + this.UnlockCost = unlockCost; + this.ActivationCost = activationCost; + } +} + +[Serializable, NetSerializable] +public enum VampirePower : Byte +{ + ToggleFangs, + Glare, + DeathsEmbrace, + DrinkBlood +} diff --git a/Content.Shared/Vampire/Components/VampireHealingComponent.cs b/Content.Shared/Vampire/Components/VampireHealingComponent.cs new file mode 100644 index 00000000000..dd08dd5f28f --- /dev/null +++ b/Content.Shared/Vampire/Components/VampireHealingComponent.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Content.Shared.Damage; + +namespace Content.Shared.Vampire.Components +{ + [RegisterComponent] + public sealed partial class VampireHealingComponent : Component + { + [ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier Damage = default!; + + public double NextHealTick = 0; + + public TimeSpan HealTickInterval = TimeSpan.FromSeconds(1); + } +} diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs new file mode 100644 index 00000000000..b5000f32ac0 --- /dev/null +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -0,0 +1,18 @@ +using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Content.Shared.Vampire.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Vampire; + +public sealed partial class VampireUsePowerEvent : InstantActionEvent +{ + [DataField("type")] + public VampirePower Type; +}; + +[Serializable, NetSerializable] +public sealed partial class VampireDrinkBloodEvent : DoAfterEvent +{ + public override DoAfterEvent Clone() => this; +} diff --git a/Resources/Locale/en-US/Vampires/vampires.ftl b/Resources/Locale/en-US/Vampires/vampires.ftl new file mode 100644 index 00000000000..21b182d955c --- /dev/null +++ b/Resources/Locale/en-US/Vampires/vampires.ftl @@ -0,0 +1,12 @@ +vampires-title = Vampires + +vampire-fangs-extended-examine = You see a glint of sharp teeth +vampire-fangs-extended = You extend your fangs +vampire-fangs-retracted = You retract your fangs + +vampire-blooddrink-empty = This body is devoid of blood +vampire-blooddrink-rotted = Their body is rotting and their blood tainted + +vampire-startlight-burning = You feel your skin burn in the light of a thousand suns + +vampire-not-enough-blood = You dont have enough blood \ No newline at end of file diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml new file mode 100644 index 00000000000..3d3d3ca7601 --- /dev/null +++ b/Resources/Prototypes/Actions/vampire.yml @@ -0,0 +1,13 @@ +- type: entity + id: ActionToggleFangs + name: Toggle Fangs + description: Extend or retract your fangs. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: drainbloood + event: !type:VampireUsePowerEvent + Type: ToggleFangs \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml index 16792b0be3d..16ca8114bad 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml @@ -470,6 +470,13 @@ max: 4 - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Coffin + damage: + groups: + Burn: -2 + Toxin: -2 + Airloss: -2 + Brute: -2 - type: Construction graph: CrateCoffin node: cratecoffin diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/drainblood.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/drainblood.png new file mode 100644 index 0000000000000000000000000000000000000000..1475c96bb8b3d131813766e3f170159a0ce84fd4 GIT binary patch literal 1390 zcmV-!1(EuRP)EX>4Tx04R}tkvmAkP!xv$riu@$4rUN>$WWauh>AE$6^me@v=v%)FuCaqnlvOS zE{=k0!NJF3)xpJCR|i)?5PX0*J2)x2NQvhrg%&X$xZIEbp8x0Ga{-}VW}4M80cg5y zCSy@4lU*o ztDLtuYvn3y-jlyDl+#yIT&FdH7?u!60umHdQ9>CuBDCwISV+=&)W<*U`V-_*$W;O( z#{w$QAiI9>KlnXcD?c^qC556u=ZoWfi~?P|K%?e3-^Y&AI01ssz?I(eSL(p*C+W48 z7Cr(7wt zj1?$*-RIri?Q{FLr#ZhL0wQvHe$DD400006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=m7};DmvfHK<@wm02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00TxzL_t(o!|j(bXd6ishCfSdB*X-n$QZOyAZ%QO3mgW6 zFjf&93>TszMM7Kzju5chD-ABLaA9zHt%4u~Qm8pFQK5h!xUer63?bk&u@^&>6_`_y z$+FR&R`wOi?9EDN$-bpF`oagxbouK}NENt7R;y8Gohh>FVinMR`#UIr?T(<9>O=;+Jm=DLBo!Z0!w$2oNdw9`(MDYi8Y z*LB&5Mt$E_JPx*v)ocbb5$YcBJde$<}Umq8L5~DrP z9$dS|p1JS$0qPtuF`o^PuP(JHm%Cm@@?iq-;?-^c5g}hTVZgG!T+0_%;*`tfp+E+% wPgjR_HfN7tyt*CCL`c})-uBy){C@!a4P9u7KS1JBS^xk507*qoM6N<$f@=AIk^lez literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json new file mode 100644 index 00000000000..205a0b6aa06 --- /dev/null +++ b/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "copyright" : "Taken from https://github.com/goonstation/goonstation at https://github.com/goonstation/goonstation/commit/4059e4be90832b02b1228b1bee3db342094e4f1e", + "license" : "CC-BY-SA-3.0", + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "drainblood" + } + ] +} \ No newline at end of file From 76517c5fd4d0fddaa750854d3995a2ace06854fe Mon Sep 17 00:00:00 2001 From: Rainfey Date: Thu, 11 Jan 2024 09:59:36 +0000 Subject: [PATCH 02/67] Unfinished --- .../Body/Systems/MetabolizerSystem.cs | 5 + Content.Server/Body/Systems/StomachSystem.cs | 6 + Content.Server/Vampire/VampireSystem.cs | 369 +++++++++++++----- .../Vampire/Components/VampireComponent.cs | 76 ++-- Content.Shared/Vampire/VampireEvents.cs | 9 +- Resources/Audio/Effects/Vampire/glare.ogg | Bin 0 -> 5592 bytes .../Audio/Effects/Vampire/screech_tone.ogg | Bin 0 -> 18617 bytes Resources/Locale/en-US/Vampires/vampires.ftl | 5 +- Resources/Prototypes/Actions/vampire.yml | 85 +++- .../Chemistry/metabolizer_types.yml | 4 + .../Prototypes/Chemistry/reactive_groups.yml | 3 + Resources/Prototypes/Reagents/medicine.yml | 28 ++ Resources/Prototypes/Vampire/powers.yml | 31 ++ .../actions_vampire.rsi/deathsembrace.png | Bin 0 -> 983 bytes .../actions_vampire.rsi/drainblood.png | Bin 1390 -> 912 bytes .../Actions/actions_vampire.rsi/glare.png | Bin 0 -> 1391 bytes .../Actions/actions_vampire.rsi/meta.json | 9 + .../Actions/actions_vampire.rsi/screech.png | Bin 0 -> 1628 bytes 18 files changed, 506 insertions(+), 124 deletions(-) create mode 100644 Resources/Audio/Effects/Vampire/glare.ogg create mode 100644 Resources/Audio/Effects/Vampire/screech_tone.ogg create mode 100644 Resources/Prototypes/Vampire/powers.yml create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/deathsembrace.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/glare.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/screech.png diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index e5f604f70df..89b3e23008c 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -64,6 +64,11 @@ private void OnApplyMetabolicMultiplier(EntityUid uid, MetabolizerComponent comp component.AccumulatedFrametime = component.UpdateFrequency; } + public void SetMetabolizerTypes(MetabolizerComponent component, HashSet? metabolizerTypes) + { + component.MetabolizerTypes = metabolizerTypes; + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Server/Body/Systems/StomachSystem.cs b/Content.Server/Body/Systems/StomachSystem.cs index 4c11244c379..88a3cc002e9 100644 --- a/Content.Server/Body/Systems/StomachSystem.cs +++ b/Content.Server/Body/Systems/StomachSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Body.Organ; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Whitelist; using Robust.Shared.Utility; namespace Content.Server.Body.Systems @@ -123,5 +124,10 @@ public bool TryTransferSolution(EntityUid uid, Solution solution, return true; } + + public void SetSpecialDigestible(StomachComponent component, EntityWhitelist? whitelist) + { + component.SpecialDigestible = whitelist; + } } } diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 104ee9b254f..bb3d697dd4d 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -1,31 +1,40 @@ using Content.Server.Actions; using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Rotting; +using Content.Server.Bible.Components; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Flash; using Content.Server.Interaction; using Content.Server.Nutrition.EntitySystems; using Content.Server.Popups; +using Content.Server.Speech.Components; using Content.Server.Storage.Components; using Content.Server.Storage.EntitySystems; using Content.Server.Temperature.Components; using Content.Shared.Atmos.Rotting; -using Content.Shared.Buckle.Components; +using Content.Shared.Body.Components; using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Cuffs.Components; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.FixedPoint; +using Content.Shared.Flash; using Content.Shared.Humanoid; using Content.Shared.Interaction; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Stunnable; using Content.Shared.Vampire; using Content.Shared.Vampire.Components; +using Content.Shared.Weapons.Melee; +using Content.Shared.Whitelist; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -33,12 +42,14 @@ using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using System.Collections.Frozen; +using System.Diagnostics; namespace Content.Server.Vampire; public sealed partial class VampireSystem : EntitySystem { - [Dependency] private readonly SolutionContainerSystem _solution = default!; [Dependency] private readonly FoodSystem _food = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; @@ -56,6 +67,18 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly ActionsSystem _action = default!; [Dependency] private readonly BloodstreamSystem _blood = default!; [Dependency] private readonly RottingSystem _rotting = default!; + [Dependency] private readonly BodySystem _body = default!; + [Dependency] private readonly StomachSystem _stomach = default!; + [Dependency] private readonly MetabolizerSystem _metabolism = default!; + [Dependency] private readonly FlashSystem _flash = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly SolutionContainerSystem _solution = default!; + + private FrozenDictionary _cachedPowers = default!; + private ReagentPrototype holyWaterReagent = default!; + private ReagentPrototype bloodReagent = default!; + private DamageSpecifier spaceDamage = default!; + public override void Initialize() { @@ -70,137 +93,188 @@ public override void Initialize() SubscribeLocalEvent(OnVampireStateChanged); SubscribeLocalEvent(OnUsePower); SubscribeLocalEvent(OnExamined); + + CachePowers(); + + holyWaterReagent = _prototypeManager.Index("HolyWater"); + bloodReagent = _prototypeManager.Index("Blood"); + spaceDamage = new(_prototypeManager.Index("Heat"), FixedPoint2.New(5)); + } + + /// + /// Convert the entity into a vampire + /// + private void OnComponentInit(EntityUid uid, VampireComponent component, ComponentInit args) + { + //_solution.EnsureSolution(uid, component.BloodContainer); + RemComp(uid); + RemComp(uid); + + if (TryComp(uid, out var temperatureComponent)) + temperatureComponent.ColdDamageThreshold = 0; + + ConvertBody(uid); + + UpdateAbilities((uid, component)); } private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) { if (component.FangsExtended && args.IsInDetailsRange) - args.AddMarkup($"{Environment.NewLine}{Loc.GetString("vampire-fangs-extended-examine")}", 3); + args.AddMarkup($"{Loc.GetString("vampire-fangs-extended-examine")}{Environment.NewLine}"); } - - private void OnUsePower(EntityUid uid, VampireComponent component, VampireUsePowerEvent arg) + private void OnUseAreaPower(EntityUid uid, VampireComponent component, VampireUseAreaPowerEvent arg) { Entity vampire = (uid, component); - if (!component.VampirePowers.TryGetValue(arg.Type, out var def)) + if (!component.UnlockedPowers.ContainsKey(arg.Type)) return; - if (def.ActivationCost > 0) - { - if (GetBlood(vampire) < def.ActivationCost) - { - _popup.PopupEntity(Loc.GetString("vampire-not-enough-blood"), uid, uid, Shared.Popups.PopupType.MediumCaution); - return; - } + if (!_cachedPowers.TryGetValue(arg.Type, out var def)) + return; - AddBlood(vampire, -def.ActivationCost); + if (def.ActivationCost > 0 && def.ActivationCost > component.AvailableBlood) + { + _popup.PopupEntity(Loc.GetString("vampire-not-enough-blood"), uid, uid, Shared.Popups.PopupType.MediumCaution); + return; } + AddBlood(vampire, -def.ActivationCost); + + //Block if we are cuffed + if (!def.UsableWhileCuffed && TryComp(uid, out var cuffable) && !cuffable.CanStillInteract) + return; + + //Block if we are stunned + if (!def.UsableWhileStunned && HasComp(uid)) + return; + + //Block if we are muzzled + if (!def.UsableWhileMuffled && TryComp(uid, out var accent) && accent.Accent.Equals("mumble")) + return; + if (def.ActivationEffect != null) Spawn(def.ActivationEffect, _transform.GetMapCoordinates(Transform(vampire.Owner))); if (def.ActivationSound != null) _audio.PlayPvs(def.ActivationSound, uid); + _action.StartUseDelay(component.UnlockedPowers[arg.Type]); + + //TODO: Rewrite when a magic system is introduced switch (arg.Type) { - case VampirePower.ToggleFangs: + case VampirePowerKey.ToggleFangs: { ToggleFangs(vampire); break; } - case VampirePower.DeathsEmbrace: + case VampirePowerKey.DeathsEmbrace: { TryMoveToCoffin(vampire); break; } - + case VampirePowerKey.Glare: + { + Glare(vampire); + break; + } + case VampirePowerKey.Screech: + { + Screech(vampire); + break; + } default: break; } } - /// - /// Ensure the player has a blood storage container, is immune to pressure, o2 and low temperatures - /// - private void OnComponentInit(EntityUid uid, VampireComponent component, ComponentInit args) - { - _solution.EnsureSolution(uid, component.BloodContainer); - RemComp(uid); - RemComp(uid); - - if (TryComp(uid, out var temperatureComponent)) - temperatureComponent.ColdDamageThreshold = 0; - - component.SpaceDamage = new(_prototypeManager.Index("Burn"), FixedPoint2.New(5)); - - UpdateAbilities((uid, component)); - } - public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityQueryEnumerator< VampireComponent>(); - while (query.MoveNext(out var vampireUid, out var vampireComponent)) + var query1 = EntityQueryEnumerator(); + while (query1.MoveNext(out var uid, out var vampireHealingComponent)) { - if (TryComp(vampireUid, out var vampireHealingComponent)) + if (vampireHealingComponent.NextHealTick <= 0) { - if (vampireHealingComponent.NextHealTick > 0) - { - vampireHealingComponent.NextHealTick -= frameTime; - continue; - } - vampireHealingComponent.NextHealTick = vampireHealingComponent.HealTickInterval.TotalSeconds; - DoCoffinHeal(vampireUid, vampireHealingComponent); + DoCoffinHeal(uid, vampireHealingComponent); } + vampireHealingComponent.NextHealTick -= frameTime; + } - if (IsInSpace(vampireUid)) + var query2 = EntityQueryEnumerator(); + while (query2.MoveNext(out var uid, out var vampireComponent)) + { + if (IsInSpace(uid)) { - if (vampireComponent.NextSpaceDamageTick > 0) - { - vampireComponent.NextSpaceDamageTick -= frameTime; - } - else + if (vampireComponent.NextSpaceDamageTick <= 0) { vampireComponent.NextSpaceDamageTick = vampireComponent.SpaceDamageInterval.TotalSeconds; - DoSpaceDamage(vampireUid, vampireComponent); + DoSpaceDamage(uid, vampireComponent); } + vampireComponent.NextSpaceDamageTick -= frameTime; } } } private void UpdateAbilities(Entity vampire) { - var availableBlood = GetBlood(vampire); - foreach (var power in vampire.Comp.VampirePowers) + foreach (var power in _cachedPowers.Values) { - var def = power.Value; - if (vampire.Comp.UnlockedPowers.Contains(power.Key)) - continue; - - if (def.UnlockCost <= availableBlood) + if (power.UnlockRequirement <= vampire.Comp.AvailableBlood) UnlockAbility(vampire, power.Key); } } - private void UnlockAbility(Entity vampire, VampirePower power) + private void UnlockAbility(Entity vampire, VampirePowerKey power) { - vampire.Comp.UnlockedPowers.Add(power); - if (vampire.Comp.VampirePowers.TryGetValue(power, out var def) && def.ActionPrototype != null) + if (vampire.Comp.UnlockedPowers.ContainsKey(power)) + return; + + if (_cachedPowers.TryGetValue(power, out var def) && def.ActionPrototype != null) { - _action.AddAction(vampire.Owner, ref def.Action, def.ActionPrototype, vampire.Owner); + var actionUid = _action.AddAction(vampire.Owner, def.ActionPrototype); + if (!actionUid.HasValue) + return; + vampire.Comp.UnlockedPowers.Add(power, actionUid.Value); } //Play unlock sound //Show popup } + private void Screech(Entity vampire) + { + var transform = Transform(vampire.Owner); + + foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Dynamic | LookupFlags.Static)) + { + if (HasComp(entity)) + return; + } + } + private void Glare(Entity vampire) + { + var transform = Transform(vampire.Owner); + + foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 2, LookupFlags.Dynamic)) + { + if (HasComp(entity)) + return; + + if (HasComp(entity)) + return; + + _flash.Flash(entity, null, null, 10, 0.8f); + } + } + #region Deaths Embrace private void OnVampireStateChanged(EntityUid uid, VampireComponent component, MobStateChangedEvent args) { if (args.NewMobState == MobState.Dead) - OnUsePower(uid, component, new() { Type = VampirePower.DeathsEmbrace }); + OnUsePower(uid, component, new() { Type = VampirePowerKey.DeathsEmbrace }); } private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, EntGotInsertedIntoContainerMessage args) { @@ -209,6 +283,7 @@ private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, component.HomeCoffin = args.Container.Owner; var comp = new VampireHealingComponent { Damage = coffinComp.Damage }; AddComp(args.Entity, comp); + _popup.PopupEntity(Loc.GetString("vampire-deathsembrace-bind"), uid, uid); } } private void OnRemovedFromContainer(EntityUid uid, VampireComponent component, EntGotRemovedFromContainerMessage args) @@ -230,8 +305,6 @@ private bool TryMoveToCoffin(Entity vampire) return _entityStorage.Insert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage); } - #endregion - private void DoCoffinHeal(EntityUid vampireUid, VampireHealingComponent healingComponent) { if (!_container.TryGetOuterContainer(vampireUid, Transform(vampireUid), out var container)) @@ -259,6 +332,7 @@ private void DoCoffinHeal(EntityUid vampireUid, VampireHealingComponent healingC _mobState.ChangeMobState(vampireUid, MobState.Critical, mobStateComponent, container.Owner); } } + #endregion #region Blood Drinking private void ToggleFangs(Entity vampire) @@ -267,7 +341,6 @@ private void ToggleFangs(Entity vampire) var popupText = Loc.GetString(vampire.Comp.FangsExtended ? "vampire-fangs-extended" : "vampire-fangs-retracted"); _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); } - private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) { if (args.Handled) @@ -278,7 +351,6 @@ private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent c args.Handled = TryDrink(args.Target, args.User); } - private bool TryDrink(EntityUid target, EntityUid drinker, VampireComponent? vampireComponent = null) { if (!Resolve(drinker, ref vampireComponent, false)) @@ -294,7 +366,12 @@ private bool TryDrink(EntityUid target, EntityUid drinker, VampireComponent? vam if (_food.IsMouthBlocked(target, drinker)) return false; - if (_rot) + if (_rotting.IsRotten(target)) + { + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), drinker, drinker, Shared.Popups.PopupType.SmallCaution); + return false; + } + var doAfterEventArgs = new DoAfterArgs(EntityManager, drinker, vampireComponent.BloodDrainDelay, new VampireDrinkBloodEvent(), eventTarget: drinker, @@ -313,7 +390,6 @@ private bool TryDrink(EntityUid target, EntityUid drinker, VampireComponent? vam _doAfter.TryStartDoAfter(doAfterEventArgs); return true; } - private void DrinkDoAfter(Entity entity, ref VampireDrinkBloodEvent args) { if (!args.Target.HasValue) @@ -325,39 +401,91 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood if (!entity.Comp.FangsExtended) return; - if (!TryComp(args.Target, out var targetBloodstream)) + if (_rotting.IsRotten(args.Target.Value)) + { + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), args.User, args.User, Shared.Popups.PopupType.SmallCaution); return; + } - if (targetBloodstream.BloodSolution == null) + if (!TryComp(args.Target, out var targetBloodstream) || targetBloodstream == null || targetBloodstream.BloodSolution == null) return; - if (!entity.Comp.VampirePowers.TryGetValue(VampirePower.DrinkBlood, out var def)) + if (!_cachedPowers.TryGetValue(VampirePowerKey.DrinkBlood, out var def) || def == null) return; //Ensure there is enough blood to drain - if (targetBloodstream.BloodSolution?.Comp.Solution.Volume < def.ActivationCost) + var victimBloodRemaining = targetBloodstream.BloodSolution.Value.Comp.Solution.Volume; + if (victimBloodRemaining <= 0) { _popup.PopupEntity(Loc.GetString("vampire-blooddrink-empty"), entity.Owner, entity.Owner, Shared.Popups.PopupType.SmallCaution); return; } + var volumeToConsume = Math.Min((float)victimBloodRemaining.Value, def.ActivationCost); - if (!_blood.TryModifyBloodLevel(args.Target.Value, def.ActivationCost)) + if (!IngestBlood(entity, volumeToConsume)) return; - AddBlood(entity, -def.ActivationCost); + if (!_blood.TryModifyBloodLevel(args.Target.Value, -volumeToConsume)) + return; _audio.PlayPvs(def.ActivationSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); + if (HasComp(args.Target.Value)) + { + //Scream, do burn damage + //Thou shall not feed upon the blood of the holy + InjectHolyWater(entity, 5); + return; + } + else + { + AddBlood(entity, volumeToConsume); + } + //Update abilities, add new unlocks UpdateAbilities(entity); args.Repeat = true; } + + private void InjectHolyWater(Entity vampire, FixedPoint2 quantity) + { + var solution = new ReagentQuantity(holyWaterReagent.ID, quantity, null); + if (TryComp(vampire.Owner, out var bloodStream) && bloodStream.ChemicalSolution != null) + { + _solution.TryAddReagent(bloodStream.ChemicalSolution.Value, solution, out _); + } + } + private bool IngestBlood(Entity vampire, float quantity) + { + if (TryComp(vampire.Owner, out var body) && _body.TryGetBodyOrganComponents(vampire.Owner, out var stomachs, body)) + { + var ingestedSolution = new Solution(bloodReagent.ID, quantity); + var firstStomach = stomachs.FirstOrNull(stomach => _stomach.CanTransferSolution(stomach.Comp.Owner, ingestedSolution, stomach.Comp)); + if (firstStomach == null) + { + //We are full + _popup.PopupEntity(Loc.GetString("vampire-full-stomach"), vampire.Owner, vampire.Owner, Shared.Popups.PopupType.SmallCaution); + return false; + } + _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, ingestedSolution, firstStomach.Value.Comp); + return true; + } + + //No stomach + return false; + } + private void AddBlood(Entity vampire, float quantity) + { + vampire.Comp.TotalBloodDrank += quantity; + vampire.Comp.AvailableBlood += quantity; + } #endregion private void DoSpaceDamage(EntityUid vampireUid, VampireComponent component) { - _damageableSystem.TryChangeDamage(vampireUid, component.SpaceDamage, true, origin: vampireUid); + _damageableSystem.TryChangeDamage(vampireUid, spaceDamage, true, origin: vampireUid); + _popup.PopupEntity(Loc.GetString("vampire-startlight-burning"), vampireUid, vampireUid, Shared.Popups.PopupType.LargeCaution); } private bool IsInSpace(EntityUid vampireUid) { @@ -373,24 +501,85 @@ private bool IsInSpace(EntityUid vampireUid) return tileRef.Tile.IsEmpty; } - private FixedPoint2? GetBlood(Entity vampire) + /// + /// Convert the players body into a vampire + /// Alternative to this would be creating a dedicated vampire race + /// But i want the player to look 'normal' and keep the same customisations as the non vampire player + /// + /// Which entity to convert + private void ConvertBody(EntityUid vampire) { - if (!_solution.TryGetSolution(vampire.Owner, vampire.Comp.BloodContainer, out var vampireBloodContainer)) - return null; + var metabolizerTypes = new HashSet() { "bloodsucker", "vampire" }; //Heal from drinking blood, and be damaged by drinking holy water + var specialDigestion = new EntityWhitelist() { Tags = new() { "Pill" } }; //Restrict Diet - return vampireBloodContainer.Value.Comp.Solution.Volume; - } - private void AddBlood(Entity vampire, Solution solution) { - AddBlood(vampire, solution.Volume); + if (!TryComp(vampire, out var bodyComponent)) + return; + + //Add vampire and bloodsucker to all metabolizing organs + //And restrict diet to Pills (and liquids) + foreach(var organ in _body.GetBodyOrgans(vampire, bodyComponent)) + { + if (TryComp(organ.Id, out var metabolizer)) + { + if (TryComp(organ.Id, out var stomachComponent)) + { + //Override the stomach, prevents humans getting sick when ingesting blood + _metabolism.SetMetabolizerTypes(metabolizer, metabolizerTypes); + _stomach.SetSpecialDigestible(stomachComponent, specialDigestion); + } + else + { + //Otherwise just add the metabolizers on + var tempMetabolizer = metabolizer.MetabolizerTypes ?? new HashSet(); + foreach (var t in metabolizerTypes) + tempMetabolizer.Add(t); + + _metabolism.SetMetabolizerTypes(metabolizer, tempMetabolizer); + } + } + } + + //Take damage from holy water splash + if (TryComp(vampire, out var reactive)) + { + if (reactive.ReactiveGroups == null) + reactive.ReactiveGroups = new(); + + reactive.ReactiveGroups.Add("Unholy", new() { ReactionMethod.Touch }); + } + + DamageSpecifier meleeDamage = new(_prototypeManager.Index("Slash"), FixedPoint2.New(10)); + + //Extra melee power + if (TryComp(vampire, out var melee)) + { + melee.Damage = meleeDamage; + melee.Animation = "WeaponArcSlash"; + melee.HitSound = new SoundPathSpecifier("/Audio/Weapons/slash.ogg"); + } } - private void AddBlood(Entity vampire, FixedPoint2 quantity) + //Remove weakeness to holy items + private void MakeImmuneToHoly(EntityUid vampire) { - if (!_solution.TryGetSolution(vampire.Owner, vampire.Comp.BloodContainer, out var vampireBloodContainer)) + if (!TryComp(vampire, out var bodyComponent)) return; - //Convert everything into 'blood' - //No point restricting people to only feeding on people with their blood type - var solution = new Solution("blood", quantity); - _solution.TryAddSolution(vampireBloodContainer.Value, solution); + if (TryComp(vampire, out var reactive)) + { + if (reactive.ReactiveGroups == null) + return; + + reactive.ReactiveGroups.Remove("Unholy"); + } + } + private void CachePowers() + { + var tempDict = new Dictionary(); + foreach (var power in _prototypeManager.EnumeratePrototypes()) + { + tempDict.Add(power.Key, power); + } + + _cachedPowers = tempDict.ToFrozenDictionary(); } } diff --git a/Content.Shared/Vampire/Components/VampireComponent.cs b/Content.Shared/Vampire/Components/VampireComponent.cs index 41024f476b0..9dda6d4d9f0 100644 --- a/Content.Shared/Vampire/Components/VampireComponent.cs +++ b/Content.Shared/Vampire/Components/VampireComponent.cs @@ -1,6 +1,7 @@ using Content.Shared.Damage; using Content.Shared.FixedPoint; using Robust.Shared.Audio; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using System; using System.Collections.Frozen; @@ -15,13 +16,14 @@ namespace Content.Shared.Vampire.Components; [RegisterComponent] public sealed partial class VampireComponent : Component { - public readonly string BloodContainer = "blood@vampire"; - [DataField, ViewVariables(VVAccess.ReadWrite)] public bool FangsExtended = false; [DataField, ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier SpaceDamage = default!; + public float AvailableBlood = default!; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float TotalBloodDrank = default!; [DataField, ViewVariables(VVAccess.ReadWrite)] public TimeSpan SpaceDamageInterval = TimeSpan.FromSeconds(1); @@ -31,46 +33,66 @@ public sealed partial class VampireComponent : Component public EntityUid? HomeCoffin = default!; //Abilities - public HashSet UnlockedPowers = new(); + public Dictionary UnlockedPowers = new(); //Blood Drinking - public TimeSpan BloodDrainDelay = TimeSpan.FromSeconds(2); - public readonly SoundSpecifier DrinkSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); - - - public FrozenDictionary VampirePowers = new Dictionary - { - { VampirePower.ToggleFangs, new("ActionToggleFangs", null, null, 0, 0) }, - { VampirePower.Glare, new("ActionVampireGlare", null, null, 0, 0) }, - { VampirePower.DeathsEmbrace, new(null, "Smoke", new SoundPathSpecifier("/Audio/Effects/explosionsmallfar.ogg"), 100, 200) }, - { VampirePower.DrinkBlood, new(null, null, new SoundPathSpecifier("/Audio/Items/drink.ogg"), 0, -30) } //Activation cost is how much we drain from the victim - }.ToFrozenDictionary(); + public TimeSpan BloodDrainDelay = TimeSpan.FromSeconds(1); } -public sealed class VampirePowerDef +[Prototype("vampirePower")] +public sealed partial class VampirePowerPrototype : IPrototype { + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + public VampirePowerKey Key => Enum.Parse(ID); + + [DataField] public readonly string? ActionPrototype; + + [DataField] public readonly string? ActivationEffect; + + [DataField] public readonly SoundSpecifier? ActivationSound; - public readonly int ActivationCost; - public readonly int UnlockCost; + + [DataField] + public readonly int ActivationCost = 0; + + [DataField] + public readonly int UnlockRequirement = 0; + + [DataField] + public readonly bool UsableWhileCuffed = true; + + [DataField] + public readonly bool UsableWhileStunned = true; + + [DataField] + public readonly bool UsableWhileMuffled = true; + public EntityUid? Action; - public VampirePowerDef(string? actionPrototype, string? activationEffect, SoundSpecifier? activationSound, int unlockCost, int activationCost) + /*public VampirePowerPrototype(string? actionPrototype, string? activationEffect, SoundSpecifier? activationSound, int unlockCost, int activationCost, bool usableWhileCuffed, bool usableWhileStunned, bool usableWhileMuffled) { - this.ActionPrototype = actionPrototype; - this.ActivationEffect = activationEffect; - this.ActivationSound = activationSound; - this.UnlockCost = unlockCost; - this.ActivationCost = activationCost; - } + ActionPrototype = actionPrototype; + ActivationEffect = activationEffect; + ActivationSound = activationSound; + UnlockCost = unlockCost; + ActivationCost = activationCost; + UsableWhileCuffed = usableWhileCuffed; + UsableWhileStunned = usableWhileStunned; + UsableWhileMuffled = usableWhileMuffled; + }*/ } [Serializable, NetSerializable] -public enum VampirePower : Byte +public enum VampirePowerKey : byte { ToggleFangs, Glare, DeathsEmbrace, - DrinkBlood + DrinkBlood, + Screech } diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index b5000f32ac0..c4ffd82f986 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -5,10 +5,15 @@ namespace Content.Shared.Vampire; -public sealed partial class VampireUsePowerEvent : InstantActionEvent +public sealed partial class VampireUseAreaPowerEvent : InstantActionEvent { [DataField("type")] - public VampirePower Type; + public VampirePowerKey Type; +}; +public sealed partial class VampireUseTargetedPowerEvent : EntityTargetActionEvent +{ + [DataField("type")] + public VampirePowerKey Type; }; [Serializable, NetSerializable] diff --git a/Resources/Audio/Effects/Vampire/glare.ogg b/Resources/Audio/Effects/Vampire/glare.ogg new file mode 100644 index 0000000000000000000000000000000000000000..44e46ea28effdb65c952db680ed5bc27572e0215 GIT binary patch literal 5592 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 h065dvJY+D(@wlw=FJRI`L#hzF;aBIe)(QFmG6 zYTT|_4{!I)?6_%dHucA_^PBhHet+-x{r!0_)ND4L<;3(%l%8dMeI2M)tKu_j?>FQ5 zu(O;1E}Yr?^vkE%JrQOG1_nZ7V`BmIE9nE9e{D3VHu)8Ph7ADjLZkgiowo9~cut;t z$8}w?V}YgwEMLF&LU_;f3MLFBEgPOlTDOwcEr`M6pg|uz3M%Y{y%2>*AOd07lS-ux z!!Qg(aDL4CLE#~bGBh+K__?_lzquHXMqUKouucFO`<#ENVJ-oNxnh{B_W(?#E~iqL zg%g^lwLB>Q_V(Z*Yw(ceUz@bf`}7;GGb(h~8ALsedKUF8>c{`K0ZJI;^(gVOl_n@=X zv+yU>p)QBRfn~+~icrR_sZP3X6pHuB2wm5uUa3?&4E$6M$+^+v31H5?IBrcPtuMr& zM0cHohPl$!K)F(h>3X?R0YYtVTCaVvrd+plH9v39j$2bA28E(hC@LevTWxYqxoc?; zwf2!zW4bPWV?I|NdL4wfo0zjN0vY=~C<6s@K*!$i&H+RQ|EqxmL(hsJj~-75iu}}j z$@k=>yOwUD%lzq#Fwobh1}VBzx-Cj!>1tkj0E`T8c>xGG0o1d)t_RQqRo6X>M4?G& zzm1*5U|+0R{n5)JIbA3!u5%PH%-qQER%Y$Nn_bsR7R|2R7;=W012Xn20JYD0G9ZI2 zT7{pPRxeUd^<-f8&jWv&icAeqr9Gf*E*-v_wS3?Xf%L{i3!iZi6dXnF1&pB)n(xNwLjqLZU0@n)Uvcq3>uAw zg#O^&@26*?|JQk-X_`>peCyjQ>Dm?>P~{$1$NFmf>e%8$h=2Ry0vY=5o5Nygq42uA zw2FIjFXFoHg)^JeGtsaa&pXQrf$n!@$5JYlq@K&={sA`8ag2MVo{azi002ovPDHLk FV1nB>+DZTb literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/drainblood.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/drainblood.png index 1475c96bb8b3d131813766e3f170159a0ce84fd4..10ca88140ca3e8cc850ff014ca8c90b6b539cdc4 100644 GIT binary patch delta 900 zcmV-~1AF}L3Xlho8Gix*008_L?V|ty010qNS#tmY3ljhU3ljkVnw%H_00TlvL_t(Y z$GuifNE<;E{+f)e$dm*lh207ki_m%~f*`hgkQAl0DfFQ9RHP}IYmdSr^b&*|dyRs{ zW5I(c)M8;NvRVY86ymL7B<_MV66s>I%|v^6c1*YakZAiI9)C0M?Y!^(%Rn^()8j~8-iPj~buBF|08~}29QY&vX}yW-KT-NT zvDz`x1z>dCPJbE2CqtV7{9~}r&d%3w8~~puRsr0>?Hat4O%r$qpb_DZ=??&U61}72 zb|@4&%HU)ALnFeC2+IYphK7ExJi^S>l(f!FO_fcP22xMB3Vs!wXaKM?;(9wVA^nz3 z6S__wgVCt`=8I_p2u7pW@GsXi?Hq#4(h`9AWD>x_{(nA_Ns_j0&sSttS)vnqaBh7U z_V)p}u4@+xE@KNoyAZZ*Q-J5<&)Nfft3CAd>4oMzI?g$~>qzilQ*a7-O9CNF?$i zGf>Mx1Msk+Vc{e_=Ui)OpwqY=iPTu4SS&`{iGOy~Jt4Y^Mb5eJr{9{()k4tS-QBP2 zskBV#r2gw^8QCEwPI~;O^)a0 a=6(U*(y9@JzKP-h0000~F&?4d42@r*5jd2+#8G`?LDgoW+2EL_Eg~(CuBDCwISV+=&)W<*U`V-_*$W;O(#{w$QAiI9>KlnXcD?c^q zC556u=ZoWfi~?P|K%?e3-^Y&AI01ssz?I(eSL(p*C+W487Cr(7wtV-R*Pxx2HM3 z9|9tBdVbC7A^-pY24YJ`L;(K){{a7>y{D6rAs2rD2XskIMF;2s2>>cO-^@Vo0000P zbVXQnLvL+uWo~o;Lvm$dbY)~9cWHEJAV*0}P*;Ht7XSbQMoC0LR9M69moaD?Nfd@Z zOKT*=1ewSfv{4{zT!af827@qG5gZH`q9R2?Tm+5~u-hvQF0OE4aCxnQAOupVIWSS7 zfFOUkurC-4A>cHz7eka4m{X9+veBMa_7%zO%}QspF`o zagxbouKw|7oub6wqii5QdTI9``&C*LAUN8-O<>|LhxK7@0<+5ncu=j?*LJ=;-Ln z=jOVBxxz3q6~{Ss2DH;olqt3~4cC8l*@#Ac-&Q;hwvE+n1~L)q9`HPm&9CI$a9tPI zb@4pUFNvASgd;#*NF~p8Iz4k`SU+yWKuN0fRQyMl{Os z@bLKwkch=%J!5L*kw_$b0gra>l1im`J35LGqDO>`JQjfA4UxIu-AJ~x7o=3LTLI}}w+8MQ4xB{l8rg&E@mL$Qn=b`I5 znx>J@=Mh5q0!`B_jorI&Oc}Vb zFv~}R-+gpuhCBQ^C#~v}ef58$@^AQr&5ecG{sQj*v_U$ZW@0`|orAv0RUxXdZh-(F zY5L^9JT&`%1^$I`0rm>}Umq8L5~DrP9$dS|p1JS$0qPtuF`o^PuP(JHm%Cm@@?iq- z;?-^c5g}hTVZgG!T+0_%;*`tfp+E+%PgjR_HfN7tyt*CCL`c})-u4^YlKg)F{0&`b Vi9bN%Q(6E3002ovPDHLkV1kL$f|LLN diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/glare.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/glare.png new file mode 100644 index 0000000000000000000000000000000000000000..0e9c8efd1feb5b5e1417612ef319f851fa2b61c2 GIT binary patch literal 1391 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 aZT%M(_MSNNJNM}T0000 Date: Sat, 13 Jan 2024 19:00:29 +0000 Subject: [PATCH 03/67] Continuing, more abilities --- Content.Server/Bible/BibleSystem.cs | 36 +- .../Bible/Components/BibleComponent.cs | 8 + .../Vampire/VampireSystem.Transform.cs | 92 +++ Content.Server/Vampire/VampireSystem.cs | 632 ++++++++++++------ .../Vampire/Components/CoffinComponent.cs | 17 - .../Vampire/Components/VampireComponent.cs | 98 --- .../Components/VampireHealingComponent.cs | 20 - Content.Shared/Vampire/VampireComponent.cs | 166 +++++ Content.Shared/Vampire/VampireEvents.cs | 22 +- Resources/Locale/en-US/Vampires/vampires.ftl | 15 +- Resources/Locale/en-US/chapel/bible.ftl | 3 + Resources/Prototypes/Actions/vampire.yml | 131 ++-- .../Prototypes/Entities/Effects/vampire.yml | 25 + .../Objects/Specific/Chapel/bibles.yml | 14 + .../Structures/Storage/Crates/crates.yml | 6 - Resources/Prototypes/Polymorphs/polymorph.yml | 7 + Resources/Prototypes/Vampire/powers.yml | 122 +++- .../Actions/actions_vampire.rsi/batform.png | Bin 0 -> 1119 bytes .../actions_vampire.rsi/bloodsteal.png | Bin 0 -> 756 bytes ...{deathsembrace.png => cloakofdarkness.png} | Bin .../{drainblood.png => fangs_extended.png} | Bin .../actions_vampire.rsi/fangs_retracted.png | Bin 0 -> 951 bytes .../Actions/actions_vampire.rsi/hypnotise.png | Bin 0 -> 1228 bytes .../Actions/actions_vampire.rsi/meta.json | 8 +- 24 files changed, 993 insertions(+), 429 deletions(-) create mode 100644 Content.Server/Vampire/VampireSystem.Transform.cs delete mode 100644 Content.Shared/Vampire/Components/CoffinComponent.cs delete mode 100644 Content.Shared/Vampire/Components/VampireComponent.cs delete mode 100644 Content.Shared/Vampire/Components/VampireHealingComponent.cs create mode 100644 Content.Shared/Vampire/VampireComponent.cs create mode 100644 Resources/Prototypes/Entities/Effects/vampire.yml create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/batform.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/bloodsteal.png rename Resources/Textures/Interface/Actions/actions_vampire.rsi/{deathsembrace.png => cloakofdarkness.png} (100%) rename Resources/Textures/Interface/Actions/actions_vampire.rsi/{drainblood.png => fangs_extended.png} (100%) create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_retracted.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/hypnotise.png diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index c845b17230a..f612fd20ca7 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -12,12 +12,16 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; +using Content.Shared.Stunnable; using Content.Shared.Timing; +using Content.Shared.Vampire.Components; using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Timing; namespace Content.Server.Bible { @@ -33,12 +37,14 @@ public sealed class BibleSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UseDelaySystem _delay = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnInsertedContainer); SubscribeLocalEvent>(AddSummonVerb); SubscribeLocalEvent(GetSummonAction); SubscribeLocalEvent(OnSummon); @@ -46,6 +52,20 @@ public override void Initialize() SubscribeLocalEvent(OnSpawned); } + private void OnInsertedContainer(EntityUid uid, BibleComponent component, EntGotInsertedIntoContainerMessage args) + { + //If an unholy creature picks up the bible, knock them down + if (HasComp(args.Container.Owner)) + { + Timer.Spawn(500, () => + { + _stun.TryParalyze(args.Container.Owner, TimeSpan.FromSeconds(10), true); + _damageableSystem.TryChangeDamage(args.Container.Owner, component.DamageOnUnholyUse); + _audio.PlayPvs(component.SizzleSoundPath, args.Container.Owner); + }); + } + } + private readonly Queue _addQueue = new(); private readonly Queue _remQueue = new(); @@ -115,7 +135,21 @@ private void OnAfterInteract(EntityUid uid, BibleComponent component, AfterInter return; } - // This only has a chance to fail if the target is not wearing anything on their head and is not a familiar. + //Damage unholy creatures + if (HasComp(args.Target)) + { + _damageableSystem.TryChangeDamage(args.Target.Value, component.DamageUnholy, true, origin: uid); + + var othersMessage = Loc.GetString(component.LocPrefix + "-damage-unholy-others", ("user", Identity.Entity(args.User, EntityManager)), ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid)); + _popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.MediumCaution); + + var selfMessage = Loc.GetString(component.LocPrefix + "-damage-unholy-self", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid)); + _popupSystem.PopupEntity(selfMessage, args.User, args.User, PopupType.LargeCaution); + + return; + } + + // This only has a chance to fail if the target is not wearing anything on their head and is not a familiar.. if (!_invSystem.TryGetSlotEntity(args.Target.Value, "head", out var _) && !HasComp(args.Target.Value)) { if (_random.Prob(component.FailChance)) diff --git a/Content.Server/Bible/Components/BibleComponent.cs b/Content.Server/Bible/Components/BibleComponent.cs index b7dc3db8e35..3201831017d 100644 --- a/Content.Server/Bible/Components/BibleComponent.cs +++ b/Content.Server/Bible/Components/BibleComponent.cs @@ -27,6 +27,14 @@ public sealed partial class BibleComponent : Component [ViewVariables(VVAccess.ReadWrite)] public DamageSpecifier DamageOnUntrainedUse = default!; + [DataField("damageOnUnholyUse", required: true)] + [ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier DamageOnUnholyUse = default!; + + [DataField("damageUnholy", required: true)] + [ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier DamageUnholy = default!; + /// /// Chance the bible will fail to heal someone with no helmet /// diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs new file mode 100644 index 00000000000..81d64720bde --- /dev/null +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -0,0 +1,92 @@ +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Shared.Body.Components; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Damage; +using Content.Shared.Vampire.Components; +using Content.Shared.Weapons.Melee; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Server.Vampire; + +public sealed partial class VampireSystem +{ + [Dependency] private readonly MetabolizerSystem _metabolism = default!; + + /// + /// Convert the players body into a vampire + /// Alternative to this would be creating a dedicated vampire race + /// But i want the player to look 'normal' and keep the same customisations as the non vampire player + /// + /// Which entity to convert + private void ConvertBody(EntityUid vampire, VampireAbilityListPrototype abilityList) + { + var metabolizerTypes = new HashSet() { "bloodsucker", "vampire" }; //Heal from drinking blood, and be damaged by drinking holy water + //var specialDigestion = new EntityWhitelist() { Tags = new() { "Pill" } }; //Restrict Diet + + if (!TryComp(vampire, out var bodyComponent)) + return; + + //Add vampire and bloodsucker to all metabolizing organs + //And restrict diet to Pills (and liquids) + foreach (var organ in _body.GetBodyOrgans(vampire, bodyComponent)) + { + if (TryComp(organ.Id, out var metabolizer)) + { + if (TryComp(organ.Id, out var stomachComponent)) + { + //Override the stomach, prevents humans getting sick when ingesting blood + _metabolism.SetMetabolizerTypes(metabolizer, metabolizerTypes); + _stomach.SetSpecialDigestible(stomachComponent, abilityList.AcceptableFoods); + } + else + { + //Otherwise just add the metabolizers on + var tempMetabolizer = metabolizer.MetabolizerTypes ?? new HashSet(); + foreach (var t in metabolizerTypes) + tempMetabolizer.Add(t); + + _metabolism.SetMetabolizerTypes(metabolizer, tempMetabolizer); + } + } + } + + //Take damage from holy water splash + if (TryComp(vampire, out var reactive)) + { + if (reactive.ReactiveGroups == null) + reactive.ReactiveGroups = new(); + + reactive.ReactiveGroups.Add("Unholy", new() { ReactionMethod.Touch }); + } + + //Extra melee power + if (TryComp(vampire, out var melee)) + { + melee.Damage = abilityList.MeleeDamage; + melee.Animation = "WeaponArcSlash"; + melee.HitSound = new SoundPathSpecifier("/Audio/Weapons/slash.ogg"); + } + } + + //Remove weakeness to holy items + private void MakeImmuneToHoly(EntityUid vampire) + { + if (TryComp(vampire, out var reactive)) + { + if (reactive.ReactiveGroups == null) + return; + + reactive.ReactiveGroups.Remove("Unholy"); + } + + RemComp(vampire); + } +} diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index bb3d697dd4d..33ac9df91b0 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -1,22 +1,28 @@ using Content.Server.Actions; using Content.Server.Atmos.Components; using Content.Server.Atmos.Rotting; +using Content.Server.Beam; +using Content.Server.Bed.Sleep; using Content.Server.Bible.Components; using Content.Server.Body.Components; using Content.Server.Body.Systems; +using Content.Server.Chat.Systems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Flash; +using Content.Server.Flash.Components; using Content.Server.Interaction; using Content.Server.Nutrition.EntitySystems; +using Content.Server.Polymorph.Systems; using Content.Server.Popups; using Content.Server.Speech.Components; using Content.Server.Storage.Components; using Content.Server.Storage.EntitySystems; using Content.Server.Temperature.Components; using Content.Shared.Atmos.Rotting; +using Content.Shared.Bed.Sleep; using Content.Shared.Body.Components; +using Content.Shared.Chat.Prototypes; using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Cuffs.Components; using Content.Shared.Damage; @@ -30,11 +36,12 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Polymorph; +using Content.Shared.StatusEffect; +using Content.Shared.Stealth.Components; using Content.Shared.Stunnable; using Content.Shared.Vampire; using Content.Shared.Vampire.Components; -using Content.Shared.Weapons.Melee; -using Content.Shared.Whitelist; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -44,7 +51,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Collections.Frozen; -using System.Diagnostics; namespace Content.Server.Vampire; @@ -69,36 +75,51 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly RottingSystem _rotting = default!; [Dependency] private readonly BodySystem _body = default!; [Dependency] private readonly StomachSystem _stomach = default!; - [Dependency] private readonly MetabolizerSystem _metabolism = default!; - [Dependency] private readonly FlashSystem _flash = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SolutionContainerSystem _solution = default!; - - private FrozenDictionary _cachedPowers = default!; - private ReagentPrototype holyWaterReagent = default!; - private ReagentPrototype bloodReagent = default!; - private DamageSpecifier spaceDamage = default!; - + [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly PolymorphSystem _polymorph = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly BeamSystem _beam = default!; + + private FrozenDictionary _cachedAbilityLists = default!; + + [ValidatePrototypeId] + private const string SleepStatusEffectKey = "ForcedSleep"; + [ValidatePrototypeId] + private const string HolyWaterKey = "Holywater"; + [ValidatePrototypeId] + private const string VampireBatKey = "VampireBat"; + [ValidatePrototypeId] + private const string ScreamEmoteKey = "Scream"; + + private ReagentPrototype _holyWater = default!; + private PolymorphPrototype _vampireBat = default!; + private EmotePrototype _scream = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnInteractWithHumanoid, before: new[] { typeof(InteractionPopupSystem) }); + SubscribeLocalEvent(OnInteractWithHumanoid, before: new[] { typeof(InteractionPopupSystem), typeof(SleepingSystem) }); SubscribeLocalEvent(DrinkDoAfter); + SubscribeLocalEvent(HypnotiseDoAfter); SubscribeLocalEvent(OnInsertedIntoContainer); SubscribeLocalEvent(OnRemovedFromContainer); SubscribeLocalEvent(OnVampireStateChanged); - SubscribeLocalEvent(OnUsePower); + SubscribeLocalEvent(OnUseAreaPower); + SubscribeLocalEvent(OnUseTargetedPower); SubscribeLocalEvent(OnExamined); CachePowers(); - holyWaterReagent = _prototypeManager.Index("HolyWater"); - bloodReagent = _prototypeManager.Index("Blood"); - spaceDamage = new(_prototypeManager.Index("Heat"), FixedPoint2.New(5)); + //Local references + _holyWater = _prototypeManager.Index(HolyWaterKey); + _vampireBat = _prototypeManager.Index(VampireBatKey); + _scream = _prototypeManager.Index(ScreamEmoteKey); } /// @@ -109,61 +130,84 @@ private void OnComponentInit(EntityUid uid, VampireComponent component, Componen //_solution.EnsureSolution(uid, component.BloodContainer); RemComp(uid); RemComp(uid); + EnsureComp(uid); if (TryComp(uid, out var temperatureComponent)) temperatureComponent.ColdDamageThreshold = 0; - ConvertBody(uid); + //Hardcoding the default ability list + //TODO: Add client UI and multiple ability lists + component.ChosenAbilityList = _cachedAbilityLists["Default"]; - UpdateAbilities((uid, component)); + ConvertBody(uid, component.ChosenAbilityList); + + UpdateAbilities((uid, component), true); } private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) { - if (component.FangsExtended && args.IsInDetailsRange) + if (component.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs) && args.IsInDetailsRange) args.AddMarkup($"{Loc.GetString("vampire-fangs-extended-examine")}{Environment.NewLine}"); } - private void OnUseAreaPower(EntityUid uid, VampireComponent component, VampireUseAreaPowerEvent arg) + /// + /// Upon using any non targeted power + /// + private void OnUseAreaPower(EntityUid uid, VampireComponent component, VampireUseAreaPowerEvent args) + { + Entity vampire = (uid, component); + + TriggerPower(vampire, args.Type, null); + } + private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireUseTargetedPowerEvent args) { Entity vampire = (uid, component); - if (!component.UnlockedPowers.ContainsKey(arg.Type)) + TriggerPower(vampire, args.Type, args.Target); + } + + private void TriggerPower(Entity vampire, VampirePowerKey powerType, EntityUid? target) + { + if (!vampire.Comp.UnlockedPowers.ContainsKey(powerType)) return; - if (!_cachedPowers.TryGetValue(arg.Type, out var def)) + if (!GetAbilityDefinition(vampire.Comp, powerType, out var def) || def == null) return; - if (def.ActivationCost > 0 && def.ActivationCost > component.AvailableBlood) + if (def.ActivationCost > 0 && def.ActivationCost > vampire.Comp.AvailableBlood) { - _popup.PopupEntity(Loc.GetString("vampire-not-enough-blood"), uid, uid, Shared.Popups.PopupType.MediumCaution); + _popup.PopupEntity(Loc.GetString("vampire-not-enough-blood"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); return; } - AddBlood(vampire, -def.ActivationCost); - //Block if we are cuffed - if (!def.UsableWhileCuffed && TryComp(uid, out var cuffable) && !cuffable.CanStillInteract) + if (!def.UsableWhileCuffed && TryComp(vampire, out var cuffable) && !cuffable.CanStillInteract) + { + _popup.PopupEntity(Loc.GetString("vampire-cuffed"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); return; + } //Block if we are stunned - if (!def.UsableWhileStunned && HasComp(uid)) + if (!def.UsableWhileStunned && HasComp(vampire)) + { + _popup.PopupEntity(Loc.GetString("vampire-stunned"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); return; + } - //Block if we are muzzled - if (!def.UsableWhileMuffled && TryComp(uid, out var accent) && accent.Accent.Equals("mumble")) + //Block if we are muzzled - so far only one item does this? + if (!def.UsableWhileMuffled && TryComp(vampire, out var accent) && accent.Accent.Equals("mumble")) + { + _popup.PopupEntity(Loc.GetString("vampire-muffled"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); return; + } if (def.ActivationEffect != null) Spawn(def.ActivationEffect, _transform.GetMapCoordinates(Transform(vampire.Owner))); - if (def.ActivationSound != null) - _audio.PlayPvs(def.ActivationSound, uid); - - _action.StartUseDelay(component.UnlockedPowers[arg.Type]); + var success = true; - //TODO: Rewrite when a magic system is introduced - switch (arg.Type) + //TODO: Rewrite when a magic effect system is introduced (like reagents) + switch (powerType) { case VampirePowerKey.ToggleFangs: { @@ -172,117 +216,316 @@ private void OnUseAreaPower(EntityUid uid, VampireComponent component, VampireUs } case VampirePowerKey.DeathsEmbrace: { - TryMoveToCoffin(vampire); + success = TryMoveToCoffin(vampire); break; } case VampirePowerKey.Glare: { - Glare(vampire); + Glare(vampire, target, def.Duration, def.Damage); break; } case VampirePowerKey.Screech: { - Screech(vampire); + Screech(vampire, def.Duration, def.Damage); + break; + } + case VampirePowerKey.BatForm: + { + PolymorphBat(vampire); + break; + } + case VampirePowerKey.Hypnotise: + { + success = TryHypnotise(vampire, target, def.Duration, def.Delay); + break; + } + case VampirePowerKey.BloodSteal: + { + BloodSteal(vampire); + break; + } + case VampirePowerKey.CloakOfDarkness: + { + CloakOfDarkness(vampire); break; } default: break; } + + if (!success) + return; + + AddBlood(vampire, -def.ActivationCost); + + _action.StartUseDelay(vampire.Comp.UnlockedPowers[powerType]); } + /// + /// Handles healing and damaging in space + /// public override void Update(float frameTime) { base.Update(frameTime); - var query1 = EntityQueryEnumerator(); - while (query1.MoveNext(out var uid, out var vampireHealingComponent)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var vampireComponent)) { - if (vampireHealingComponent.NextHealTick <= 0) + var vampire = (uid, vampireComponent); + + if (TryComp(uid, out var vampireSealthComponent)) { - vampireHealingComponent.NextHealTick = vampireHealingComponent.HealTickInterval.TotalSeconds; - DoCoffinHeal(uid, vampireHealingComponent); + if (vampireSealthComponent.NextStealthTick <= 0) + { + vampireSealthComponent.NextStealthTick = 1; + AddBlood(vampire, vampireComponent.ChosenAbilityList.StealthBloodCost); + } + vampireSealthComponent.NextStealthTick -= frameTime; + } + + if (TryComp(uid, out var vampireHealingComponent)) + { + if (vampireHealingComponent.NextHealTick <= 0) + { + vampireHealingComponent.NextHealTick = 1; + DoCoffinHeal(vampire); + } + vampireHealingComponent.NextHealTick -= frameTime; } - vampireHealingComponent.NextHealTick -= frameTime; - } - var query2 = EntityQueryEnumerator(); - while (query2.MoveNext(out var uid, out var vampireComponent)) - { if (IsInSpace(uid)) { if (vampireComponent.NextSpaceDamageTick <= 0) { - vampireComponent.NextSpaceDamageTick = vampireComponent.SpaceDamageInterval.TotalSeconds; - DoSpaceDamage(uid, vampireComponent); + vampireComponent.NextSpaceDamageTick = 1; + DoSpaceDamage(vampire); } vampireComponent.NextSpaceDamageTick -= frameTime; } } } - private void UpdateAbilities(Entity vampire) + /// + /// Update which abilities are available based upon available blood + /// + private void UpdateAbilities(Entity vampire, bool silent = false) { - foreach (var power in _cachedPowers.Values) + foreach (var power in vampire.Comp.ChosenAbilityList.Abilities) { - if (power.UnlockRequirement <= vampire.Comp.AvailableBlood) - UnlockAbility(vampire, power.Key); + if (power.BloodUnlockRequirement <= vampire.Comp.AvailableBlood) + UnlockAbility(vampire, power, silent); } } - private void UnlockAbility(Entity vampire, VampirePowerKey power) + private void UnlockAbility(Entity vampire, VampireAbilityEntry powerDef, bool silent = false) { - if (vampire.Comp.UnlockedPowers.ContainsKey(power)) + if (vampire.Comp.UnlockedPowers.ContainsKey(powerDef.Type)) return; - if (_cachedPowers.TryGetValue(power, out var def) && def.ActionPrototype != null) + if (powerDef.ActionPrototype == null) { - var actionUid = _action.AddAction(vampire.Owner, def.ActionPrototype); + //passive ability + vampire.Comp.UnlockedPowers.Add(powerDef.Type, null); + } + else + { + var actionUid = _action.AddAction(vampire.Owner, powerDef.ActionPrototype); if (!actionUid.HasValue) return; - vampire.Comp.UnlockedPowers.Add(power, actionUid.Value); + vampire.Comp.UnlockedPowers.Add(powerDef.Type, actionUid.Value); } - //Play unlock sound - //Show popup + + if (!silent) + RaiseNetworkEvent(new VampireAbilityUnlockedEvent() { UnlockedAbility = powerDef.Type }, vampire); } - private void Screech(Entity vampire) + #region Other Powers + private void Screech(Entity vampire, float duration, DamageSpecifier? damage = null) { var transform = Transform(vampire.Owner); - foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Dynamic | LookupFlags.Static)) + foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries)) { if (HasComp(entity)) - return; + continue; + + if (HasComp(entity)) + continue; + + if (HasComp(entity)) + { + _stun.TryParalyze(entity, TimeSpan.FromSeconds(duration), false); + _chat.TryEmoteWithoutChat(entity, _scream, true); + } + + if (damage != null) + _damageableSystem.TryChangeDamage(entity, damage); } } - private void Glare(Entity vampire) + private void Glare(Entity vampire, EntityUid? target, float duration, DamageSpecifier? damage = null) + { + if (!target.HasValue) + return; + + if (HasComp(target)) + return; + + if (!HasComp(target)) + return; + + if (HasComp(target)) + return; + + if (HasComp(target)) + { + _stun.TryParalyze(vampire.Owner, TimeSpan.FromSeconds(duration), true); + _chat.TryEmoteWithoutChat(vampire.Owner, _scream, true); + if (damage != null) + _damageableSystem.TryChangeDamage(vampire.Owner, damage); + + return; + } + + _stun.TryParalyze(target.Value, TimeSpan.FromSeconds(duration), true); + } + private void PolymorphBat(Entity vampire) + { + _polymorph.PolymorphEntity(vampire, _vampireBat); + } + private void BloodSteal(Entity vampire) { var transform = Transform(vampire.Owner); - foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 2, LookupFlags.Dynamic)) + var targets = new HashSet(); + + foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic)) { - if (HasComp(entity)) - return; + if (entity == vampire.Owner) + continue; + + if (!HasComp(entity)) + continue; + + if (_rotting.IsRotten(entity)) + continue; if (HasComp(entity)) - return; + continue; + + if (!TryComp(entity, out var bloodstream) || bloodstream.BloodSolution == null) + continue; + + var victimBloodRemaining = bloodstream.BloodSolution.Value.Comp.Solution.Volume; + if (victimBloodRemaining <= 0) + continue; + + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, 20); //HARDCODE, 20u of blood per person per use + + targets.Add(entity); + + //Transfer 80% to the vampire + var bloodSolution = _solution.SplitSolution(bloodstream.BloodSolution.Value, volumeToConsume * 0.80); + //And spill 20% on the floor + _blood.TryModifyBloodLevel(entity, -(volumeToConsume * 0.2)); + + //Dont check this time, if we are full - just continue anyway + TryIngestBlood(vampire, bloodSolution); + + AddBlood(vampire, volumeToConsume * 0.80); + + _beam.TryCreateBeam(vampire, entity, "Lightning"); + + _popup.PopupEntity(Log.GetString("vampire-bloodsteal-other"), entity, entity, Shared.Popups.PopupType.LargeCaution); + } + + + + //Update abilities, add new unlocks + UpdateAbilities(vampire); + } + private void CloakOfDarkness(Entity vampire) + { + if (vampire.Comp.ActiveAbilities.Contains(VampirePowerKey.CloakOfDarkness)) + { + vampire.Comp.ActiveAbilities.Remove(VampirePowerKey.CloakOfDarkness); + _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.CloakOfDarkness], false); + RemComp(vampire); + RemComp(vampire); + RemComp(vampire); + _popup.PopupEntity(Loc.GetString("vampire-cloak-disable"), vampire, vampire); + } + else + { + vampire.Comp.ActiveAbilities.Add(VampirePowerKey.CloakOfDarkness); + _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.CloakOfDarkness], true); + EnsureComp(vampire); + EnsureComp(vampire); + EnsureComp(vampire); + _popup.PopupEntity(Loc.GetString("vampire-cloak-enable"), vampire, vampire); + } + } + #endregion - _flash.Flash(entity, null, null, 10, 0.8f); + #region Hypnotise + private bool TryHypnotise(Entity vampire, EntityUid? target, float duration, float delay) + { + if (target == null) + return false; + + var attempt = new FlashAttemptEvent(target.Value, vampire.Owner, vampire.Owner); + RaiseLocalEvent(target.Value, attempt, true); + + if (attempt.Cancelled) + return false; + + var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, TimeSpan.FromSeconds(delay), + new VampireHypnotiseEvent(duration), + eventTarget: vampire, + target: target, + used: target) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + MovementThreshold = 0.01f, + DistanceThreshold = 1.0f, + NeedHand = false, + }; + + if (_doAfter.TryStartDoAfter(doAfterEventArgs)) + { + _popup.PopupEntity(Loc.GetString("vampire-hypnotise-other"), target.Value, Shared.Popups.PopupType.SmallCaution); } + else + { + return false; + } + return true; + } + private void HypnotiseDoAfter(Entity vampire, ref VampireHypnotiseEvent args) + { + if (!args.Target.HasValue) + return; + + if (args.Cancelled) + return; + //Do checks + //Force sleep 30seconds + _statusEffects.TryAddStatusEffect(args.Target.Value, SleepStatusEffectKey, TimeSpan.FromSeconds(args.Duration), false); } + #endregion #region Deaths Embrace private void OnVampireStateChanged(EntityUid uid, VampireComponent component, MobStateChangedEvent args) { if (args.NewMobState == MobState.Dead) - OnUsePower(uid, component, new() { Type = VampirePowerKey.DeathsEmbrace }); + OnUseAreaPower(uid, component, new() { Type = VampirePowerKey.DeathsEmbrace }); } private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, EntGotInsertedIntoContainerMessage args) { if (TryComp(args.Container.Owner, out var coffinComp)) { component.HomeCoffin = args.Container.Owner; - var comp = new VampireHealingComponent { Damage = coffinComp.Damage }; - AddComp(args.Entity, comp); + EnsureComp(args.Entity); _popup.PopupEntity(Loc.GetString("vampire-deathsembrace-bind"), uid, uid); } } @@ -301,35 +544,43 @@ private bool TryMoveToCoffin(Entity vampire) if (!_entityStorage.CanInsert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage)) return false; + Spawn("Smoke", Transform(vampire).Coordinates); + _entityStorage.CloseStorage(vampire.Comp.HomeCoffin.Value, coffinEntityStorage); return _entityStorage.Insert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage); } - private void DoCoffinHeal(EntityUid vampireUid, VampireHealingComponent healingComponent) + private void DoCoffinHeal(Entity vampire) { - if (!_container.TryGetOuterContainer(vampireUid, Transform(vampireUid), out var container)) + if (!_container.TryGetOuterContainer(vampire.Owner, Transform(vampire.Owner), out var container)) return; if (!HasComp(container.Owner)) return; - _damageableSystem.TryChangeDamage(vampireUid, healingComponent.Damage, true, origin: container.Owner); + //Heal the vampire + if (!GetAbilityDefinition(vampire.Comp, VampirePowerKey.DeathsEmbrace, out var healing) || healing == null) + return; + + _damageableSystem.TryChangeDamage(vampire.Owner, healing.Damage, true, origin: container.Owner); - if (!TryComp(vampireUid, out var mobStateComponent)) + //If they are dead and we are below the death threshold - revive + if (!TryComp(vampire, out var mobStateComponent)) return; - if (!_mobState.IsDead(vampireUid, mobStateComponent)) + if (!_mobState.IsDead(vampire, mobStateComponent)) return; - if (!_mobThreshold.TryGetThresholdForState(vampireUid, MobState.Dead, out var threshold)) + if (!_mobThreshold.TryGetThresholdForState(vampire, MobState.Dead, out var threshold)) return; - if (!TryComp(vampireUid, out var damageableComponent)) + if (!TryComp(vampire, out var damageableComponent)) return; + //Should be around 150 total damage ish if (damageableComponent.TotalDamage < threshold * 0.75) { - _mobState.ChangeMobState(vampireUid, MobState.Critical, mobStateComponent, container.Owner); + _mobState.ChangeMobState(vampire, MobState.Critical, mobStateComponent, container.Owner); } } #endregion @@ -337,8 +588,19 @@ private void DoCoffinHeal(EntityUid vampireUid, VampireHealingComponent healingC #region Blood Drinking private void ToggleFangs(Entity vampire) { - vampire.Comp.FangsExtended = !vampire.Comp.FangsExtended; - var popupText = Loc.GetString(vampire.Comp.FangsExtended ? "vampire-fangs-extended" : "vampire-fangs-retracted"); + var popupText = string.Empty; + if (vampire.Comp.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs)) + { + vampire.Comp.ActiveAbilities.Remove(VampirePowerKey.ToggleFangs); + _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.ToggleFangs], false); + popupText = Loc.GetString("vampire-fangs-retracted"); + } + else + { + vampire.Comp.ActiveAbilities.Add(VampirePowerKey.ToggleFangs); + _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.ToggleFangs], true); + popupText = Loc.GetString("vampire-fangs-extended"); + } _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); } private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) @@ -349,32 +611,33 @@ private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent c if (args.Target == args.User) return; - args.Handled = TryDrink(args.Target, args.User); + if (!TryComp(args.User, out var vampireComponent)) + return; + + args.Handled = TryDrink((args.User, vampireComponent), args.Target); } - private bool TryDrink(EntityUid target, EntityUid drinker, VampireComponent? vampireComponent = null) + private bool TryDrink(Entity vampire, EntityUid target) { - if (!Resolve(drinker, ref vampireComponent, false)) - return false; //Do a precheck - if (!vampireComponent.FangsExtended) + if (!vampire.Comp.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs)) return false; - if (!_interaction.InRangeUnobstructed(drinker, target, popup: true)) + if (!_interaction.InRangeUnobstructed(vampire, target, popup: true)) return false; - if (_food.IsMouthBlocked(target, drinker)) + if (_food.IsMouthBlocked(target, vampire)) return false; if (_rotting.IsRotten(target)) { - _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), drinker, drinker, Shared.Popups.PopupType.SmallCaution); + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), vampire, vampire, Shared.Popups.PopupType.SmallCaution); return false; } - var doAfterEventArgs = new DoAfterArgs(EntityManager, drinker, vampireComponent.BloodDrainDelay, + var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, vampire.Comp.ChosenAbilityList.BloodDrainFrequency, new VampireDrinkBloodEvent(), - eventTarget: drinker, + eventTarget: vampire, target: target, used: target) { @@ -395,10 +658,13 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood if (!args.Target.HasValue) return; + if (args.Cancelled) + return; + if (_food.IsMouthBlocked(args.Target.Value, entity)) return; - if (!entity.Comp.FangsExtended) + if (!entity.Comp.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs)) return; if (_rotting.IsRotten(args.Target.Value)) @@ -410,9 +676,6 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood if (!TryComp(args.Target, out var targetBloodstream) || targetBloodstream == null || targetBloodstream.BloodSolution == null) return; - if (!_cachedPowers.TryGetValue(VampirePowerKey.DrinkBlood, out var def) || def == null) - return; - //Ensure there is enough blood to drain var victimBloodRemaining = targetBloodstream.BloodSolution.Value.Comp.Solution.Volume; if (victimBloodRemaining <= 0) @@ -420,47 +683,46 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood _popup.PopupEntity(Loc.GetString("vampire-blooddrink-empty"), entity.Owner, entity.Owner, Shared.Popups.PopupType.SmallCaution); return; } - var volumeToConsume = Math.Min((float)victimBloodRemaining.Value, def.ActivationCost); + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, entity.Comp.ChosenAbilityList.BloodDrainVolume); - if (!IngestBlood(entity, volumeToConsume)) - return; - - if (!_blood.TryModifyBloodLevel(args.Target.Value, -volumeToConsume)) - return; + args.Repeat = true; - _audio.PlayPvs(def.ActivationSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); + //Transfer 95% to the vampire + var bloodSolution = _solution.SplitSolution(targetBloodstream.BloodSolution.Value, volumeToConsume * 0.95); - if (HasComp(args.Target.Value)) + //Thou shall not feed upon the blood of the holy + if (HasComp(args.Target)) { - //Scream, do burn damage - //Thou shall not feed upon the blood of the holy - InjectHolyWater(entity, 5); - return; + bloodSolution.AddReagent(_holyWater.ID, 5); + _popup.PopupEntity(Loc.GetString("vampire-ingest-holyblood"), entity, entity, Shared.Popups.PopupType.LargeCaution); + args.Repeat = false; } - else + + if (!TryIngestBlood(entity, bloodSolution)) { - AddBlood(entity, volumeToConsume); + //Undo, put the blood back + _solution.AddSolution(targetBloodstream.BloodSolution.Value, bloodSolution); + args.Repeat = false; + return; } + //Slurp + _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); + + AddBlood(entity, volumeToConsume * 0.95); + //Update abilities, add new unlocks UpdateAbilities(entity); - args.Repeat = true; - } - - private void InjectHolyWater(Entity vampire, FixedPoint2 quantity) - { - var solution = new ReagentQuantity(holyWaterReagent.ID, quantity, null); - if (TryComp(vampire.Owner, out var bloodStream) && bloodStream.ChemicalSolution != null) - { - _solution.TryAddReagent(bloodStream.ChemicalSolution.Value, solution, out _); - } + //And spill 5% on the floor + _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume * 0.05)); } - private bool IngestBlood(Entity vampire, float quantity) + private bool TryIngestBlood(Entity vampire, Solution ingestedSolution, bool force = false) { + //Get all stomaches if (TryComp(vampire.Owner, out var body) && _body.TryGetBodyOrganComponents(vampire.Owner, out var stomachs, body)) { - var ingestedSolution = new Solution(bloodReagent.ID, quantity); + //Pick the first one var firstStomach = stomachs.FirstOrNull(stomach => _stomach.CanTransferSolution(stomach.Comp.Owner, ingestedSolution, stomach.Comp)); if (firstStomach == null) { @@ -468,24 +730,44 @@ private bool IngestBlood(Entity vampire, float quantity) _popup.PopupEntity(Loc.GetString("vampire-full-stomach"), vampire.Owner, vampire.Owner, Shared.Popups.PopupType.SmallCaution); return false; } - _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, ingestedSolution, firstStomach.Value.Comp); - return true; + //Fill the stomach with that delicious blood + return _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, ingestedSolution, firstStomach.Value.Comp); } //No stomach return false; } - private void AddBlood(Entity vampire, float quantity) + private void AddBlood(Entity vampire, FixedPoint2 quantity) { - vampire.Comp.TotalBloodDrank += quantity; - vampire.Comp.AvailableBlood += quantity; + vampire.Comp.TotalBloodDrank += quantity.Float(); + vampire.Comp.AvailableBlood += quantity.Float(); } #endregion - private void DoSpaceDamage(EntityUid vampireUid, VampireComponent component) + private bool GetAbilityDefinition(VampireComponent component, VampirePowerKey key, out VampireAbilityEntry? vampireAbilityEntry) { - _damageableSystem.TryChangeDamage(vampireUid, spaceDamage, true, origin: vampireUid); - _popup.PopupEntity(Loc.GetString("vampire-startlight-burning"), vampireUid, vampireUid, Shared.Popups.PopupType.LargeCaution); + if (component.ChosenAbilityList == null) + { + vampireAbilityEntry = null; + return false; + } + + if (component.ChosenAbilityList.AbilitiesByKey.TryGetValue(key, out var entry)) + { + vampireAbilityEntry = entry; + return true; + } + + vampireAbilityEntry = null; + return false; + } + private void DoSpaceDamage(Entity vampire) + { + if (!GetAbilityDefinition(vampire.Comp, VampirePowerKey.StellarWeakness, out var def) || def == null) + return; + + _damageableSystem.TryChangeDamage(vampire, def.Damage, true, origin: vampire); + _popup.PopupEntity(Loc.GetString("vampire-startlight-burning"), vampire, vampire, Shared.Popups.PopupType.LargeCaution); } private bool IsInSpace(EntityUid vampireUid) { @@ -501,85 +783,23 @@ private bool IsInSpace(EntityUid vampireUid) return tileRef.Tile.IsEmpty; } - /// - /// Convert the players body into a vampire - /// Alternative to this would be creating a dedicated vampire race - /// But i want the player to look 'normal' and keep the same customisations as the non vampire player - /// - /// Which entity to convert - private void ConvertBody(EntityUid vampire) - { - var metabolizerTypes = new HashSet() { "bloodsucker", "vampire" }; //Heal from drinking blood, and be damaged by drinking holy water - var specialDigestion = new EntityWhitelist() { Tags = new() { "Pill" } }; //Restrict Diet - if (!TryComp(vampire, out var bodyComponent)) - return; + private void CachePowers() + { + var tempDict = new Dictionary(); - //Add vampire and bloodsucker to all metabolizing organs - //And restrict diet to Pills (and liquids) - foreach(var organ in _body.GetBodyOrgans(vampire, bodyComponent)) + var abilityLists = _prototypeManager.EnumeratePrototypes(); + var listAbilities = new Dictionary(); + foreach (var abilityList in abilityLists) { - if (TryComp(organ.Id, out var metabolizer)) + tempDict.Add(abilityList.ID, abilityList); + foreach (var ability in abilityList.Abilities) { - if (TryComp(organ.Id, out var stomachComponent)) - { - //Override the stomach, prevents humans getting sick when ingesting blood - _metabolism.SetMetabolizerTypes(metabolizer, metabolizerTypes); - _stomach.SetSpecialDigestible(stomachComponent, specialDigestion); - } - else - { - //Otherwise just add the metabolizers on - var tempMetabolizer = metabolizer.MetabolizerTypes ?? new HashSet(); - foreach (var t in metabolizerTypes) - tempMetabolizer.Add(t); - - _metabolism.SetMetabolizerTypes(metabolizer, tempMetabolizer); - } + listAbilities.TryAdd(ability.Type, ability); } + abilityList.AbilitiesByKey = listAbilities.ToFrozenDictionary(); } - //Take damage from holy water splash - if (TryComp(vampire, out var reactive)) - { - if (reactive.ReactiveGroups == null) - reactive.ReactiveGroups = new(); - - reactive.ReactiveGroups.Add("Unholy", new() { ReactionMethod.Touch }); - } - - DamageSpecifier meleeDamage = new(_prototypeManager.Index("Slash"), FixedPoint2.New(10)); - - //Extra melee power - if (TryComp(vampire, out var melee)) - { - melee.Damage = meleeDamage; - melee.Animation = "WeaponArcSlash"; - melee.HitSound = new SoundPathSpecifier("/Audio/Weapons/slash.ogg"); - } - } - //Remove weakeness to holy items - private void MakeImmuneToHoly(EntityUid vampire) - { - if (!TryComp(vampire, out var bodyComponent)) - return; - - if (TryComp(vampire, out var reactive)) - { - if (reactive.ReactiveGroups == null) - return; - - reactive.ReactiveGroups.Remove("Unholy"); - } - } - private void CachePowers() - { - var tempDict = new Dictionary(); - foreach (var power in _prototypeManager.EnumeratePrototypes()) - { - tempDict.Add(power.Key, power); - } - - _cachedPowers = tempDict.ToFrozenDictionary(); + _cachedAbilityLists = tempDict.ToFrozenDictionary(); } } diff --git a/Content.Shared/Vampire/Components/CoffinComponent.cs b/Content.Shared/Vampire/Components/CoffinComponent.cs deleted file mode 100644 index b957304649c..00000000000 --- a/Content.Shared/Vampire/Components/CoffinComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Content.Shared.Damage; - -namespace Content.Shared.Vampire.Components -{ - [RegisterComponent] - public sealed partial class CoffinComponent : Component - { - [DataField("damage", required: true)] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier Damage = default!; - } -} diff --git a/Content.Shared/Vampire/Components/VampireComponent.cs b/Content.Shared/Vampire/Components/VampireComponent.cs deleted file mode 100644 index 9dda6d4d9f0..00000000000 --- a/Content.Shared/Vampire/Components/VampireComponent.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Content.Shared.Damage; -using Content.Shared.FixedPoint; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using System; -using System.Collections.Frozen; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Content.Shared.Vampire.Components; - -[RegisterComponent] -public sealed partial class VampireComponent : Component -{ - [DataField, ViewVariables(VVAccess.ReadWrite)] - public bool FangsExtended = false; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float AvailableBlood = default!; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float TotalBloodDrank = default!; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan SpaceDamageInterval = TimeSpan.FromSeconds(1); - - public double NextSpaceDamageTick = 0f; - - public EntityUid? HomeCoffin = default!; - - //Abilities - public Dictionary UnlockedPowers = new(); - - //Blood Drinking - public TimeSpan BloodDrainDelay = TimeSpan.FromSeconds(1); -} - -[Prototype("vampirePower")] -public sealed partial class VampirePowerPrototype : IPrototype -{ - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - - public VampirePowerKey Key => Enum.Parse(ID); - - [DataField] - public readonly string? ActionPrototype; - - [DataField] - public readonly string? ActivationEffect; - - [DataField] - public readonly SoundSpecifier? ActivationSound; - - [DataField] - public readonly int ActivationCost = 0; - - [DataField] - public readonly int UnlockRequirement = 0; - - [DataField] - public readonly bool UsableWhileCuffed = true; - - [DataField] - public readonly bool UsableWhileStunned = true; - - [DataField] - public readonly bool UsableWhileMuffled = true; - - public EntityUid? Action; - - /*public VampirePowerPrototype(string? actionPrototype, string? activationEffect, SoundSpecifier? activationSound, int unlockCost, int activationCost, bool usableWhileCuffed, bool usableWhileStunned, bool usableWhileMuffled) - { - ActionPrototype = actionPrototype; - ActivationEffect = activationEffect; - ActivationSound = activationSound; - UnlockCost = unlockCost; - ActivationCost = activationCost; - UsableWhileCuffed = usableWhileCuffed; - UsableWhileStunned = usableWhileStunned; - UsableWhileMuffled = usableWhileMuffled; - }*/ -} - -[Serializable, NetSerializable] -public enum VampirePowerKey : byte -{ - ToggleFangs, - Glare, - DeathsEmbrace, - DrinkBlood, - Screech -} diff --git a/Content.Shared/Vampire/Components/VampireHealingComponent.cs b/Content.Shared/Vampire/Components/VampireHealingComponent.cs deleted file mode 100644 index dd08dd5f28f..00000000000 --- a/Content.Shared/Vampire/Components/VampireHealingComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Content.Shared.Damage; - -namespace Content.Shared.Vampire.Components -{ - [RegisterComponent] - public sealed partial class VampireHealingComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier Damage = default!; - - public double NextHealTick = 0; - - public TimeSpan HealTickInterval = TimeSpan.FromSeconds(1); - } -} diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs new file mode 100644 index 00000000000..03e9de497e2 --- /dev/null +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -0,0 +1,166 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Stealth.Components; +using Content.Shared.Whitelist; +using JetBrains.Annotations; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared.Vampire.Components; + +[RegisterComponent] +public sealed partial class VampireComponent : Component +{ + /// + /// How much blood is available for abilities + /// + [ViewVariables(VVAccess.ReadWrite)] + public float AvailableBlood = default!; + + /// + /// Total blood drank, counter for end of round screen + /// + [ViewVariables(VVAccess.ReadWrite)] + public float TotalBloodDrank = default!; + + /// + /// How long till we apply another tick of space damage + /// + [ViewVariables(VVAccess.ReadWrite)] + public double NextSpaceDamageTick = 0f; + + /// + /// Uid of the last coffin the vampire slept in + /// TODO: UI prompt client side to set this + /// + [ViewVariables(VVAccess.ReadWrite)] + public EntityUid? HomeCoffin = default!; + + /// + /// Which ability list has the vampire chosen + /// TODO: Add ability lists + /// + [ViewVariables(VVAccess.ReadWrite)] + public VampireAbilityListPrototype ChosenAbilityList = default!; + + [ViewVariables(VVAccess.ReadWrite)] + public HashSet ActiveAbilities = new(); + /// + /// All unlocked abilities + /// + public Dictionary UnlockedPowers = new(); + + public SoundSpecifier BloodDrainSound = new SoundPathSpecifier("/Audio/Items/drink.ogg", new AudioParams() { Volume = -3f }); +} + +[RegisterComponent] +public sealed partial class UnholyComponent : Component +{ +} +[RegisterComponent] +public sealed partial class CoffinComponent : Component +{ +} +[RegisterComponent] +public sealed partial class VampireHealingComponent : Component +{ + public double NextHealTick = 0; +} + +[RegisterComponent] +public sealed partial class VampireSealthComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public float NextStealthTick = 0; +} + +[Prototype("vampireAbilityList")] +public sealed partial class VampireAbilityListPrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + [DataField] + public List Abilities = new(); + + /// + /// For quick reference, populated at system init + /// + public FrozenDictionary AbilitiesByKey = default!; + + [DataField(required: true)] + public DamageSpecifier SpaceDamage = default!; + + [DataField(required: true)] + public DamageSpecifier MeleeDamage = default!; + + [DataField(required: true)] + public DamageSpecifier CoffinHealing = default!; + + [DataField] + public float BloodDrainVolume = 5; + + [DataField] + public float BloodDrainFrequency = 1; + + [DataField] + public float SpaceDamageFrequency = 2; + + [DataField] + public float StealthBloodCost = 5; + + [DataField] + public EntityWhitelist AcceptableFoods = new EntityWhitelist() { Tags = new() { "Pill" } }; +} + +[DataDefinition] +public sealed partial class VampireAbilityEntry +{ + [DataField] + public string? ActionPrototype = default!; + + [DataField] + public int BloodUnlockRequirement = 0; + [DataField] + public float ActivationCost = 0; + [DataField] + public bool UsableWhileCuffed = true; + [DataField] + public bool UsableWhileStunned = true; + [DataField] + public bool UsableWhileMuffled = true; + [DataField(required: true)] + public VampirePowerKey Type = default!; + [DataField] + public string ActivationEffect = default!; + [DataField] + public DamageSpecifier Damage = default!; + [DataField] + public float Duration = 0; + [DataField] + public float Delay = 0; +} + +[Serializable, NetSerializable] +public enum VampirePowerKey : byte +{ + ToggleFangs, + Glare, + DeathsEmbrace, + Screech, + Hypnotise, + BatForm, + NecroticTouch, + BloodSteal, + CloakOfDarkness, + StellarWeakness +} diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index c4ffd82f986..a33f5fe45a3 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -7,12 +7,12 @@ namespace Content.Shared.Vampire; public sealed partial class VampireUseAreaPowerEvent : InstantActionEvent { - [DataField("type")] + [DataField] public VampirePowerKey Type; }; public sealed partial class VampireUseTargetedPowerEvent : EntityTargetActionEvent { - [DataField("type")] + [DataField] public VampirePowerKey Type; }; @@ -21,3 +21,21 @@ public sealed partial class VampireDrinkBloodEvent : DoAfterEvent { public override DoAfterEvent Clone() => this; } + +[Serializable, NetSerializable] +public sealed partial class VampireHypnotiseEvent : DoAfterEvent +{ + [DataField] + public float Duration = 0; + + public VampireHypnotiseEvent(float duration) + { + Duration = duration; + } + public override DoAfterEvent Clone() => this; +} +[Serializable, NetSerializable] +public sealed partial class VampireAbilityUnlockedEvent : EntityEventArgs +{ + public VampirePowerKey UnlockedAbility = default!; +} diff --git a/Resources/Locale/en-US/Vampires/vampires.ftl b/Resources/Locale/en-US/Vampires/vampires.ftl index e5710094798..f051277d2f4 100644 --- a/Resources/Locale/en-US/Vampires/vampires.ftl +++ b/Resources/Locale/en-US/Vampires/vampires.ftl @@ -1,6 +1,6 @@ vampires-title = Vampires -vampire-fangs-extended-examine = You see a glint of sharp teeth +vampire-fangs-extended-examine = You see a glint of [color=white]sharp teeth[/color] vampire-fangs-extended = You extend your fangs vampire-fangs-retracted = You retract your fangs @@ -10,6 +10,17 @@ vampire-blooddrink-rotted = Their body is rotting and their blood tainted vampire-startlight-burning = You feel your skin burn in the light of a thousand suns vampire-not-enough-blood = You dont have enough blood +vampire-cuffed = You need your hands free! +vampire-stunned = You cant concentrate enough! +vampire-muffled = Your mouth is muzzled vampire-full-stomach = You are bloated with blood -vampire-deathsembrace-bind = Feels like home \ No newline at end of file +vampire-deathsembrace-bind = Feels like home + +vampire-ingest-holyblood = Your mouth burns! + +vampire-cloak-enable = You wrap shadows around your form +vampire-cloak-disable = You release your grip on the shadows + +vampire-bloodsteal-other = You feel blood being ripped from your body! +vampire-hypnotise-other = {CAPITALIZE(THE($user))} stares deeply into {THE($target)}'s eyes! \ No newline at end of file diff --git a/Resources/Locale/en-US/chapel/bible.ftl b/Resources/Locale/en-US/chapel/bible.ftl index c59492b70a6..191aa870a7f 100644 --- a/Resources/Locale/en-US/chapel/bible.ftl +++ b/Resources/Locale/en-US/chapel/bible.ftl @@ -3,6 +3,9 @@ bible-heal-success-others = {CAPITALIZE(THE($user))} hits {THE($target)} with {T bible-heal-success-none-self = You hit {THE($target)} with {THE($bible)}, but they have no wounds you can heal! bible-heal-success-none-others = {CAPITALIZE(THE($user))} hits {THE($target)} with {THE($bible)}! +bible-damage-unholy-self = You hit {THE($target)} with {THE($bible)}, and they are burned by a flash of holy fire! +bible-damage-unholy-others = {CAPITALIZE(THE($user))} hits {THE($target)} with {THE($bible)}, and they are burned by a flash of holy fire! + bible-heal-fail-self = You hit {THE($target)} with {THE($bible)}, and it lands with a sad thwack, dazing {OBJECT($target)}! bible-heal-fail-others = {CAPITALIZE(THE($user))} hits {THE($target)} with {THE($bible)}, and it lands with a sad thack, dazing {OBJECT($target)}! bible-sizzle = The book sizzles in your hands! diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index c175f237f2a..c70068ab6b3 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -6,85 +6,126 @@ components: - type: InstantAction checkCanInteract: false + priority: 1 icon: sprite: Interface/Actions/actions_vampire.rsi - state: drainblood + state: fangs_retracted + iconOn: + sprite: Interface/Actions/actions_vampire.rsi + state: fangs_extended event: - !type:VampireUsePowerEvent + !type:VampireUseAreaPowerEvent type: ToggleFangs - type: entity - id: ActionVampireDeathsEmbrace - name: Deaths Embrace - description: Return to your coffin + id: ActionVampireGlare + name: Glare + description: Strike fear into their souls + noSpawn: true + components: + - type: EntityTargetAction + priority: 2 + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: glare + sound: !type:SoundPathSpecifier + path: /Audio/Effects/Vampire/glare.ogg + event: + !type:VampireUseTargetedPowerEvent + type: Glare + useDelay: 60 + +- type: entity + id: ActionVampireScreech + name: Screech + description: Your enemies shall kneel noSpawn: true components: - type: InstantAction checkCanInteract: false + priority: 2 icon: sprite: Interface/Actions/actions_vampire.rsi - state: deathsembrace + state: screech sound: !type:SoundPathSpecifier - path: /Audio/Effects/explosionsmallfar.ogg + path: /Audio/Effects/Vampire/screech_tone.ogg event: - !type:VampireUsePowerEvent - type: DeathsEmbrace + !type:VampireUseAreaPowerEvent + type: Screech useDelay: 60 - type: entity - id: ActionVampireGlare - name: Glare - description: Strike fear into their souls + id: ActionVampireBloodSteal + name: Blood Steal + description: It's not theirs, it's yours noSpawn: true components: - type: InstantAction checkCanInteract: false + priority: 2 icon: sprite: Interface/Actions/actions_vampire.rsi - state: glare + state: bloodsteal sound: !type:SoundPathSpecifier - path: /Audio/Effects/Vampire/glare.ogg + path: /Audio/Effects/demon_consume.ogg event: - !type:VampireUsePowerEvent - type: Glare + !type:VampireUseAreaPowerEvent + type: BloodSteal useDelay: 60 - type: entity - id: ActionVampireScreech - name: Screech - description: Crush your enemies willpower + id: ActionVampireHypnotise + name: Hypnotise + description: Come with me, little lamb + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + priority: 2 + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: hypnotise + event: + !type:VampireUseTargetedPowerEvent + type: Hypnotise + useDelay: 300 + +- type: entity + id: ActionVampireBatform + name: Toggle Bat Form + description: Assume your bat form noSpawn: true components: - type: InstantAction checkCanInteract: false + priority: 2 icon: sprite: Interface/Actions/actions_vampire.rsi - state: screech + state: batform sound: !type:SoundPathSpecifier - path: /Audio/Effects/Vampire/screech_tone.ogg + path: /Audio/Effects/teleport_arrival.ogg event: - !type:VampireUsePowerEvent - type: Screech - useDelay: 60 + !type:VampireUseAreaPowerEvent + type: Batform + useDelay: 30 -#- type: entity -# id: ActionSmite -# name: Smite -# description: Instantly gibs a target. -# noSpawn: true -# components: -# - type: EntityTargetAction -# useDelay: 60 -# itemIconStyle: BigAction -# whitelist: -# components: -# - Body -# canTargetSelf: false -# interactOnMiss: false -# sound: !type:SoundPathSpecifier -# path: /Audio/Magic/disintegrate.ogg -# icon: -# sprite: Objects/Magic/magicactions.rsi -# state: gib -# event: !type:SmiteSpellEvent -# speech: action-speech-spell-smite \ No newline at end of file +- type: entity + id: ActionVampireCloakOfDarkness + name: Cloak of Darkness + description: Hide yourself from mortal eyes + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + priority: 2 + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: cloakofdarkness + event: + !type:VampireUseAreaPowerEvent + type: CloakOfDarkness + useDelay: 10 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Effects/vampire.yml b/Resources/Prototypes/Entities/Effects/vampire.yml new file mode 100644 index 00000000000..6db9ba6dc23 --- /dev/null +++ b/Resources/Prototypes/Entities/Effects/vampire.yml @@ -0,0 +1,25 @@ +- type: entity + name: Blood Drain + id: beam_blooddrain + components: + - type: Sprite + sprite: /Textures/Effects/lightning.rsi + drawdepth: Effects + layers: + - state: "lightning_1" + shader: unshaded + - type: Physics + canCollide: false + - type: PointLight + enabled: true + color: "#800000" + radius: 3.5 + softness: 1 + autoRot: true + castShadows: false + - type: Beam + - type: TimedDespawn + lifetime: 3 + - type: Tag + tags: + - HideContextMenu \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index fef4d47be03..c95ae6152c9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -18,6 +18,12 @@ damageOnUntrainedUse: ## What a non-chaplain takes when attempting to heal someone groups: Burn: 10 + damageOnUnholyUse: ## What an unholy creature takes when picking up the bible + groups: + Burn: 30 + damageUnholy: ## What damage is dealt when a chaplain hits an unholy creature + groups: + Burn: 20 - type: Prayable bibleUserOnly: true - type: Summonable @@ -65,6 +71,14 @@ damageOnUntrainedUse: types: Caustic: 50 + damageOnUnholyUse: ## What an unholy creature takes when picking up the bible + groups: + Brute: -15 + Burn: -15 + damageUnholy: ## What damage is dealt when a chaplain hits an unholy creature + groups: + Brute: -15 + Burn: -15 failChance: 0 locPrefix: "necro" healSound: "/Audio/Effects/lightburn.ogg" diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml index 16ca8114bad..c2f12053345 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml @@ -471,12 +471,6 @@ - !type:DoActsBehavior acts: [ "Destruction" ] - type: Coffin - damage: - groups: - Burn: -2 - Toxin: -2 - Airloss: -2 - Brute: -2 - type: Construction graph: CrateCoffin node: cratecoffin diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml index 46590248aec..b6fed13b64d 100644 --- a/Resources/Prototypes/Polymorphs/polymorph.yml +++ b/Resources/Prototypes/Polymorphs/polymorph.yml @@ -148,3 +148,10 @@ revertOnDeath: true revertOnCrit: true duration: 20 + +#Vampire +- type: polymorph + id: VampireBat + entity: MobBat + revertOnDeath: true + revertOnCrit: true \ No newline at end of file diff --git a/Resources/Prototypes/Vampire/powers.yml b/Resources/Prototypes/Vampire/powers.yml index 6b2769b0b1f..3023fffe81c 100644 --- a/Resources/Prototypes/Vampire/powers.yml +++ b/Resources/Prototypes/Vampire/powers.yml @@ -1,31 +1,91 @@ -- type: vampirePower - id: ToggleFangs - actionPrototype: ActionVampireToggleFangs - -- type: vampirePower - id: DrinkBlood - activationSound: - path: /Audio/Items/drink.ogg - usableWhileMuffled: false - activationCost: 5 #Actually how much we drink from the victim - -- type: vampirePower - id: Glare - actionPrototype: ActionVampireGlare - activationSound: - path: /Audio/Effects/Vampire/glare.ogg - -- type: vampirePower - id: DeathsEmbrace - actionPrototype: ActionVampireDeathsEmbrace - activationSound: - path: /Audio/Effects/explosionsmallfar.ogg - -- type: vampirePower - id: Screech - actionPrototype: ActionVampireScreech - activationSound: - path: /Audio/Effects/Vampire/screech_tone.ogg - activationCost: 25 - unlockRequirement: 50 - usableWhileMuffled: false \ No newline at end of file +- type: vampireAbilityList + id: Default + + spaceDamage: + types: + Burn: 2.5 + + meleeDamage: + types: + Slash: 10 + + coffinHealing: + groups: + Brute: 6 + Toxin: 4 + Genetic: 2 + types: + Burn: 1 + Shock: 2 + Cold: 2 + Airloss: 2 + Bloodloss: 2 + + # How much blood per mouthfull + bloodDrainVolume: 5 + #How frequenty per mountfull + bloodDrainFrequency: 1 + + acceptableFoods: + tags: + - Pill + + abilities: + - actionPrototype: ActionVampireToggleFangs + bloodUnlockRequirement: 0 + type: ToggleFangs + + - actionPrototype: ActionVampireGlare + bloodUnlockRequirement: 0 + type: Glare + duration: 10 + + - actionPrototype: ActionVampireHypnotise + bloodUnlockRequirement: 0 + type: Hypnotise + duration: 60 + delay: 5 + + - actionPrototype: ActionVampireBatform + bloodUnlockRequirement: 30 + type: Batform + usableWhileStunned: false + usableWhileCuffed: false + activationCost: 20 + + - actionPrototype: ActionVampireBloodSteal + bloodUnlockRequirement: 30 + type: BloodSteal + usableWhileStunned: false + usableWhileCuffed: false + activationCost: 20 + + - actionPrototype: ActionVampireScreech + bloodUnlockRequirement: 50 + activationCost: 10 + type: Screech + usableWhileMuffled: false + duration: 3 + damage: + types: + Blunt: 10 + Structural: 40 + + #Passive ability, heals and returns to coffin on death + - type: DeathsEmbrace + bloodUnlockRequirement: 150 + activationCost: 100 + activationEffect: Smoke + + #Passive ability, burn damage outside station + - type: StellarWeakness + bloodUnlockRequirement: 0 + damage: + types: + Burn: 2.5 + + - actionPrototype: ActionVampireCloakOfDarkness + bloodUnlockRequirement: 300 + activationCost: 20 + type: CloakOfDarkness + usableWhileStunned: false \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/batform.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/batform.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe2d43a4e8a529582016accb8c88e9f9380e9b7 GIT binary patch literal 1119 zcmV-l1fctgP)iC3u&0^=e?kw}pM zR{<%iIiOxzltZr^BjtkHL!=zjQ>98(TdB&aLNy{%19hcVfuLklXfUE~pqSz{N!dEA zH?^^;-NQJO&DxlxN%OR_XJ%)<_vU}|Mr$Y(3d?i-7cQCc!j?!R0PO7SknXo%xjbFd z%<^16fXf#%SH5V%%QND}?RFc6hK6+XKd${kw$@CdIQfsNZi%hX*5U;K} z&RELi;6VDOWwtbS0M<8R04!BCWKhj2JkL8Ej!HQwOZU2*`^_))4S;zr`fyLj%7(Nb zYq+Q5n;&Oc)%{EkbP7^3@)z(@pziV!FFo81Cu#dWQP;m2prSc&W?K+|Sp5 zk3M*pbjQD(ps(y4+ubYxxUm#1UuZAg7;QlUsMj3uvf&T-y$~b1z>DL6tf<^!GXS!W zCk|}#k_`ZG>hXe1<5nt_qU#?eWg!dhZj`wfn*s)b_EX2nt2_5%Q-DE?0N6|yR8=M8 z@3d$fL>YTAYLhdW-cGsJQ2Fjapz)C*wG$_|Q=J-@g5ag2Lb)*wa({NC*Gg%0T2iTif1Mi z0$>7UMYVIRtf=mCEIY^AITk=Jmjlq*+4+5eqFpnc0jrxj|SuNL)xYS3W6ZZvKI9U>p-sg z9e^2T5`HKYBH@R_VE|6MZDm6`-N>JA!1w(MXlip@r%<0U&pq=~RPnelW@FVFQ8 lx^^r7*+fyK?a9f>e*yB`=iESpsZ{^~002ovPDHLkV1in64V(Y~ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/bloodsteal.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/bloodsteal.png new file mode 100644 index 0000000000000000000000000000000000000000..2631d8c0636eb726ae0a4afed036f4547bab6f57 GIT binary patch literal 756 zcmVgsU7kIN~VyhL+dUcI@BFZi|HT@ zp(gHR7o6*0zVn@QI?GC9@<1T#PWQg=y?1A1FveJ~Q^elE8mm(LYNNocEhK%9&4GgYZxIb$PPj}X~(baI`;h*Cz2V;=ueNh5-K>36CJ{+ zjz#SIulRj`+U8vcJ6EYNU>9lybfncx1$VOf5V^d#Ar%pXNE|9aMLv^xM%J9P0DoRg zbJ9Z2EGvm9fHamh48SZSCybwCN1uUeroufI3z{h~_g<7<=Xp?-bKOC-N<=EqoU}qV zh~889NtobwDx6{g zi->jPH~Sojb247Fdr1KboGOID91Pe+EYBB_OA6>hNQK1dI8N6~WnedTjKk?x?2eZq z2Y|F?S=tFnx7X4NxURd@i%GgA+}-&r7x47aC)lekEM@C`e6Cr@H`KV+8BEWYe;Wwo}t$% ma&l7RI?(NQrQatfC-?`w);LEZ^QWBv0000W?KBZGqJA!RE#bqnc5G=x@_Sg?ysMJm-#@ebca zan;zxPPWhC@zwX~eLmkGPw(kmrBa!nt5=p&I5ix{0nl!@>2ZDa;_7;d&Ck^VtStHW zA6&xOfn?U!))EwZFFULHmSrUpi9{k1Gh$=!EUWt6-#==armCu{s_Uy4DG6b0Z0y=q z{&(XWVt-eCUBaaQV>g$}rBW$D5PFT+n8{?K+3%iT@DaS&dZGU`p2=i>`{U!at9)*L zH-Y~ctW+x9f8YRU{Q3;QB)%VkC9KteI{B`^5rw8{mpRuojWJd(mnBIe zZ2+RKe|!7(ptC5O&Gx%ynXVHBfb)jIEQ`h)>9nFKO~U|yf#DzbU}kZaF8u;j@(6|@ zD@wCakQD_$v)LrSTrB=$nr@>JnkE1Wq8A_F5#e!(e!jk`s%mjzs<<%aV6R^+ajl8)^Qpe?`idy4875_7#0fxG#|mFwc$S^8D3Owq5=4D{z1%2x&-wm` z0yWm<(YP-U_xJO-FU@c1+uCvvkH@1H8?~g3oO^l)Alb5IYilXSJrtm-G8^|$fVP(6 zE&Y3X2XHtXI2;a@?uJ9t8xz<`6P=x%GFl`(y%%*|(zl${cG%9@3v&RB|5!@->o-(C zppOYankPm=S~{BF!Q=7Z^?FG$WOqZ7jI=@}U$r$iMHN<0A^Ihe&){mWRa zR@E0PuGMOd7n|O1gB(4Q&$<5PggKG0;im|E^lm=IML8xA_YY|_I#+QIr%_&(Z;l|N z3I`}Jea@`5K6dptIDIrn;Y97$^kO1`-K8J0XHSJGM&YhLejR^?z11L*2o)t(pUdoh zwQ_@g?cTd2Xm6UqUU*31CR=}J>gI%*KwgdwM}D4ia=Bb|x?DK&`?$MMxykAQF7ryo z^XBj|Ja*+?!(Mny;Q@H=*qGYtAlgYYiZDHh)#_KP@9uVi^z*>t$DVsnTaGkJttfX; z-B*>I2Vi<}zc~|t)#Ar$@zdRP0+;JJ0JU`o5(%sYJ)AoJg-+lzLjEa05(-S=o14*b zHQg&pS179))_0UZMv3V!w(B=C^qK*SJxnN|34ZE?RtIhEpYpp{X{O@VOu zzG4_&^AidN0jRFb22jD{&FFXX%e8;?`4$siG**HFsi_vy(lP Date: Sat, 13 Jan 2024 19:21:54 +0000 Subject: [PATCH 04/67] Remove necromicon healing --- .../Prototypes/Entities/Objects/Specific/Chapel/bibles.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index c95ae6152c9..bf73f26f10b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -73,12 +73,10 @@ Caustic: 50 damageOnUnholyUse: ## What an unholy creature takes when picking up the bible groups: - Brute: -15 - Burn: -15 + Caustic: 50 damageUnholy: ## What damage is dealt when a chaplain hits an unholy creature groups: - Brute: -15 - Burn: -15 + Caustic: 20 failChance: 0 locPrefix: "necro" healSound: "/Audio/Effects/lightburn.ogg" From bf66e69dc957e1af3e5ee91e6a3126bf1b113d10 Mon Sep 17 00:00:00 2001 From: Rainfey Date: Sat, 13 Jan 2024 20:22:51 +0000 Subject: [PATCH 05/67] oops --- Content.Server/Vampire/VampireSystem.cs | 2 +- .../Prototypes/Entities/Objects/Specific/Chapel/bibles.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 33ac9df91b0..c315e6ff50c 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -434,7 +434,7 @@ private void BloodSteal(Entity vampire) _beam.TryCreateBeam(vampire, entity, "Lightning"); - _popup.PopupEntity(Log.GetString("vampire-bloodsteal-other"), entity, entity, Shared.Popups.PopupType.LargeCaution); + _popup.PopupEntity(Loc.GetString("vampire-bloodsteal-other"), entity, entity, Shared.Popups.PopupType.LargeCaution); } diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index bf73f26f10b..3e5d6af93ec 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -72,11 +72,11 @@ types: Caustic: 50 damageOnUnholyUse: ## What an unholy creature takes when picking up the bible - groups: + types: Caustic: 50 damageUnholy: ## What damage is dealt when a chaplain hits an unholy creature groups: - Caustic: 20 + types: 20 failChance: 0 locPrefix: "necro" healSound: "/Audio/Effects/lightburn.ogg" From adfdfb607fe4d64593bffec4c4f0ccd995fa28da Mon Sep 17 00:00:00 2001 From: Rainfey Date: Wed, 17 Jan 2024 13:19:29 +0000 Subject: [PATCH 06/67] Continuing --- Content.Server/Bed/Sleep/SleepingSystem.cs | 3 + .../Store/Events/StorePurchasedActionEvent.cs | 9 + .../Store/Systems/StoreSystem.Ui.cs | 7 +- .../Vampire/VampireSystem.Abilities.cs | 582 +++++++++++++ .../Vampire/VampireSystem.Transform.cs | 100 ++- Content.Server/Vampire/VampireSystem.cs | 780 +++--------------- Content.Shared/Vampire/VampireComponent.cs | 155 +++- Content.Shared/Vampire/VampireEvents.cs | 28 +- Resources/Locale/en-US/Vampires/vampires.ftl | 4 +- Resources/Prototypes/Actions/vampire.yml | 118 ++- .../Prototypes/Catalog/vampire_catalog.yml | 96 +++ .../Entities/Mobs/Player/vampire.yml | 19 + .../Entities/Objects/Misc/heirloom.yml | 20 + .../Objects/Specific/Chapel/bibles.yml | 2 +- Resources/Prototypes/Store/categories.yml | 4 + Resources/Prototypes/Store/currency.yml | 6 + Resources/Prototypes/Vampire/powers.yml | 91 -- .../Actions/actions_vampire.rsi/meta.json | 3 + .../actions_vampire.rsi/summonpendant.png | Bin 0 -> 2239 bytes 19 files changed, 1176 insertions(+), 851 deletions(-) create mode 100644 Content.Server/Store/Events/StorePurchasedActionEvent.cs create mode 100644 Content.Server/Vampire/VampireSystem.Abilities.cs create mode 100644 Resources/Prototypes/Catalog/vampire_catalog.yml create mode 100644 Resources/Prototypes/Entities/Mobs/Player/vampire.yml create mode 100644 Resources/Prototypes/Entities/Objects/Misc/heirloom.yml delete mode 100644 Resources/Prototypes/Vampire/powers.yml create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/summonpendant.png diff --git a/Content.Server/Bed/Sleep/SleepingSystem.cs b/Content.Server/Bed/Sleep/SleepingSystem.cs index 4a6874bcccd..ce68b2e3433 100644 --- a/Content.Server/Bed/Sleep/SleepingSystem.cs +++ b/Content.Server/Bed/Sleep/SleepingSystem.cs @@ -152,6 +152,9 @@ private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEve /// private void OnInteractHand(EntityUid uid, SleepingComponent component, InteractHandEvent args) { + if (args.Handled) + return; + args.Handled = true; if (!TryWakeCooldown(uid)) diff --git a/Content.Server/Store/Events/StorePurchasedActionEvent.cs b/Content.Server/Store/Events/StorePurchasedActionEvent.cs new file mode 100644 index 00000000000..b91fe8ef4c1 --- /dev/null +++ b/Content.Server/Store/Events/StorePurchasedActionEvent.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Server.Store.Events; + +public record struct StorePurchasedActionEvent(EntityUid Purchaser, EntityUid Action); diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 32c9a050435..297d1bae4b7 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -4,6 +4,7 @@ using Content.Server.PDA.Ringer; using Content.Server.Stack; using Content.Server.Store.Components; +using Content.Server.Store.Events; using Content.Server.UserInterface; using Content.Shared.Database; using Content.Shared.FixedPoint; @@ -167,7 +168,11 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi if (!string.IsNullOrWhiteSpace(listing.ProductAction)) { // I guess we just allow duplicate actions? - _actions.AddAction(buyer, listing.ProductAction); + var actionUid = _actions.AddAction(buyer, listing.ProductAction); + + //Raise a purchase event for handling downstream + if (actionUid != null) + RaiseLocalEvent(uid, new StorePurchasedActionEvent(buyer, actionUid.Value)); } //broadcast event diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs new file mode 100644 index 00000000000..727924db044 --- /dev/null +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -0,0 +1,582 @@ +using Content.Server.Bible.Components; +using Content.Server.Body.Components; +using Content.Server.Flash; +using Content.Server.Flash.Components; +using Content.Server.Speech.Components; +using Content.Server.Storage.Components; +using Content.Server.Store.Components; +using Content.Shared.Bed.Sleep; +using Content.Shared.Body.Components; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chemistry.Components; +using Content.Shared.Cuffs.Components; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.Flash; +using Content.Shared.Humanoid; +using Content.Shared.Interaction; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Popups; +using Content.Shared.Stealth.Components; +using Content.Shared.Stunnable; +using Content.Shared.Vampire; +using Content.Shared.Vampire.Components; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.Utility; + +namespace Content.Server.Vampire; + +public sealed partial class VampireSystem +{ + /// + /// Upon using any power that does not require a target + /// + private void OnUseSelfPower(EntityUid uid, VampireComponent component, VampireSelfPowerEvent args) => TriggerPower((uid, component), args.Type, args.Details); + /// + /// Upon using any power that requires a target + /// + private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireTargetedPowerEvent args) => TriggerPower((uid, component), args.Type, args.Details, args.Target); + private void TriggerPower(Entity vampire, VampirePowerKey powerType, VampirePowerDetails def, EntityUid? target = null) + { + if (!IsAbilityUnlocked(vampire, powerType)) + return; + + if (def.ActivationCost > 0 && def.ActivationCost > GetBloodEssence(vampire)) + { + _popup.PopupClient(Loc.GetString("vampire-not-enough-blood"), vampire, vampire, PopupType.MediumCaution); + return; + } + + //Block if we are cuffed + if (!def.UsableWhileCuffed && TryComp(vampire, out var cuffable) && !cuffable.CanStillInteract) + { + _popup.PopupEntity(Loc.GetString("vampire-cuffed"), vampire, vampire, PopupType.MediumCaution); + return; + } + + //Block if we are stunned + if (!def.UsableWhileStunned && HasComp(vampire)) + { + _popup.PopupEntity(Loc.GetString("vampire-stunned"), vampire, vampire, PopupType.MediumCaution); + return; + } + + //Block if we are muzzled - so far only one item does this? + if (!def.UsableWhileMuffled && TryComp(vampire, out var accent) && accent.Accent.Equals("mumble")) + { + _popup.PopupEntity(Loc.GetString("vampire-muffled"), vampire, vampire, PopupType.MediumCaution); + return; + } + + var success = true; + + //TODO: Rewrite when a magic effect system is introduced (like reagents) + switch (powerType) + { + case VampirePowerKey.ToggleFangs: + { + ToggleFangs(vampire); + break; + } + case VampirePowerKey.DeathsEmbrace: + { + success = TryMoveToCoffin(vampire); + break; + } + case VampirePowerKey.Glare: + { + Glare(vampire, target, def.Duration, def.Damage); + break; + } + case VampirePowerKey.Screech: + { + Screech(vampire, def.Duration, def.Damage); + break; + } + case VampirePowerKey.Polymorph: + { + PolymorphSelf(vampire, def.PolymorphTarget); + break; + } + case VampirePowerKey.Hypnotise: + { + success = TryHypnotise(vampire, target, def.Duration, def.DoAfterDelay); + break; + } + case VampirePowerKey.BloodSteal: + { + BloodSteal(vampire); + break; + } + case VampirePowerKey.CloakOfDarkness: + { + CloakOfDarkness(vampire, def.Upkeep, -1, 1); + break; + } + default: + break; + } + + if (!success) + return; + + AddBloodEssence(vampire, -def.ActivationCost); + + _action.StartUseDelay(GetAbilityEntity(vampire, powerType)); + } + + #region Other Powers + /// + /// Spawn and bind the pendant if one does not already exist, otherwise just summon to the vampires hand + /// + private void OnSummonHeirloom(EntityUid uid, VampireComponent component, VampireSummonHeirloomEvent args) + { + if (!component.Heirloom.HasValue + || LifeStage(component.Heirloom.Value) >= EntityLifeStage.Terminating) + { + //If the pendant does not exist, or has been deleted - spawn one + component.Heirloom = Spawn(VampireComponent.HeirloomProto); + + if (TryComp(component.Heirloom, out var heirloomComponent)) + heirloomComponent.Owner = uid; + + if (TryComp(component.Heirloom, out var storeComponent)) + //Tie the store balance to the vampires balance + storeComponent.Balance = component.Balance; + } + //Move to players hands + _hands.PickupOrDrop(uid, component.Heirloom.Value); + } + private void Screech(Entity vampire, TimeSpan? duration, DamageSpecifier? damage = null) + { + var transform = Transform(vampire.Owner); + + foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries)) + { + if (HasComp(entity)) + continue; + + if (HasComp(entity)) + continue; + + if (HasComp(entity)) + { + _stun.TryParalyze(entity, duration ?? TimeSpan.FromSeconds(3), false); + _chat.TryEmoteWithoutChat(entity, _prototypeManager.Index(VampireComponent.ScreamEmoteProto), true); + } + + if (damage != null) + _damageableSystem.TryChangeDamage(entity, damage); + } + } + private void Glare(Entity vampire, EntityUid? target, TimeSpan? duration, DamageSpecifier? damage = null) + { + if (!target.HasValue) + return; + + if (HasComp(target)) + return; + + if (!HasComp(target)) + return; + + if (HasComp(target)) + return; + + if (HasComp(target)) + { + _stun.TryParalyze(vampire, duration ?? TimeSpan.FromSeconds(3), true); + _chat.TryEmoteWithoutChat(vampire.Owner, _prototypeManager.Index(VampireComponent.ScreamEmoteProto), true); + if (damage != null) + _damageableSystem.TryChangeDamage(vampire.Owner, damage); + return; + } + + _stun.TryParalyze(target.Value, duration ?? TimeSpan.FromSeconds(3), true); + } + private void PolymorphSelf(Entity vampire, string? polymorphTarget) + { + if (polymorphTarget == null) + return; + + _polymorph.PolymorphEntity(vampire, polymorphTarget); + } + private void BloodSteal(Entity vampire) + { + var transform = Transform(vampire.Owner); + + var targets = new HashSet(); + + foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic)) + { + if (entity == vampire.Owner) + continue; + + if (!HasComp(entity)) + continue; + + if (_rotting.IsRotten(entity)) + continue; + + if (HasComp(entity)) + continue; + + if (!TryComp(entity, out var bloodstream) || bloodstream.BloodSolution == null) + continue; + + var victimBloodRemaining = bloodstream.BloodSolution.Value.Comp.Solution.Volume; + if (victimBloodRemaining <= 0) + continue; + + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, 20); //HARDCODE, 20u of blood per person per use + + targets.Add(entity); + + //Transfer 80% to the vampire + var bloodSolution = _solution.SplitSolution(bloodstream.BloodSolution.Value, volumeToConsume * 0.80); + //And spill 20% on the floor + _blood.TryModifyBloodLevel(entity, -(volumeToConsume * 0.2)); + + //Dont check this time, if we are full - just continue anyway + TryIngestBlood(vampire, bloodSolution); + + AddBloodEssence(vampire, volumeToConsume * 0.80); + + _beam.TryCreateBeam(vampire, entity, "Lightning"); + + _popup.PopupEntity(Loc.GetString("vampire-bloodsteal-other"), entity, entity, Shared.Popups.PopupType.LargeCaution); + } + + + + //Update abilities, add new unlocks + //UpdateAbilities(vampire); + } + private void CloakOfDarkness(Entity vampire, float upkeep, float passiveVisibilityRate, float movementVisibilityRate) + { + var actionEntity = GetAbilityEntity(vampire.Comp, VampirePowerKey.CloakOfDarkness); + if (IsAbilityActive(vampire.Comp, VampirePowerKey.CloakOfDarkness)) + { + SetAbilityActive(vampire.Comp, VampirePowerKey.CloakOfDarkness, false); + _action.SetToggled(actionEntity, false); + RemComp(vampire); + RemComp(vampire); + RemComp(vampire); + _popup.PopupEntity(Loc.GetString("vampire-cloak-disable"), vampire, vampire); + } + else + { + SetAbilityActive(vampire.Comp, VampirePowerKey.CloakOfDarkness, true); + _action.SetToggled(actionEntity, true); + EnsureComp(vampire); + var stealthMovement = EnsureComp(vampire); + stealthMovement.PassiveVisibilityRate = passiveVisibilityRate; + stealthMovement.MovementVisibilityRate = movementVisibilityRate; + var vampireStealth = EnsureComp(vampire); + vampireStealth.Upkeep = upkeep; + _popup.PopupEntity(Loc.GetString("vampire-cloak-enable"), vampire, vampire); + } + } + #endregion + + #region Hypnotise + private bool TryHypnotise(Entity vampire, EntityUid? target, TimeSpan? duration, TimeSpan? delay) + { + if (target == null) + return false; + + var attempt = new FlashAttemptEvent(target.Value, vampire.Owner, vampire.Owner); + RaiseLocalEvent(target.Value, attempt, true); + + if (attempt.Cancelled) + return false; + + var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, delay ?? TimeSpan.FromSeconds(5), + new VampireHypnotiseEvent() { Duration = duration }, + eventTarget: vampire, + target: target, + used: target) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + MovementThreshold = 0.01f, + DistanceThreshold = 1.0f, + NeedHand = false, + }; + + if (_doAfter.TryStartDoAfter(doAfterEventArgs)) + { + _popup.PopupEntity(Loc.GetString("vampire-hypnotise-other"), target.Value, Shared.Popups.PopupType.SmallCaution); + } + else + { + return false; + } + return true; + } + private void HypnotiseDoAfter(Entity vampire, ref VampireHypnotiseEvent args) + { + if (!args.Target.HasValue) + return; + + if (args.Cancelled) + return; + + _statusEffects.TryAddStatusEffect(args.Target.Value, VampireComponent.SleepStatusEffectProto, args.Duration ?? TimeSpan.FromSeconds(30), false); + } + #endregion + + #region Deaths Embrace + /// + /// When the vampire dies, attempt to activate the Deaths Embrace power + /// + private void OnVampireStateChanged(EntityUid uid, VampireComponent component, MobStateChangedEvent args) + { + if (args.OldMobState != MobState.Dead && args.NewMobState == MobState.Dead) + OnUseSelfPower(uid, component, new() { Type = VampirePowerKey.DeathsEmbrace, Details = new() { ActivationCost = 100 } }); + } + /// + /// When the vampire is inserted into a container (ie locker, crate etc) check for a coffin, and bind their home to it + /// + private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, EntGotInsertedIntoContainerMessage args) + { + if (HasComp(args.Container.Owner)) + { + component.HomeCoffin = args.Container.Owner; + EnsureComp(args.Entity); + _popup.PopupEntity(Loc.GetString("vampire-deathsembrace-bind"), uid, uid); + } + } + /// + /// When leaving a container, remove the healing component + /// + private void OnRemovedFromContainer(EntityUid uid, VampireComponent component, EntGotRemovedFromContainerMessage args) + { + //Presence check is done upstream + RemComp(args.Entity); + } + /// + /// Attempt to move the vampire to their bound coffin + /// + private bool TryMoveToCoffin(Entity vampire) + { + if (!vampire.Comp.HomeCoffin.HasValue) + return false; + + //Someone smashed your crib bro' + if (!Exists(vampire.Comp.HomeCoffin.Value) + || LifeStage(vampire.Comp.HomeCoffin.Value) == EntityLifeStage.Terminating + || LifeStage(vampire.Comp.HomeCoffin.Value) == EntityLifeStage.Deleted) + { + vampire.Comp.HomeCoffin = null; + return false; + } + + //Your crib.. is not a crib, how? + if (!TryComp(vampire.Comp.HomeCoffin, out var coffinEntityStorage)) + { + vampire.Comp.HomeCoffin = null; + return false; + } + + //I guess its full? + if (!_entityStorage.CanInsert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage)) + return false; + + Spawn("Smoke", Transform(vampire).Coordinates); + + _entityStorage.CloseStorage(vampire.Comp.HomeCoffin.Value, coffinEntityStorage); + + return _entityStorage.Insert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage); + } + /// + /// Heal the vampire while they are in the coffin + /// Revive them if they are dead and below a ~150ish damage + /// + private void DoCoffinHeal(EntityUid vampire, VampireHealingComponent healing) + { + _damageableSystem.TryChangeDamage(vampire, healing.Healing, true, origin: vampire); + + //If they are dead and we are below the death threshold - revive + if (!TryComp(vampire, out var mobStateComponent)) + return; + + if (!_mobState.IsDead(vampire, mobStateComponent)) + return; + + if (!_mobThreshold.TryGetThresholdForState(vampire, MobState.Dead, out var threshold)) + return; + + if (!TryComp(vampire, out var damageableComponent)) + return; + + //Should be around 150 total damage ish + if (damageableComponent.TotalDamage < threshold * 0.75) + { + _mobState.ChangeMobState(vampire, MobState.Critical, mobStateComponent, vampire); + } + } + #endregion + + #region Blood Drinking + /// + /// Check and start drinking blood from a humanoid + /// + private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) + { + if (args.Handled) + return; + + if (args.Target == args.User) + return; + + if (!TryComp(args.User, out var vampireComponent)) + return; + + args.Handled = TryDrink((args.User, vampireComponent), args.Target, TimeSpan.FromSeconds(1)); + } + private void ToggleFangs(Entity vampire) + { + var actionEntity = GetAbilityEntity(vampire.Comp, VampirePowerKey.ToggleFangs); + var popupText = string.Empty; + if (IsAbilityActive(vampire, VampirePowerKey.ToggleFangs)) + { + SetAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs, false); + _action.SetToggled(actionEntity, false); + popupText = Loc.GetString("vampire-fangs-retracted"); + } + else + { + SetAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs, true); + _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.ToggleFangs], true); + popupText = Loc.GetString("vampire-fangs-extended"); + } + _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); + } + + private bool TryDrink(Entity vampire, EntityUid target, TimeSpan doAfterDelay) + { + //Do a precheck + if (!IsAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs)) + return false; + + if (!_interaction.InRangeUnobstructed(vampire, target, popup: true)) + return false; + + if (_food.IsMouthBlocked(target, vampire)) + return false; + + if (_rotting.IsRotten(target)) + { + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), vampire, vampire, Shared.Popups.PopupType.SmallCaution); + return false; + } + + var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, doAfterDelay, + new VampireDrinkBloodEvent() { Volume = 5 }, + eventTarget: vampire, + target: target, + used: target) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + MovementThreshold = 0.01f, + DistanceThreshold = 1.0f, + NeedHand = false, + Hidden = true + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + return true; + } + private void DrinkDoAfter(Entity entity, ref VampireDrinkBloodEvent args) + { + if (args.Cancelled) + return; + + if (!args.Target.HasValue) + return; + + if (!IsAbilityActive(entity.Comp, VampirePowerKey.ToggleFangs)) + return; + + if (_food.IsMouthBlocked(args.Target.Value, entity)) + return; + + //Thou shall not feed upon the blood of the holy + if (HasComp(args.Target)) + { + _damageableSystem.TryChangeDamage(entity, VampireComponent.HolyDamage, true); + _popup.PopupEntity(Loc.GetString("vampire-ingest-holyblood"), entity, entity, PopupType.LargeCaution); + args.Repeat = false; + } + + if (_rotting.IsRotten(args.Target.Value)) + { + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), args.User, args.User, PopupType.SmallCaution); + return; + } + + if (!TryComp(args.Target, out var targetBloodstream) || targetBloodstream == null || targetBloodstream.BloodSolution == null) + return; + + //Ensure there is enough blood to drain + var victimBloodRemaining = targetBloodstream.BloodSolution.Value.Comp.Solution.Volume; + if (victimBloodRemaining <= 0) + { + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-empty"), entity.Owner, entity.Owner, PopupType.SmallCaution); + return; + } + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume); + + args.Repeat = true; + + //Transfer 95% to the vampire + var bloodSolution = _solution.SplitSolution(targetBloodstream.BloodSolution.Value, volumeToConsume * 0.95); + + if (!TryIngestBlood(entity, bloodSolution)) + { + //Undo, put the blood back + _solution.AddSolution(targetBloodstream.BloodSolution.Value, bloodSolution); + args.Repeat = false; + return; + } + + //Slurp + _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); + + AddBloodEssence(entity, volumeToConsume * 0.95); + + //And spill 5% on the floor + _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume * 0.05)); + } + /// + /// Attempt to insert the solution into the first stomach that has space available + /// + private bool TryIngestBlood(Entity vampire, Solution ingestedSolution, bool force = false) + { + //Get all stomaches + if (TryComp(vampire.Owner, out var body) && _body.TryGetBodyOrganComponents(vampire.Owner, out var stomachs, body)) + { + //Pick the first one that has space available + var firstStomach = stomachs.FirstOrNull(stomach => _stomach.CanTransferSolution(stomach.Comp.Owner, ingestedSolution, stomach.Comp)); + if (firstStomach == null) + { + //We are full + _popup.PopupEntity(Loc.GetString("vampire-full-stomach"), vampire.Owner, vampire.Owner, PopupType.SmallCaution); + return false; + } + //Fill the stomach with that delicious blood + return _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, ingestedSolution, firstStomach.Value.Comp); + } + + //No stomach + return false; + } + #endregion +} diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index 81d64720bde..f3786360a13 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -1,12 +1,20 @@ +using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Body.Systems; +using Content.Server.Store.Components; +using Content.Server.Store.Systems; +using Content.Server.Temperature.Components; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Rotting; using Content.Shared.Body.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; +using Content.Shared.Store; using Content.Shared.Vampire.Components; using Content.Shared.Weapons.Melee; using Content.Shared.Whitelist; +using Robust.Server.GameObjects; using Robust.Shared.Audio; using System; using System.Collections.Generic; @@ -19,17 +27,51 @@ namespace Content.Server.Vampire; public sealed partial class VampireSystem { [Dependency] private readonly MetabolizerSystem _metabolism = default!; + [Dependency] private readonly StoreSystem _store = default!; /// - /// Convert the players body into a vampire - /// Alternative to this would be creating a dedicated vampire race - /// But i want the player to look 'normal' and keep the same customisations as the non vampire player + /// Convert the players into a vampire, all programatic because i dont want to replace the players body /// /// Which entity to convert - private void ConvertBody(EntityUid vampire, VampireAbilityListPrototype abilityList) + private void MakeVampire(EntityUid vampire) { - var metabolizerTypes = new HashSet() { "bloodsucker", "vampire" }; //Heal from drinking blood, and be damaged by drinking holy water - //var specialDigestion = new EntityWhitelist() { Tags = new() { "Pill" } }; //Restrict Diet + var vampireComponent = EnsureComp(vampire); + EnsureComp(vampire); + RemComp(vampire); + RemComp(vampire); + + if (TryComp(vampire, out var temperatureComponent)) + temperatureComponent.ColdDamageThreshold = Atmospherics.TCMB; + + //Extra melee power + if (TryComp(vampire, out var melee)) + { + melee.Damage = VampireComponent.MeleeDamage; + melee.Animation = "WeaponArcClaw"; + melee.HitSound = new SoundPathSpecifier("/Audio/Weapons/slash.ogg"); + } + + AddStartingAbilities(vampire, vampireComponent); + + var store = EnsureComp(vampire); + store.AccountOwner = vampire; + _store.InitializeFromPreset(VampireComponent.StorePresetProto, vampire, store); + + MakeVulnerableToHoly(vampire); + } + private void MakeVulnerableToHoly(EntityUid vampire) + { + //Take damage from holy water splash + if (TryComp(vampire, out var reactive)) + { + if (reactive.ReactiveGroups == null) + reactive.ReactiveGroups = new(); + + if (!reactive.ReactiveGroups.ContainsKey("Unholy")) + { + reactive.ReactiveGroups.Add("Unholy", new() { ReactionMethod.Touch }); + } + } if (!TryComp(vampire, out var bodyComponent)) return; @@ -43,42 +85,52 @@ private void ConvertBody(EntityUid vampire, VampireAbilityListPrototype abilityL if (TryComp(organ.Id, out var stomachComponent)) { //Override the stomach, prevents humans getting sick when ingesting blood - _metabolism.SetMetabolizerTypes(metabolizer, metabolizerTypes); - _stomach.SetSpecialDigestible(stomachComponent, abilityList.AcceptableFoods); + _metabolism.SetMetabolizerTypes(metabolizer, VampireComponent.Metabolizers); + _stomach.SetSpecialDigestible(stomachComponent, VampireComponent.AcceptableFoods); } else { - //Otherwise just add the metabolizers on + //Otherwise just add the metabolizers on - dont want to suffocate the vampires var tempMetabolizer = metabolizer.MetabolizerTypes ?? new HashSet(); - foreach (var t in metabolizerTypes) + foreach (var t in VampireComponent.Metabolizers) tempMetabolizer.Add(t); _metabolism.SetMetabolizerTypes(metabolizer, tempMetabolizer); } } } + } - //Take damage from holy water splash - if (TryComp(vampire, out var reactive)) - { - if (reactive.ReactiveGroups == null) - reactive.ReactiveGroups = new(); - - reactive.ReactiveGroups.Add("Unholy", new() { ReactionMethod.Touch }); - } - - //Extra melee power - if (TryComp(vampire, out var melee)) + private void AddStartingAbilities(EntityUid vampire, VampireComponent component) + { + foreach (var ability in VampireComponent.StartingAbilities) { - melee.Damage = abilityList.MeleeDamage; - melee.Animation = "WeaponArcSlash"; - melee.HitSound = new SoundPathSpecifier("/Audio/Weapons/slash.ogg"); + var action = _action.AddAction(vampire, ability); + if (action != null) + OnStorePurchase(vampire, action.Value); } } //Remove weakeness to holy items private void MakeImmuneToHoly(EntityUid vampire) { + if (!TryComp(vampire, out var bodyComponent)) + return; + + //Add vampire and bloodsucker to all metabolizing organs + //And restrict diet to Pills (and liquids) + foreach (var organ in _body.GetBodyOrgans(vampire, bodyComponent)) + { + if (TryComp(organ.Id, out var metabolizer)) + { + var currentMetabolizers = metabolizer.MetabolizerTypes; + if (currentMetabolizers == null) + continue; + currentMetabolizers.Remove("vampire"); + _metabolism.SetMetabolizerTypes(metabolizer, currentMetabolizers); + } + } + if (TryComp(vampire, out var reactive)) { if (reactive.ReactiveGroups == null) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index c315e6ff50c..26c1febee56 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -1,264 +1,90 @@ -using Content.Server.Actions; -using Content.Server.Atmos.Components; using Content.Server.Atmos.Rotting; using Content.Server.Beam; using Content.Server.Bed.Sleep; -using Content.Server.Bible.Components; -using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chat.Systems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Flash; -using Content.Server.Flash.Components; using Content.Server.Interaction; using Content.Server.Nutrition.EntitySystems; using Content.Server.Polymorph.Systems; -using Content.Server.Popups; using Content.Server.Speech.Components; -using Content.Server.Storage.Components; using Content.Server.Storage.EntitySystems; -using Content.Server.Temperature.Components; -using Content.Shared.Atmos.Rotting; -using Content.Shared.Bed.Sleep; -using Content.Shared.Body.Components; -using Content.Shared.Chat.Prototypes; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Reagent; +using Content.Server.Store.Components; +using Content.Server.Store.Events; +using Content.Shared.Actions; +using Content.Shared.Body.Systems; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Cuffs.Components; using Content.Shared.Damage; -using Content.Shared.Damage.Prototypes; using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.FixedPoint; -using Content.Shared.Flash; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Humanoid; using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; -using Content.Shared.Polymorph; +using Content.Shared.Popups; using Content.Shared.StatusEffect; -using Content.Shared.Stealth.Components; using Content.Shared.Stunnable; using Content.Shared.Vampire; using Content.Shared.Vampire.Components; -using Robust.Server.Containers; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Utility; -using System.Collections.Frozen; namespace Content.Server.Vampire; public sealed partial class VampireSystem : EntitySystem { [Dependency] private readonly FoodSystem _food = default!; + [Dependency] private readonly EntityStorageSystem _entityStorage = default!; + [Dependency] private readonly BloodstreamSystem _blood = default!; + [Dependency] private readonly RottingSystem _rotting = default!; + [Dependency] private readonly StomachSystem _stomach = default!; + [Dependency] private readonly PolymorphSystem _polymorph = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly BeamSystem _beam = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly ContainerSystem _container = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly IMapManager _mapMan = default!; - [Dependency] private readonly MapSystem _mapSystem = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly IMapManager _mapMan = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly EntityStorageSystem _entityStorage = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly ActionsSystem _action = default!; - [Dependency] private readonly BloodstreamSystem _blood = default!; - [Dependency] private readonly RottingSystem _rotting = default!; - [Dependency] private readonly BodySystem _body = default!; - [Dependency] private readonly StomachSystem _stomach = default!; - [Dependency] private readonly EntityLookupSystem _entityLookup = default!; - [Dependency] private readonly SolutionContainerSystem _solution = default!; + [Dependency] private readonly SharedActionsSystem _action = default!; + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; [Dependency] private readonly SharedStunSystem _stun = default!; - [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly PolymorphSystem _polymorph = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly BeamSystem _beam = default!; - - private FrozenDictionary _cachedAbilityLists = default!; - - [ValidatePrototypeId] - private const string SleepStatusEffectKey = "ForcedSleep"; - [ValidatePrototypeId] - private const string HolyWaterKey = "Holywater"; - [ValidatePrototypeId] - private const string VampireBatKey = "VampireBat"; - [ValidatePrototypeId] - private const string ScreamEmoteKey = "Scream"; - - private ReagentPrototype _holyWater = default!; - private PolymorphPrototype _vampireBat = default!; - private EmotePrototype _scream = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnInteractWithHumanoid, before: new[] { typeof(InteractionPopupSystem), typeof(SleepingSystem) }); + SubscribeLocalEvent(DrinkDoAfter); SubscribeLocalEvent(HypnotiseDoAfter); - + SubscribeLocalEvent(OnSummonHeirloom); SubscribeLocalEvent(OnInsertedIntoContainer); SubscribeLocalEvent(OnRemovedFromContainer); SubscribeLocalEvent(OnVampireStateChanged); - SubscribeLocalEvent(OnUseAreaPower); - SubscribeLocalEvent(OnUseTargetedPower); + SubscribeLocalEvent(OnUseSelfPower); + SubscribeLocalEvent(OnUseTargetedPower); SubscribeLocalEvent(OnExamined); - CachePowers(); - - //Local references - _holyWater = _prototypeManager.Index(HolyWaterKey); - _vampireBat = _prototypeManager.Index(VampireBatKey); - _scream = _prototypeManager.Index(ScreamEmoteKey); - } - - /// - /// Convert the entity into a vampire - /// - private void OnComponentInit(EntityUid uid, VampireComponent component, ComponentInit args) - { - //_solution.EnsureSolution(uid, component.BloodContainer); - RemComp(uid); - RemComp(uid); - EnsureComp(uid); - - if (TryComp(uid, out var temperatureComponent)) - temperatureComponent.ColdDamageThreshold = 0; - - //Hardcoding the default ability list - //TODO: Add client UI and multiple ability lists - component.ChosenAbilityList = _cachedAbilityLists["Default"]; - - ConvertBody(uid, component.ChosenAbilityList); - - UpdateAbilities((uid, component), true); - } - - private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) - { - if (component.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs) && args.IsInDetailsRange) - args.AddMarkup($"{Loc.GetString("vampire-fangs-extended-examine")}{Environment.NewLine}"); - } - - /// - /// Upon using any non targeted power - /// - private void OnUseAreaPower(EntityUid uid, VampireComponent component, VampireUseAreaPowerEvent args) - { - Entity vampire = (uid, component); - - TriggerPower(vampire, args.Type, null); - } - private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireUseTargetedPowerEvent args) - { - Entity vampire = (uid, component); - - TriggerPower(vampire, args.Type, args.Target); - } - - private void TriggerPower(Entity vampire, VampirePowerKey powerType, EntityUid? target) - { - if (!vampire.Comp.UnlockedPowers.ContainsKey(powerType)) - return; - - if (!GetAbilityDefinition(vampire.Comp, powerType, out var def) || def == null) - return; - - if (def.ActivationCost > 0 && def.ActivationCost > vampire.Comp.AvailableBlood) - { - _popup.PopupEntity(Loc.GetString("vampire-not-enough-blood"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); - return; - } - - //Block if we are cuffed - if (!def.UsableWhileCuffed && TryComp(vampire, out var cuffable) && !cuffable.CanStillInteract) - { - _popup.PopupEntity(Loc.GetString("vampire-cuffed"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); - return; - } - - //Block if we are stunned - if (!def.UsableWhileStunned && HasComp(vampire)) - { - _popup.PopupEntity(Loc.GetString("vampire-stunned"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); - return; - } - - //Block if we are muzzled - so far only one item does this? - if (!def.UsableWhileMuffled && TryComp(vampire, out var accent) && accent.Accent.Equals("mumble")) - { - _popup.PopupEntity(Loc.GetString("vampire-muffled"), vampire, vampire, Shared.Popups.PopupType.MediumCaution); - return; - } - - if (def.ActivationEffect != null) - Spawn(def.ActivationEffect, _transform.GetMapCoordinates(Transform(vampire.Owner))); - - var success = true; - - //TODO: Rewrite when a magic effect system is introduced (like reagents) - switch (powerType) - { - case VampirePowerKey.ToggleFangs: - { - ToggleFangs(vampire); - break; - } - case VampirePowerKey.DeathsEmbrace: - { - success = TryMoveToCoffin(vampire); - break; - } - case VampirePowerKey.Glare: - { - Glare(vampire, target, def.Duration, def.Damage); - break; - } - case VampirePowerKey.Screech: - { - Screech(vampire, def.Duration, def.Damage); - break; - } - case VampirePowerKey.BatForm: - { - PolymorphBat(vampire); - break; - } - case VampirePowerKey.Hypnotise: - { - success = TryHypnotise(vampire, target, def.Duration, def.Delay); - break; - } - case VampirePowerKey.BloodSteal: - { - BloodSteal(vampire); - break; - } - case VampirePowerKey.CloakOfDarkness: - { - CloakOfDarkness(vampire); - break; - } - default: - break; - } - - if (!success) - return; - - AddBlood(vampire, -def.ActivationCost); - - _action.StartUseDelay(vampire.Comp.UnlockedPowers[powerType]); + SubscribeLocalEvent(OnUseHeirloom); + SubscribeLocalEvent(OnStorePurchase); } /// @@ -268,30 +94,33 @@ public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var vampireComponent)) + var stealthQuery = EntityQueryEnumerator(); + while (stealthQuery.MoveNext(out var uid, out var vampire, out var stealth)) { - var vampire = (uid, vampireComponent); - - if (TryComp(uid, out var vampireSealthComponent)) + if (stealth.NextStealthTick <= 0) { - if (vampireSealthComponent.NextStealthTick <= 0) - { - vampireSealthComponent.NextStealthTick = 1; - AddBlood(vampire, vampireComponent.ChosenAbilityList.StealthBloodCost); - } - vampireSealthComponent.NextStealthTick -= frameTime; + stealth.NextStealthTick = 1; + if (!AddBloodEssence((uid, vampire), -stealth.Upkeep)) + RemCompDeferred(uid); } + stealth.NextStealthTick -= frameTime; + } - if (TryComp(uid, out var vampireHealingComponent)) + var healingQuery = EntityQueryEnumerator(); + while (healingQuery.MoveNext(out var uid, out var vampire, out var healing)) + { + if (healing.NextHealTick <= 0) { - if (vampireHealingComponent.NextHealTick <= 0) - { - vampireHealingComponent.NextHealTick = 1; - DoCoffinHeal(vampire); - } - vampireHealingComponent.NextHealTick -= frameTime; + healing.NextHealTick = 1; + DoCoffinHeal(uid, healing); } + healing.NextHealTick -= frameTime; + } + + /*var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var vampireComponent)) + { + var vampire = (uid, vampireComponent); if (IsInSpace(uid)) { @@ -302,473 +131,120 @@ public override void Update(float frameTime) } vampireComponent.NextSpaceDamageTick -= frameTime; } - } + }*/ } - - /// - /// Update which abilities are available based upon available blood - /// - private void UpdateAbilities(Entity vampire, bool silent = false) + private void OnComponentStartup(EntityUid uid, VampireComponent component, ComponentStartup args) { - foreach (var power in vampire.Comp.ChosenAbilityList.Abilities) - { - if (power.BloodUnlockRequirement <= vampire.Comp.AvailableBlood) - UnlockAbility(vampire, power, silent); - } + MakeVampire(uid); } - private void UnlockAbility(Entity vampire, VampireAbilityEntry powerDef, bool silent = false) - { - if (vampire.Comp.UnlockedPowers.ContainsKey(powerDef.Type)) - return; - if (powerDef.ActionPrototype == null) - { - //passive ability - vampire.Comp.UnlockedPowers.Add(powerDef.Type, null); - } - else - { - var actionUid = _action.AddAction(vampire.Owner, powerDef.ActionPrototype); - if (!actionUid.HasValue) - return; - vampire.Comp.UnlockedPowers.Add(powerDef.Type, actionUid.Value); - } - - if (!silent) - RaiseNetworkEvent(new VampireAbilityUnlockedEvent() { UnlockedAbility = powerDef.Type }, vampire); - } - - #region Other Powers - private void Screech(Entity vampire, float duration, DamageSpecifier? damage = null) + private void OnUseHeirloom(EntityUid uid, VampireHeirloomComponent component, UseInHandEvent args) { - var transform = Transform(vampire.Owner); - - foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries)) - { - if (HasComp(entity)) - continue; - - if (HasComp(entity)) - continue; - - if (HasComp(entity)) - { - _stun.TryParalyze(entity, TimeSpan.FromSeconds(duration), false); - _chat.TryEmoteWithoutChat(entity, _scream, true); - } - - if (damage != null) - _damageableSystem.TryChangeDamage(entity, damage); - } - } - private void Glare(Entity vampire, EntityUid? target, float duration, DamageSpecifier? damage = null) - { - if (!target.HasValue) + //Ensure the user is a vampire + if (!HasComp(args.User)) return; - if (HasComp(target)) + //Only allow the heirloom owner to use this - prevent stealing others blood essence + //TODO: Popup + if (component.Owner != args.User) return; - if (!HasComp(target)) - return; + //And open the UI + _store.ToggleUi(args.User, uid); + } - if (HasComp(target)) + private void OnStorePurchase(EntityUid uid, VampireHeirloomComponent component, ref StorePurchasedActionEvent ev) + => OnStorePurchase(ev.Purchaser, ev.Action); + private void OnStorePurchase(EntityUid purchaser, EntityUid purchasedAction) + { + if (!TryComp(purchaser, out var vampireComponent)) return; - if (HasComp(target)) + if (TryComp(purchasedAction, out var instantAction) && instantAction.Event != null && instantAction.Event is VampireSelfPowerEvent) { - _stun.TryParalyze(vampire.Owner, TimeSpan.FromSeconds(duration), true); - _chat.TryEmoteWithoutChat(vampire.Owner, _scream, true); - if (damage != null) - _damageableSystem.TryChangeDamage(vampire.Owner, damage); - + var vampirePower = instantAction.Event as VampireSelfPowerEvent; + vampireComponent.UnlockedPowers[vampirePower!.Type] = purchasedAction; return; } - - _stun.TryParalyze(target.Value, TimeSpan.FromSeconds(duration), true); - } - private void PolymorphBat(Entity vampire) - { - _polymorph.PolymorphEntity(vampire, _vampireBat); - } - private void BloodSteal(Entity vampire) - { - var transform = Transform(vampire.Owner); - - var targets = new HashSet(); - - foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic)) - { - if (entity == vampire.Owner) - continue; - - if (!HasComp(entity)) - continue; - - if (_rotting.IsRotten(entity)) - continue; - - if (HasComp(entity)) - continue; - - if (!TryComp(entity, out var bloodstream) || bloodstream.BloodSolution == null) - continue; - - var victimBloodRemaining = bloodstream.BloodSolution.Value.Comp.Solution.Volume; - if (victimBloodRemaining <= 0) - continue; - - var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, 20); //HARDCODE, 20u of blood per person per use - - targets.Add(entity); - - //Transfer 80% to the vampire - var bloodSolution = _solution.SplitSolution(bloodstream.BloodSolution.Value, volumeToConsume * 0.80); - //And spill 20% on the floor - _blood.TryModifyBloodLevel(entity, -(volumeToConsume * 0.2)); - - //Dont check this time, if we are full - just continue anyway - TryIngestBlood(vampire, bloodSolution); - - AddBlood(vampire, volumeToConsume * 0.80); - - _beam.TryCreateBeam(vampire, entity, "Lightning"); - - _popup.PopupEntity(Loc.GetString("vampire-bloodsteal-other"), entity, entity, Shared.Popups.PopupType.LargeCaution); - } - - - - //Update abilities, add new unlocks - UpdateAbilities(vampire); - } - private void CloakOfDarkness(Entity vampire) - { - if (vampire.Comp.ActiveAbilities.Contains(VampirePowerKey.CloakOfDarkness)) + if (TryComp(purchasedAction, out var targetAction) && targetAction.Event != null && targetAction.Event is VampireTargetedPowerEvent) { - vampire.Comp.ActiveAbilities.Remove(VampirePowerKey.CloakOfDarkness); - _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.CloakOfDarkness], false); - RemComp(vampire); - RemComp(vampire); - RemComp(vampire); - _popup.PopupEntity(Loc.GetString("vampire-cloak-disable"), vampire, vampire); - } - else - { - vampire.Comp.ActiveAbilities.Add(VampirePowerKey.CloakOfDarkness); - _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.CloakOfDarkness], true); - EnsureComp(vampire); - EnsureComp(vampire); - EnsureComp(vampire); - _popup.PopupEntity(Loc.GetString("vampire-cloak-enable"), vampire, vampire); + var vampirePower = targetAction.Event as VampireTargetedPowerEvent; + vampireComponent.UnlockedPowers[vampirePower!.Type] = purchasedAction; + return; } } - #endregion - #region Hypnotise - private bool TryHypnotise(Entity vampire, EntityUid? target, float duration, float delay) - { - if (target == null) - return false; - - var attempt = new FlashAttemptEvent(target.Value, vampire.Owner, vampire.Owner); - RaiseLocalEvent(target.Value, attempt, true); - - if (attempt.Cancelled) - return false; - - var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, TimeSpan.FromSeconds(delay), - new VampireHypnotiseEvent(duration), - eventTarget: vampire, - target: target, - used: target) - { - BreakOnUserMove = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - MovementThreshold = 0.01f, - DistanceThreshold = 1.0f, - NeedHand = false, - }; - if (_doAfter.TryStartDoAfter(doAfterEventArgs)) - { - _popup.PopupEntity(Loc.GetString("vampire-hypnotise-other"), target.Value, Shared.Popups.PopupType.SmallCaution); - } - else - { - return false; - } - return true; - } - private void HypnotiseDoAfter(Entity vampire, ref VampireHypnotiseEvent args) + /// + /// Called by the store when a new passive ability is purchased + /// + /// + /// + /// + /// + /*private void OnUnlockPassive(EntityUid uid, VampireComponent component, VampireUnlockPassiveEvent args) { - if (!args.Target.HasValue) - return; + throw new NotImplementedException(); + }*/ - if (args.Cancelled) - return; - //Do checks - //Force sleep 30seconds - _statusEffects.TryAddStatusEffect(args.Target.Value, SleepStatusEffectKey, TimeSpan.FromSeconds(args.Duration), false); - } - #endregion - #region Deaths Embrace - private void OnVampireStateChanged(EntityUid uid, VampireComponent component, MobStateChangedEvent args) + private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) { - if (args.NewMobState == MobState.Dead) - OnUseAreaPower(uid, component, new() { Type = VampirePowerKey.DeathsEmbrace }); + if (IsAbilityActive(component, VampirePowerKey.ToggleFangs) && args.IsInDetailsRange && !_food.IsMouthBlocked(uid)) + args.AddMarkup($"{Loc.GetString("vampire-fangs-extended-examine")}{Environment.NewLine}"); } - private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, EntGotInsertedIntoContainerMessage args) + private bool AddBloodEssence(Entity vampire, FixedPoint2 quantity) { - if (TryComp(args.Container.Owner, out var coffinComp)) - { - component.HomeCoffin = args.Container.Owner; - EnsureComp(args.Entity); - _popup.PopupEntity(Loc.GetString("vampire-deathsembrace-bind"), uid, uid); - } + vampire.Comp.TotalBloodDrank += quantity.Float(); + vampire.Comp.Balance[VampireComponent.CurrencyProto] += quantity; + return true; } - private void OnRemovedFromContainer(EntityUid uid, VampireComponent component, EntGotRemovedFromContainerMessage args) + private FixedPoint2 GetBloodEssence(Entity vampire) { - RemCompDeferred(args.Entity); + if (!TryComp(vampire, out var storeComp)) + return 0; + var currencies = storeComp.Balance; + if (!currencies.TryGetValue(VampireComponent.CurrencyProto, out var val)) + return 0; + return val; } - private bool TryMoveToCoffin(Entity vampire) - { - if (!vampire.Comp.HomeCoffin.HasValue) - return false; - if (!TryComp(vampire.Comp.HomeCoffin, out var coffinEntityStorage)) - return false; - - if (!_entityStorage.CanInsert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage)) - return false; - - Spawn("Smoke", Transform(vampire).Coordinates); - - _entityStorage.CloseStorage(vampire.Comp.HomeCoffin.Value, coffinEntityStorage); - - return _entityStorage.Insert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage); - } - private void DoCoffinHeal(Entity vampire) + private bool IsAbilityUnlocked(VampireComponent vampire, VampirePowerKey ability) { - if (!_container.TryGetOuterContainer(vampire.Owner, Transform(vampire.Owner), out var container)) - return; - - if (!HasComp(container.Owner)) - return; - - //Heal the vampire - if (!GetAbilityDefinition(vampire.Comp, VampirePowerKey.DeathsEmbrace, out var healing) || healing == null) - return; - - _damageableSystem.TryChangeDamage(vampire.Owner, healing.Damage, true, origin: container.Owner); - - //If they are dead and we are below the death threshold - revive - if (!TryComp(vampire, out var mobStateComponent)) - return; - - if (!_mobState.IsDead(vampire, mobStateComponent)) - return; - - if (!_mobThreshold.TryGetThresholdForState(vampire, MobState.Dead, out var threshold)) - return; - - if (!TryComp(vampire, out var damageableComponent)) - return; - - //Should be around 150 total damage ish - if (damageableComponent.TotalDamage < threshold * 0.75) - { - _mobState.ChangeMobState(vampire, MobState.Critical, mobStateComponent, container.Owner); - } + return vampire.UnlockedPowers.ContainsKey(ability); } - #endregion - - #region Blood Drinking - private void ToggleFangs(Entity vampire) + private bool IsAbilityActive(VampireComponent vampire, VampirePowerKey ability) { - var popupText = string.Empty; - if (vampire.Comp.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs)) - { - vampire.Comp.ActiveAbilities.Remove(VampirePowerKey.ToggleFangs); - _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.ToggleFangs], false); - popupText = Loc.GetString("vampire-fangs-retracted"); - } - else - { - vampire.Comp.ActiveAbilities.Add(VampirePowerKey.ToggleFangs); - _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.ToggleFangs], true); - popupText = Loc.GetString("vampire-fangs-extended"); - } - _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); - } - private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) - { - if (args.Handled) - return; - - if (args.Target == args.User) - return; - - if (!TryComp(args.User, out var vampireComponent)) - return; - - args.Handled = TryDrink((args.User, vampireComponent), args.Target); + return vampire.AbilityStates.Contains(ability); } - private bool TryDrink(Entity vampire, EntityUid target) + private bool SetAbilityActive(VampireComponent vampire, VampirePowerKey ability, bool active) { - - //Do a precheck - if (!vampire.Comp.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs)) - return false; - - if (!_interaction.InRangeUnobstructed(vampire, target, popup: true)) - return false; - - if (_food.IsMouthBlocked(target, vampire)) - return false; - - if (_rotting.IsRotten(target)) - { - _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), vampire, vampire, Shared.Popups.PopupType.SmallCaution); - return false; - } - - var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, vampire.Comp.ChosenAbilityList.BloodDrainFrequency, - new VampireDrinkBloodEvent(), - eventTarget: vampire, - target: target, - used: target) - { - BreakOnUserMove = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - MovementThreshold = 0.01f, - DistanceThreshold = 1.0f, - NeedHand = false, - Hidden = true - }; - - _doAfter.TryStartDoAfter(doAfterEventArgs); - return true; - } - private void DrinkDoAfter(Entity entity, ref VampireDrinkBloodEvent args) - { - if (!args.Target.HasValue) - return; - - if (args.Cancelled) - return; - - if (_food.IsMouthBlocked(args.Target.Value, entity)) - return; - - if (!entity.Comp.ActiveAbilities.Contains(VampirePowerKey.ToggleFangs)) - return; - - if (_rotting.IsRotten(args.Target.Value)) - { - _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), args.User, args.User, Shared.Popups.PopupType.SmallCaution); - return; - } - - if (!TryComp(args.Target, out var targetBloodstream) || targetBloodstream == null || targetBloodstream.BloodSolution == null) - return; - - //Ensure there is enough blood to drain - var victimBloodRemaining = targetBloodstream.BloodSolution.Value.Comp.Solution.Volume; - if (victimBloodRemaining <= 0) - { - _popup.PopupEntity(Loc.GetString("vampire-blooddrink-empty"), entity.Owner, entity.Owner, Shared.Popups.PopupType.SmallCaution); - return; - } - var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, entity.Comp.ChosenAbilityList.BloodDrainVolume); - - args.Repeat = true; - - //Transfer 95% to the vampire - var bloodSolution = _solution.SplitSolution(targetBloodstream.BloodSolution.Value, volumeToConsume * 0.95); - - //Thou shall not feed upon the blood of the holy - if (HasComp(args.Target)) - { - bloodSolution.AddReagent(_holyWater.ID, 5); - _popup.PopupEntity(Loc.GetString("vampire-ingest-holyblood"), entity, entity, Shared.Popups.PopupType.LargeCaution); - args.Repeat = false; - } - - if (!TryIngestBlood(entity, bloodSolution)) + if (active) { - //Undo, put the blood back - _solution.AddSolution(targetBloodstream.BloodSolution.Value, bloodSolution); - args.Repeat = false; - return; + return vampire.AbilityStates.Add(ability); } - - //Slurp - _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); - - AddBlood(entity, volumeToConsume * 0.95); - - //Update abilities, add new unlocks - UpdateAbilities(entity); - - //And spill 5% on the floor - _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume * 0.05)); - } - private bool TryIngestBlood(Entity vampire, Solution ingestedSolution, bool force = false) - { - //Get all stomaches - if (TryComp(vampire.Owner, out var body) && _body.TryGetBodyOrganComponents(vampire.Owner, out var stomachs, body)) + else { - //Pick the first one - var firstStomach = stomachs.FirstOrNull(stomach => _stomach.CanTransferSolution(stomach.Comp.Owner, ingestedSolution, stomach.Comp)); - if (firstStomach == null) - { - //We are full - _popup.PopupEntity(Loc.GetString("vampire-full-stomach"), vampire.Owner, vampire.Owner, Shared.Popups.PopupType.SmallCaution); - return false; - } - //Fill the stomach with that delicious blood - return _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, ingestedSolution, firstStomach.Value.Comp); + return vampire.AbilityStates.Remove(ability); } - - //No stomach - return false; } - private void AddBlood(Entity vampire, FixedPoint2 quantity) + private EntityUid? GetAbilityEntity(VampireComponent vampire, VampirePowerKey ability) { - vampire.Comp.TotalBloodDrank += quantity.Float(); - vampire.Comp.AvailableBlood += quantity.Float(); + if (IsAbilityUnlocked(vampire, ability)) + return vampire.UnlockedPowers[ability]; + return null; } - #endregion - private bool GetAbilityDefinition(VampireComponent component, VampirePowerKey key, out VampireAbilityEntry? vampireAbilityEntry) - { - if (component.ChosenAbilityList == null) - { - vampireAbilityEntry = null; - return false; - } - if (component.ChosenAbilityList.AbilitiesByKey.TryGetValue(key, out var entry)) - { - vampireAbilityEntry = entry; - return true; - } - vampireAbilityEntry = null; - return false; - } - private void DoSpaceDamage(Entity vampire) + /*private void DoSpaceDamage(Entity vampire) { if (!GetAbilityDefinition(vampire.Comp, VampirePowerKey.StellarWeakness, out var def) || def == null) return; _damageableSystem.TryChangeDamage(vampire, def.Damage, true, origin: vampire); _popup.PopupEntity(Loc.GetString("vampire-startlight-burning"), vampire, vampire, Shared.Popups.PopupType.LargeCaution); - } + }*/ private bool IsInSpace(EntityUid vampireUid) { var vampireTransform = Transform(vampireUid); @@ -782,24 +258,4 @@ private bool IsInSpace(EntityUid vampireUid) return tileRef.Tile.IsEmpty; } - - - private void CachePowers() - { - var tempDict = new Dictionary(); - - var abilityLists = _prototypeManager.EnumeratePrototypes(); - var listAbilities = new Dictionary(); - foreach (var abilityList in abilityLists) - { - tempDict.Add(abilityList.ID, abilityList); - foreach (var ability in abilityList.Abilities) - { - listAbilities.TryAdd(ability.Type, ability); - } - abilityList.AbilitiesByKey = listAbilities.ToFrozenDictionary(); - } - - _cachedAbilityLists = tempDict.ToFrozenDictionary(); - } } diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index 03e9de497e2..4480952e2ca 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -1,35 +1,66 @@ +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; using Content.Shared.FixedPoint; -using Content.Shared.Stealth.Components; +using Content.Shared.StatusEffect; +using Content.Shared.Store; using Content.Shared.Whitelist; -using JetBrains.Annotations; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using System; -using System.Collections.Frozen; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Content.Shared.Vampire.Components; [RegisterComponent] public sealed partial class VampireComponent : Component { + //Statics + [ValidatePrototypeId] + public static readonly string StorePresetProto = "StorePresetVampire"; + [ValidatePrototypeId] + public static readonly string CurrencyProto = "BloodEssence"; + [ValidatePrototypeId] + public static readonly string SleepStatusEffectProto = "ForcedSleep"; + [ValidatePrototypeId] + public static readonly string ScreamEmoteProto = "Scream"; + [ValidatePrototypeId] + public static readonly string HeirloomProto = "HeirloomeVampire"; + + public static readonly EntityWhitelist AcceptableFoods = new() + { + Tags = new() { "Pill" } + }; + public static readonly HashSet Metabolizers = new() + { + "bloodsucker", + "vampire" + }; + public static readonly DamageSpecifier MeleeDamage = new() + { + DamageDict = new Dictionary() { { "Slash", 10 } } + }; + public static readonly DamageSpecifier HolyDamage = new() + { + DamageDict = new Dictionary() { { "Burn", 10 } } + }; + public static readonly List StartingAbilities = new() + { + "ActionVampireSummonHeirloom", + "ActionVampireToggleFangs", + "ActionVampireGlare" + }; + /// - /// How much blood is available for abilities + /// Total blood drank, counter for end of round screen /// [ViewVariables(VVAccess.ReadWrite)] - public float AvailableBlood = default!; + public float TotalBloodDrank = 0; /// - /// Total blood drank, counter for end of round screen + /// How much blood per mouthful /// [ViewVariables(VVAccess.ReadWrite)] - public float TotalBloodDrank = default!; + public float MouthVolume = 0; /// /// How long till we apply another tick of space damage @@ -44,21 +75,53 @@ public sealed partial class VampireComponent : Component [ViewVariables(VVAccess.ReadWrite)] public EntityUid? HomeCoffin = default!; - /// - /// Which ability list has the vampire chosen - /// TODO: Add ability lists - /// - [ViewVariables(VVAccess.ReadWrite)] - public VampireAbilityListPrototype ChosenAbilityList = default!; [ViewVariables(VVAccess.ReadWrite)] - public HashSet ActiveAbilities = new(); + public HashSet AbilityStates = new(); /// /// All unlocked abilities /// public Dictionary UnlockedPowers = new(); - public SoundSpecifier BloodDrainSound = new SoundPathSpecifier("/Audio/Items/drink.ogg", new AudioParams() { Volume = -3f }); + /// + /// Link to the vampires heirloom + /// + public EntityUid? Heirloom = default!; + + /// + /// Current available balance, used to sync currency across heirlooms and add essence as we feed + /// + public Dictionary Balance = new() { { VampireComponent.CurrencyProto, 0 } }; + + public readonly SoundSpecifier BloodDrainSound = new SoundPathSpecifier("/Audio/Items/drink.ogg", new AudioParams() { Volume = -3f }); + public readonly SoundSpecifier AbilityPurchaseSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); +} + + +/// +/// Contains all details about the ability and its effects or restrictions +/// +[DataDefinition] +public sealed partial class VampirePowerDetails +{ + [DataField] + public float ActivationCost = 0; + [DataField] + public bool UsableWhileCuffed = true; + [DataField] + public bool UsableWhileStunned = true; + [DataField] + public bool UsableWhileMuffled = true; + [DataField] + public DamageSpecifier? Damage = default!; + [DataField] + public TimeSpan? Duration = TimeSpan.Zero; + [DataField] + public TimeSpan? DoAfterDelay = TimeSpan.Zero; + [DataField] + public string? PolymorphTarget = default!; + [DataField] + public float Upkeep = 0; } [RegisterComponent] @@ -70,9 +133,31 @@ public sealed partial class CoffinComponent : Component { } [RegisterComponent] +public sealed partial class VampireHeirloomComponent : Component +{ + //public EntityUid? Owner = default!; +} +[RegisterComponent] public sealed partial class VampireHealingComponent : Component { public double NextHealTick = 0; + + public DamageSpecifier Healing = new DamageSpecifier() + { + DamageDict = new Dictionary() + { + { "Blunt", 2 }, + { "Slash", 2 }, + { "Pierce", 2 }, + { "Heat", 1 }, + { "Cold", 2 }, + { "Shock", 2 }, + { "Caustic", 2 }, + { "Airloss", 2 }, + { "Bloodloss", 2 }, + { "Genetic", 2 } + } + }; } [RegisterComponent] @@ -80,9 +165,12 @@ public sealed partial class VampireSealthComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public float NextStealthTick = 0; + + [ViewVariables(VVAccess.ReadWrite)] + public float Upkeep = 0; } -[Prototype("vampireAbilityList")] +/*[Prototype("vampireAbilityList")] public sealed partial class VampireAbilityListPrototype : IPrototype { [ViewVariables] @@ -97,15 +185,15 @@ public sealed partial class VampireAbilityListPrototype : IPrototype /// public FrozenDictionary AbilitiesByKey = default!; - [DataField(required: true)] - public DamageSpecifier SpaceDamage = default!; - [DataField(required: true)] public DamageSpecifier MeleeDamage = default!; [DataField(required: true)] public DamageSpecifier CoffinHealing = default!; + [DataField] + public bool WeakToHolyWater = true; + [DataField] public float BloodDrainVolume = 5; @@ -116,7 +204,7 @@ public sealed partial class VampireAbilityListPrototype : IPrototype public float SpaceDamageFrequency = 2; [DataField] - public float StealthBloodCost = 5; + public float StealthCostPerSecond = 5; [DataField] public EntityWhitelist AcceptableFoods = new EntityWhitelist() { Tags = new() { "Pill" } }; @@ -145,11 +233,15 @@ public sealed partial class VampireAbilityEntry [DataField] public DamageSpecifier Damage = default!; [DataField] - public float Duration = 0; + public TimeSpan? Duration = default!; + [DataField] + public TimeSpan? DoAfterDelay = default!; [DataField] - public float Delay = 0; + public TimeSpan? UseDelay = default!; + [DataField] + public string PolymorphTarget = default!; } - +*/ [Serializable, NetSerializable] public enum VampirePowerKey : byte { @@ -158,9 +250,10 @@ public enum VampirePowerKey : byte DeathsEmbrace, Screech, Hypnotise, - BatForm, + Polymorph, NecroticTouch, BloodSteal, CloakOfDarkness, - StellarWeakness + StellarWeakness, + SupernaturalStrength } diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index a33f5fe45a3..b05d8ef1109 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -1,24 +1,37 @@ using Content.Shared.Actions; +using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Vampire.Components; using Robust.Shared.Serialization; namespace Content.Shared.Vampire; -public sealed partial class VampireUseAreaPowerEvent : InstantActionEvent +//Use power events +public sealed partial class VampireSelfPowerEvent : InstantActionEvent { [DataField] public VampirePowerKey Type; + [DataField] + public VampirePowerDetails Details = new(); }; -public sealed partial class VampireUseTargetedPowerEvent : EntityTargetActionEvent +public sealed partial class VampireTargetedPowerEvent : EntityTargetActionEvent { [DataField] public VampirePowerKey Type; + [DataField] + public VampirePowerDetails Details = new(); }; +public sealed partial class VampireSummonHeirloomEvent : InstantActionEvent +{ + +} +//Doafter events [Serializable, NetSerializable] public sealed partial class VampireDrinkBloodEvent : DoAfterEvent { + [DataField] + public float Volume = 0; public override DoAfterEvent Clone() => this; } @@ -26,16 +39,7 @@ public sealed partial class VampireDrinkBloodEvent : DoAfterEvent public sealed partial class VampireHypnotiseEvent : DoAfterEvent { [DataField] - public float Duration = 0; + public TimeSpan? Duration = TimeSpan.Zero; - public VampireHypnotiseEvent(float duration) - { - Duration = duration; - } public override DoAfterEvent Clone() => this; } -[Serializable, NetSerializable] -public sealed partial class VampireAbilityUnlockedEvent : EntityEventArgs -{ - public VampirePowerKey UnlockedAbility = default!; -} diff --git a/Resources/Locale/en-US/Vampires/vampires.ftl b/Resources/Locale/en-US/Vampires/vampires.ftl index f051277d2f4..bc75fae5224 100644 --- a/Resources/Locale/en-US/Vampires/vampires.ftl +++ b/Resources/Locale/en-US/Vampires/vampires.ftl @@ -23,4 +23,6 @@ vampire-cloak-enable = You wrap shadows around your form vampire-cloak-disable = You release your grip on the shadows vampire-bloodsteal-other = You feel blood being ripped from your body! -vampire-hypnotise-other = {CAPITALIZE(THE($user))} stares deeply into {THE($target)}'s eyes! \ No newline at end of file +vampire-hypnotise-other = {CAPITALIZE(THE($user))} stares deeply into {THE($target)}'s eyes! + +store-currency-display-blood-essence = Blood Essence \ No newline at end of file diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index c70068ab6b3..2c7cc007781 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -1,3 +1,15 @@ +- type: entity + id: ActionVampireSummonHeirloom + name: Summon Heirloom + description: Summon a family heirloom, gifted by lilith herself. + noSpawn: true + components: + - type: InstantAction + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: summonpendant + event: !type:VampireSummonHeirloomEvent + - type: entity id: ActionVampireToggleFangs name: Toggle Fangs @@ -14,7 +26,7 @@ sprite: Interface/Actions/actions_vampire.rsi state: fangs_extended event: - !type:VampireUseAreaPowerEvent + !type:VampireSelfPowerEvent type: ToggleFangs - type: entity @@ -31,10 +43,36 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/Vampire/glare.ogg event: - !type:VampireUseTargetedPowerEvent - type: Glare + !type:VampireTargetedPowerEvent + type: Glare + details: + duration: 10 useDelay: 60 +- type: entity + id: ActionVampireHypnotise + name: Hypnotise + description: Come with me, little lamb + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + priority: 2 + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: hypnotise + event: + !type:VampireTargetedPowerEvent + type: Hypnotise + details: + duration: 60 + doAfterDelay: 5 + useDelay: 300 + - type: entity id: ActionVampireScreech name: Screech @@ -50,8 +88,16 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/Vampire/screech_tone.ogg event: - !type:VampireUseAreaPowerEvent - type: Screech + !type:VampireSelfPowerEvent + type: Screech + details: + duration: 3 + damage: + types: + Blunt: 10 + Structural: 40 + usableWhileMuffled: false + activationCost: 10 useDelay: 60 - type: entity @@ -69,35 +115,42 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/demon_consume.ogg event: - !type:VampireUseAreaPowerEvent - type: BloodSteal + !type:VampireSelfPowerEvent + type: BloodSteal + details: + usableWhileStunned: false + usableWhileCuffed: false + activationCost: 20 useDelay: 60 - type: entity - id: ActionVampireHypnotise - name: Hypnotise - description: Come with me, little lamb + id: ActionVampireBatform + name: Toggle Bat Form + description: Assume your bat form noSpawn: true components: - - type: EntityTargetAction - whitelist: - components: - - Body - canTargetSelf: false - interactOnMiss: false + - type: InstantAction + checkCanInteract: false priority: 2 icon: sprite: Interface/Actions/actions_vampire.rsi - state: hypnotise + state: batform + sound: !type:SoundPathSpecifier + path: /Audio/Effects/teleport_arrival.ogg event: - !type:VampireUseTargetedPowerEvent - type: Hypnotise - useDelay: 300 + !type:VampireSelfPowerEvent + type: Polymorph + details: + usableWhileStunned: false + usableWhileCuffed: false + polymorphTarget: mobBatVampire + activationCost: 20 + useDelay: 30 - type: entity - id: ActionVampireBatform - name: Toggle Bat Form - description: Assume your bat form + id: ActionVampireMouseform + name: Toggle Mouse Form + description: Assume your mouse form noSpawn: true components: - type: InstantAction @@ -105,12 +158,17 @@ priority: 2 icon: sprite: Interface/Actions/actions_vampire.rsi - state: batform + state: mouseform sound: !type:SoundPathSpecifier path: /Audio/Effects/teleport_arrival.ogg event: - !type:VampireUseAreaPowerEvent - type: Batform + !type:VampireSelfPowerEvent + type: Polymorph + details: + usableWhileStunned: false + usableWhileCuffed: false + polymorphTarget: MobMouse + activationCost: 20 useDelay: 30 - type: entity @@ -126,6 +184,10 @@ sprite: Interface/Actions/actions_vampire.rsi state: cloakofdarkness event: - !type:VampireUseAreaPowerEvent - type: CloakOfDarkness + !type:VampireSelfPowerEvent + type: CloakOfDarkness + details: + usableWhileStunned: false + activationCost: 30 + upkeep: 1 useDelay: 10 \ No newline at end of file diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml new file mode 100644 index 00000000000..67a5ed2a4ef --- /dev/null +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -0,0 +1,96 @@ +- type: listing + id: VampireHypnotise + name: Hypnotise + description: Stare deeply into the eyes of a mortal, rendering them senseless for 60 seconds. + cost: + BloodEssence: 60 + productAction: ActionVampireHypnotise + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: VampireScreech + name: Screech + description: Release a piercing scream, stunning unprotected mortals and shattering fragile objects nearby. + cost: + BloodEssence: 120 + productAction: ActionVampireScreech + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: VampireBloodSteal + name: Blood Steal + description: Wrench the blood from all bodies nearby - living or dead. + cost: + BloodEssence: 120 + productAction: ActionVampireBloodSteal + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: VampireBatForm + name: Bat Form + description: Transform into a bat + cost: + BloodEssence: 200 + productAction: ActionVampireBatform + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - !type:BuyBeforeCondition + whitelist: [] + blacklist: VampireMouseForm + +- type: listing + id: VampireMouseForm + name: Mouse Form + description: Transform into a mouse + cost: + BloodEssence: 200 + productAction: ActionVampireMouseform + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - !type:BuyBeforeCondition + whitelist: [] + blacklist: VampireBatForm + +- type: listing + id: VampireCloakOfDarkness + name: Cloak of Darkness + description: Cloak your form in darkness, hiding you from mortal eyes. + cost: + BloodEssence: 400 + productAction: ActionVampireCloakOfDarkness + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +#- type: listing +# id: VampireSupernaturalStrength +# name: Supernatural Strength +# description: Infuse your muscles with unholy strength, allowing you to pry open any barricade mortals may hide behind. +# productAction: SupernaturalStrength +# cost: +# BloodEssence: 400 +# categories: +# - VampireAbilities +# conditions: +# - !type:ListingLimitedStockCondition +# stock: 1 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/vampire.yml b/Resources/Prototypes/Entities/Mobs/Player/vampire.yml new file mode 100644 index 00000000000..500b3884909 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Player/vampire.yml @@ -0,0 +1,19 @@ +- type: entity + name: bat + parent: MobBat + id: MobBatVampire + description: Some cultures find them terrifying, others crunchy on the teeth. + components: + - type: Tag + tags: + - DoorBumpOpener + - VimPilot + - CannotSuicide + - type: MindContainer + showExamineInfo: true + - type: NpcFactionMember + factions: + - PetsNT + - type: Alerts + + \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml new file mode 100644 index 00000000000..cb70d333e0e --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml @@ -0,0 +1,20 @@ +- type: entity + id: HeirloomeVampire + name: Family Heirloom + suffix: Vampire + description: A relic of a bygone age, remember your heritage. + parent: BaseItem + components: + - type: Sprite + sprite: Objects/Tools/Toolboxes/toolbox_thief.rsi + state: icon + - type: UserInterface + interfaces: + - key: enum.StoreUiKey.Key + type: StoreBoundUserInterface + - type: Store + categories: + - VampireAbilities + currencyWhitelist: + - BloodEssence + - type: VampireHeirloomComponent \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index 3e5d6af93ec..0b971848398 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -75,7 +75,7 @@ types: Caustic: 50 damageUnholy: ## What damage is dealt when a chaplain hits an unholy creature - groups: + types: types: 20 failChance: 0 locPrefix: "necro" diff --git a/Resources/Prototypes/Store/categories.yml b/Resources/Prototypes/Store/categories.yml index c16972c8a31..3243c4ec3e9 100644 --- a/Resources/Prototypes/Store/categories.yml +++ b/Resources/Prototypes/Store/categories.yml @@ -68,3 +68,7 @@ id: RevenantAbilities name: store-category-abilities +#vampire +- type: storeCategory + id: VampireAbilities + name: store-category-abilities diff --git a/Resources/Prototypes/Store/currency.yml b/Resources/Prototypes/Store/currency.yml index 91039a75e6a..cd688f26529 100644 --- a/Resources/Prototypes/Store/currency.yml +++ b/Resources/Prototypes/Store/currency.yml @@ -10,6 +10,12 @@ displayName: store-currency-display-stolen-essence canWithdraw: false +- type: currency + id: BloodEssence + displayName: store-currency-display-blood-essence + canWithdraw: false + + #debug - type: currency id: DebugDollar diff --git a/Resources/Prototypes/Vampire/powers.yml b/Resources/Prototypes/Vampire/powers.yml deleted file mode 100644 index 3023fffe81c..00000000000 --- a/Resources/Prototypes/Vampire/powers.yml +++ /dev/null @@ -1,91 +0,0 @@ -- type: vampireAbilityList - id: Default - - spaceDamage: - types: - Burn: 2.5 - - meleeDamage: - types: - Slash: 10 - - coffinHealing: - groups: - Brute: 6 - Toxin: 4 - Genetic: 2 - types: - Burn: 1 - Shock: 2 - Cold: 2 - Airloss: 2 - Bloodloss: 2 - - # How much blood per mouthfull - bloodDrainVolume: 5 - #How frequenty per mountfull - bloodDrainFrequency: 1 - - acceptableFoods: - tags: - - Pill - - abilities: - - actionPrototype: ActionVampireToggleFangs - bloodUnlockRequirement: 0 - type: ToggleFangs - - - actionPrototype: ActionVampireGlare - bloodUnlockRequirement: 0 - type: Glare - duration: 10 - - - actionPrototype: ActionVampireHypnotise - bloodUnlockRequirement: 0 - type: Hypnotise - duration: 60 - delay: 5 - - - actionPrototype: ActionVampireBatform - bloodUnlockRequirement: 30 - type: Batform - usableWhileStunned: false - usableWhileCuffed: false - activationCost: 20 - - - actionPrototype: ActionVampireBloodSteal - bloodUnlockRequirement: 30 - type: BloodSteal - usableWhileStunned: false - usableWhileCuffed: false - activationCost: 20 - - - actionPrototype: ActionVampireScreech - bloodUnlockRequirement: 50 - activationCost: 10 - type: Screech - usableWhileMuffled: false - duration: 3 - damage: - types: - Blunt: 10 - Structural: 40 - - #Passive ability, heals and returns to coffin on death - - type: DeathsEmbrace - bloodUnlockRequirement: 150 - activationCost: 100 - activationEffect: Smoke - - #Passive ability, burn damage outside station - - type: StellarWeakness - bloodUnlockRequirement: 0 - damage: - types: - Burn: 2.5 - - - actionPrototype: ActionVampireCloakOfDarkness - bloodUnlockRequirement: 300 - activationCost: 20 - type: CloakOfDarkness - usableWhileStunned: false \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json index ceba6022609..b68faff3cde 100644 --- a/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json +++ b/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json @@ -24,6 +24,9 @@ }, { "name": "batform" + }, + { + "name": "summonpendant" } ] } \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/summonpendant.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/summonpendant.png new file mode 100644 index 0000000000000000000000000000000000000000..ff41c2e7a038375f8dc8d01bc22ab0e6bb08a613 GIT binary patch literal 2239 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 N002ovPDHLkV1nlAHvRwr literal 0 HcmV?d00001 From ca250a417e95fb132d051038045311d9bd610ad8 Mon Sep 17 00:00:00 2001 From: Rainfey Date: Thu, 18 Jan 2024 16:46:40 +0000 Subject: [PATCH 07/67] Continuing --- Content.Server/Bed/Sleep/SleepingSystem.cs | 3 - .../Body/Systems/MetabolizerSystem.cs | 31 +++- .../Store/Conditions/BuyBeforeCondition.cs | 3 +- .../Vampire/VampireSystem.Abilities.cs | 163 ++++++++++++------ .../Vampire/VampireSystem.Transform.cs | 63 +++---- Content.Server/Vampire/VampireSystem.cs | 95 +++++----- Content.Shared/Vampire/VampireComponent.cs | 121 +++---------- Content.Shared/Vampire/VampireEvents.cs | 4 +- Resources/Locale/en-US/Vampires/vampires.ftl | 35 +++- Resources/Prototypes/Actions/vampire.yml | 47 ++--- .../Prototypes/Catalog/vampire_catalog.yml | 77 +++++++-- .../Entities/Objects/Misc/heirloom.yml | 4 +- .../Actions/actions_vampire.rsi/batform.png | Bin 1119 -> 1104 bytes .../actions_vampire.rsi/bloodsteal.png | Bin 756 -> 722 bytes .../actions_vampire.rsi/cloakofdarkness.png | Bin 983 -> 965 bytes .../actions_vampire.rsi/fangs_extended.png | Bin 912 -> 900 bytes .../actions_vampire.rsi/fangs_retracted.png | Bin 951 -> 936 bytes .../Actions/actions_vampire.rsi/glare.png | Bin 1391 -> 1383 bytes .../Actions/actions_vampire.rsi/hypnotise.png | Bin 1228 -> 1149 bytes .../Actions/actions_vampire.rsi/meta.json | 14 +- .../Actions/actions_vampire.rsi/screech.png | Bin 1628 -> 1630 bytes .../actions_vampire.rsi/summonheirloom.png | Bin 0 -> 2008 bytes .../actions_vampire.rsi/summonpendant.png | Bin 2239 -> 0 bytes 23 files changed, 369 insertions(+), 291 deletions(-) create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/summonheirloom.png delete mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/summonpendant.png diff --git a/Content.Server/Bed/Sleep/SleepingSystem.cs b/Content.Server/Bed/Sleep/SleepingSystem.cs index ce68b2e3433..4a6874bcccd 100644 --- a/Content.Server/Bed/Sleep/SleepingSystem.cs +++ b/Content.Server/Bed/Sleep/SleepingSystem.cs @@ -152,9 +152,6 @@ private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEve /// private void OnInteractHand(EntityUid uid, SleepingComponent component, InteractHandEvent args) { - if (args.Handled) - return; - args.Handled = true; if (!TryWakeCooldown(uid)) diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index 89b3e23008c..a5fc87e52cc 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Chemistry.Containers.EntitySystems; using Content.Shared.Administration.Logs; using Content.Shared.Body.Organ; +using Content.Shared.Body.Prototypes; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.Reagent; @@ -64,11 +65,6 @@ private void OnApplyMetabolicMultiplier(EntityUid uid, MetabolizerComponent comp component.AccumulatedFrametime = component.UpdateFrequency; } - public void SetMetabolizerTypes(MetabolizerComponent component, HashSet? metabolizerTypes) - { - component.MetabolizerTypes = metabolizerTypes; - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -212,6 +208,31 @@ private void TryMetabolize(EntityUid uid, MetabolizerComponent meta, OrganCompon _solutionContainerSystem.UpdateChemicals(soln.Value); } + + public bool TryAddMetabolizerType(MetabolizerComponent component, string metabolizerType) + { + if (!_prototypeManager.HasIndex(metabolizerType)) + return false; + + if (component.MetabolizerTypes == null) + component.MetabolizerTypes = new(); + + return component.MetabolizerTypes.Add(metabolizerType); + } + + public bool TryRemoveMetabolizerType(MetabolizerComponent component, string metabolizerType) + { + if (component.MetabolizerTypes == null) + return true; + + return component.MetabolizerTypes.Remove(metabolizerType); + } + + public void ClearMetabolizerTypes(MetabolizerComponent component) + { + if (component.MetabolizerTypes != null) + component.MetabolizerTypes.Clear(); + } } public sealed class ApplyMetabolicMultiplierEvent : EntityEventArgs diff --git a/Content.Server/Store/Conditions/BuyBeforeCondition.cs b/Content.Server/Store/Conditions/BuyBeforeCondition.cs index 132f3534391..f5ea7edceaf 100644 --- a/Content.Server/Store/Conditions/BuyBeforeCondition.cs +++ b/Content.Server/Store/Conditions/BuyBeforeCondition.cs @@ -1,4 +1,4 @@ -using Content.Server.Store.Components; +using Content.Server.Store.Components; using Content.Server.Store.Systems; using Content.Shared.Store; using Robust.Shared.Prototypes; @@ -16,6 +16,7 @@ public sealed partial class BuyBeforeCondition : ListingCondition /// /// Listing(s) that if bought, block this purchase, if any. /// + [DataField] public HashSet>? Blacklist; public override bool Condition(ListingConditionArgs args) diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 727924db044..6bd3157cf2d 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -11,6 +11,7 @@ using Content.Shared.Chemistry.Components; using Content.Shared.Cuffs.Components; using Content.Shared.Damage; +using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Flash; @@ -34,41 +35,41 @@ public sealed partial class VampireSystem /// /// Upon using any power that does not require a target /// - private void OnUseSelfPower(EntityUid uid, VampireComponent component, VampireSelfPowerEvent args) => TriggerPower((uid, component), args.Type, args.Details); + private void OnUseSelfPower(EntityUid uid, VampireComponent component, VampireSelfPowerEvent args) => args.Handled = TriggerPower((uid, component), args.Type, args.Details); /// /// Upon using any power that requires a target /// - private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireTargetedPowerEvent args) => TriggerPower((uid, component), args.Type, args.Details, args.Target); - private void TriggerPower(Entity vampire, VampirePowerKey powerType, VampirePowerDetails def, EntityUid? target = null) + private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireTargetedPowerEvent args) => args.Handled = TriggerPower((uid, component), args.Type, args.Details, args.Target); + private bool TriggerPower(Entity vampire, VampirePowerKey powerType, VampirePowerDetails def, EntityUid? target = null) { if (!IsAbilityUnlocked(vampire, powerType)) - return; - - if (def.ActivationCost > 0 && def.ActivationCost > GetBloodEssence(vampire)) - { - _popup.PopupClient(Loc.GetString("vampire-not-enough-blood"), vampire, vampire, PopupType.MediumCaution); - return; - } + return false; //Block if we are cuffed if (!def.UsableWhileCuffed && TryComp(vampire, out var cuffable) && !cuffable.CanStillInteract) { _popup.PopupEntity(Loc.GetString("vampire-cuffed"), vampire, vampire, PopupType.MediumCaution); - return; + return false; } //Block if we are stunned if (!def.UsableWhileStunned && HasComp(vampire)) { _popup.PopupEntity(Loc.GetString("vampire-stunned"), vampire, vampire, PopupType.MediumCaution); - return; + return false; } //Block if we are muzzled - so far only one item does this? if (!def.UsableWhileMuffled && TryComp(vampire, out var accent) && accent.Accent.Equals("mumble")) { _popup.PopupEntity(Loc.GetString("vampire-muffled"), vampire, vampire, PopupType.MediumCaution); - return; + return false; + } + + if (def.ActivationCost > 0 && !SubtractBloodEssence(vampire, def.ActivationCost)) + { + _popup.PopupClient(Loc.GetString("vampire-not-enough-blood"), vampire, vampire, PopupType.MediumCaution); + return false; } var success = true; @@ -76,6 +77,11 @@ private void TriggerPower(Entity vampire, VampirePowerKey powe //TODO: Rewrite when a magic effect system is introduced (like reagents) switch (powerType) { + case VampirePowerKey.SummonHeirloom: + { + SummonHeirloom(vampire); + break; + } case VampirePowerKey.ToggleFangs: { ToggleFangs(vampire); @@ -120,35 +126,36 @@ private void TriggerPower(Entity vampire, VampirePowerKey powe break; } - if (!success) - return; - - AddBloodEssence(vampire, -def.ActivationCost); - _action.StartUseDelay(GetAbilityEntity(vampire, powerType)); + //if (success) + // _action.StartUseDelay(GetAbilityEntity(vampire, powerType)); + return success; } #region Other Powers /// /// Spawn and bind the pendant if one does not already exist, otherwise just summon to the vampires hand /// - private void OnSummonHeirloom(EntityUid uid, VampireComponent component, VampireSummonHeirloomEvent args) + private void SummonHeirloom(Entity vampire) { - if (!component.Heirloom.HasValue - || LifeStage(component.Heirloom.Value) >= EntityLifeStage.Terminating) + if (!vampire.Comp.Heirloom.HasValue + || LifeStage(vampire.Comp.Heirloom.Value) >= EntityLifeStage.Terminating) { //If the pendant does not exist, or has been deleted - spawn one - component.Heirloom = Spawn(VampireComponent.HeirloomProto); + vampire.Comp.Heirloom = Spawn(VampireComponent.HeirloomProto); - if (TryComp(component.Heirloom, out var heirloomComponent)) - heirloomComponent.Owner = uid; + if (TryComp(vampire.Comp.Heirloom, out var heirloomComponent)) + heirloomComponent.VampireOwner = vampire; - if (TryComp(component.Heirloom, out var storeComponent)) - //Tie the store balance to the vampires balance - storeComponent.Balance = component.Balance; + //Init the store balance, or init the vampire's balance if this is the first summon + if (TryComp(vampire.Comp.Heirloom, out var storeComponent)) + if (vampire.Comp.Balance == null) + vampire.Comp.Balance = storeComponent.Balance; + else + storeComponent.Balance = vampire.Comp.Balance; } //Move to players hands - _hands.PickupOrDrop(uid, component.Heirloom.Value); + _hands.PickupOrDrop(vampire, vampire.Comp.Heirloom.Value); } private void Screech(Entity vampire, TimeSpan? duration, DamageSpecifier? damage = null) { @@ -349,6 +356,8 @@ private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, component.HomeCoffin = args.Container.Owner; EnsureComp(args.Entity); _popup.PopupEntity(Loc.GetString("vampire-deathsembrace-bind"), uid, uid); + _admin.Add(LogType.Damaged, LogImpact.Low, $"{ToPrettyString(uid):user} bound a new coffin"); + } } /// @@ -368,9 +377,7 @@ private bool TryMoveToCoffin(Entity vampire) return false; //Someone smashed your crib bro' - if (!Exists(vampire.Comp.HomeCoffin.Value) - || LifeStage(vampire.Comp.HomeCoffin.Value) == EntityLifeStage.Terminating - || LifeStage(vampire.Comp.HomeCoffin.Value) == EntityLifeStage.Deleted) + if (!Exists(vampire.Comp.HomeCoffin.Value) || LifeStage(vampire.Comp.HomeCoffin.Value) >= EntityLifeStage.Terminating) { vampire.Comp.HomeCoffin = null; return false; @@ -391,7 +398,13 @@ private bool TryMoveToCoffin(Entity vampire) _entityStorage.CloseStorage(vampire.Comp.HomeCoffin.Value, coffinEntityStorage); - return _entityStorage.Insert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage); + if (_entityStorage.Insert(vampire, vampire.Comp.HomeCoffin.Value, coffinEntityStorage)) + { + _admin.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(vampire):user} was moved to their home coffin"); + return true; + } + + return false; } /// /// Heal the vampire while they are in the coffin @@ -426,7 +439,7 @@ private void DoCoffinHeal(EntityUid vampire, VampireHealingComponent healing) /// /// Check and start drinking blood from a humanoid /// - private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) + /*private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) { if (args.Handled) return; @@ -438,7 +451,18 @@ private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent c return; args.Handled = TryDrink((args.User, vampireComponent), args.Target, TimeSpan.FromSeconds(1)); + }*/ + private void OnInteractHandEvent(EntityUid uid, VampireComponent component, BeforeInteractHandEvent args) + { + if (!HasComp(args.Target)) + return; + + if (args.Target == uid) + return; + + args.Handled = TryDrink((uid, component), args.Target, TimeSpan.FromSeconds(1)); } + private void ToggleFangs(Entity vampire) { var actionEntity = GetAbilityEntity(vampire.Comp, VampirePowerKey.ToggleFangs); @@ -448,12 +472,14 @@ private void ToggleFangs(Entity vampire) SetAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs, false); _action.SetToggled(actionEntity, false); popupText = Loc.GetString("vampire-fangs-retracted"); + _admin.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(vampire):user} retracted their fangs"); } else { SetAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs, true); _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.ToggleFangs], true); popupText = Loc.GetString("vampire-fangs-extended"); + _admin.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(vampire):user} extended their fangs"); } _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); } @@ -508,14 +534,6 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood if (_food.IsMouthBlocked(args.Target.Value, entity)) return; - //Thou shall not feed upon the blood of the holy - if (HasComp(args.Target)) - { - _damageableSystem.TryChangeDamage(entity, VampireComponent.HolyDamage, true); - _popup.PopupEntity(Loc.GetString("vampire-ingest-holyblood"), entity, entity, PopupType.LargeCaution); - args.Repeat = false; - } - if (_rotting.IsRotten(args.Target.Value)) { _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), args.User, args.User, PopupType.SmallCaution); @@ -532,28 +550,40 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood _popup.PopupEntity(Loc.GetString("vampire-blooddrink-empty"), entity.Owner, entity.Owner, PopupType.SmallCaution); return; } + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume); - args.Repeat = true; + //Slurp + _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); - //Transfer 95% to the vampire - var bloodSolution = _solution.SplitSolution(targetBloodstream.BloodSolution.Value, volumeToConsume * 0.95); + //Spill an extra 5% on the floor + _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume * 0.05)); - if (!TryIngestBlood(entity, bloodSolution)) + //Thou shall not feed upon the blood of the holy + if (HasComp(args.Target)) { - //Undo, put the blood back - _solution.AddSolution(targetBloodstream.BloodSolution.Value, bloodSolution); - args.Repeat = false; + _damageableSystem.TryChangeDamage(entity, VampireComponent.HolyDamage, true); + _popup.PopupEntity(Loc.GetString("vampire-ingest-holyblood"), entity, entity, PopupType.LargeCaution); + _admin.Add(LogType.Damaged, LogImpact.Low, $"{ToPrettyString(entity):user} attempted to drink {volumeToConsume}u of {ToPrettyString(args.Target):target}'s holy blood"); return; } + else + { + //Pull out some of the blood + var bloodSolution = _solution.SplitSolution(targetBloodstream.BloodSolution.Value, volumeToConsume); - //Slurp - _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); + if (!TryIngestBlood(entity, bloodSolution)) + { + //Undo, put the blood back + _solution.AddSolution(targetBloodstream.BloodSolution.Value, bloodSolution); + return; + } - AddBloodEssence(entity, volumeToConsume * 0.95); + _admin.Add(LogType.Damaged, LogImpact.Low, $"{ToPrettyString(entity):user} drank {volumeToConsume}u of {ToPrettyString(args.Target):target}'s blood"); + AddBloodEssence(entity, volumeToConsume * 0.95); - //And spill 5% on the floor - _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume * 0.05)); + args.Repeat = true; + } } /// /// Attempt to insert the solution into the first stomach that has space available @@ -579,4 +609,31 @@ private bool TryIngestBlood(Entity vampire, Solution ingestedS return false; } #endregion + + private bool IsAbilityUnlocked(VampireComponent vampire, VampirePowerKey ability) + { + return vampire.UnlockedPowers.ContainsKey(ability); + } + private bool IsAbilityActive(VampireComponent vampire, VampirePowerKey ability) + { + return vampire.AbilityStates.Contains(ability); + } + private bool SetAbilityActive(VampireComponent vampire, VampirePowerKey ability, bool active) + { + if (active) + { + return vampire.AbilityStates.Add(ability); + } + else + { + return vampire.AbilityStates.Remove(ability); + } + } + private EntityUid? GetAbilityEntity(VampireComponent vampire, VampirePowerKey ability) + { + if (IsAbilityUnlocked(vampire, ability)) + return vampire.UnlockedPowers[ability]; + return null; + } + } diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index f3786360a13..c6496dfe467 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -1,41 +1,28 @@ using Content.Server.Atmos.Components; using Content.Server.Body.Components; -using Content.Server.Body.Systems; -using Content.Server.Store.Components; -using Content.Server.Store.Systems; using Content.Server.Temperature.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.Rotting; using Content.Shared.Body.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Damage; -using Content.Shared.Store; using Content.Shared.Vampire.Components; using Content.Shared.Weapons.Melee; -using Content.Shared.Whitelist; -using Robust.Server.GameObjects; using Robust.Shared.Audio; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Content.Server.Vampire; public sealed partial class VampireSystem { - [Dependency] private readonly MetabolizerSystem _metabolism = default!; - [Dependency] private readonly StoreSystem _store = default!; - /// /// Convert the players into a vampire, all programatic because i dont want to replace the players body /// /// Which entity to convert - private void MakeVampire(EntityUid vampire) + private void MakeVampire(EntityUid vampireUid) { - var vampireComponent = EnsureComp(vampire); + var vampireComponent = EnsureComp(vampireUid); + var vampire = new Entity(vampireUid, vampireComponent); + EnsureComp(vampire); RemComp(vampire); RemComp(vampire); @@ -50,16 +37,18 @@ private void MakeVampire(EntityUid vampire) melee.Animation = "WeaponArcClaw"; melee.HitSound = new SoundPathSpecifier("/Audio/Weapons/slash.ogg"); } + MakeVulnerableToHoly(vampire); - AddStartingAbilities(vampire, vampireComponent); + //Initialise currency + vampireComponent.Balance = new() { { VampireComponent.CurrencyProto, 0 } }; - var store = EnsureComp(vampire); - store.AccountOwner = vampire; - _store.InitializeFromPreset(VampireComponent.StorePresetProto, vampire, store); + //Add the summon heirloom ability + AddStartingAbilities(vampire); - MakeVulnerableToHoly(vampire); + //Order of operation requirement, must be called after initialising balance + UpdateBloodDisplay(vampire); } - private void MakeVulnerableToHoly(EntityUid vampire) + private void MakeVulnerableToHoly(Entity vampire) { //Take damage from holy water splash if (TryComp(vampire, out var reactive)) @@ -85,29 +74,23 @@ private void MakeVulnerableToHoly(EntityUid vampire) if (TryComp(organ.Id, out var stomachComponent)) { //Override the stomach, prevents humans getting sick when ingesting blood - _metabolism.SetMetabolizerTypes(metabolizer, VampireComponent.Metabolizers); + _metabolism.ClearMetabolizerTypes(metabolizer); _stomach.SetSpecialDigestible(stomachComponent, VampireComponent.AcceptableFoods); } - else - { - //Otherwise just add the metabolizers on - dont want to suffocate the vampires - var tempMetabolizer = metabolizer.MetabolizerTypes ?? new HashSet(); - foreach (var t in VampireComponent.Metabolizers) - tempMetabolizer.Add(t); - _metabolism.SetMetabolizerTypes(metabolizer, tempMetabolizer); - } + _metabolism.TryAddMetabolizerType(metabolizer, VampireComponent.MetabolizerVampire); + _metabolism.TryAddMetabolizerType(metabolizer, VampireComponent.MetabolizerBloodsucker); } } } - private void AddStartingAbilities(EntityUid vampire, VampireComponent component) + private void AddStartingAbilities(Entity vampire) { - foreach (var ability in VampireComponent.StartingAbilities) + var action = _action.AddAction(vampire, VampireComponent.SummonActionPrototype); + if (action.HasValue) { - var action = _action.AddAction(vampire, ability); - if (action != null) - OnStorePurchase(vampire, action.Value); + OnStorePurchase(vampire, action.Value); + vampire.Comp.UnlockedPowers[VampirePowerKey.SummonHeirloom] = action; } } @@ -123,11 +106,7 @@ private void MakeImmuneToHoly(EntityUid vampire) { if (TryComp(organ.Id, out var metabolizer)) { - var currentMetabolizers = metabolizer.MetabolizerTypes; - if (currentMetabolizers == null) - continue; - currentMetabolizers.Remove("vampire"); - _metabolism.SetMetabolizerTypes(metabolizer, currentMetabolizers); + _metabolism.TryRemoveMetabolizerType(metabolizer, VampireComponent.MetabolizerVampire); } } diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 26c1febee56..e59c4b9dad3 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Administration.Logs; using Content.Server.Atmos.Rotting; using Content.Server.Beam; using Content.Server.Bed.Sleep; @@ -6,14 +7,14 @@ using Content.Server.Interaction; using Content.Server.Nutrition.EntitySystems; using Content.Server.Polymorph.Systems; -using Content.Server.Speech.Components; using Content.Server.Storage.EntitySystems; using Content.Server.Store.Components; using Content.Server.Store.Events; +using Content.Server.Store.Systems; using Content.Shared.Actions; using Content.Shared.Body.Systems; +using Content.Shared.Buckle; using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Cuffs.Components; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Examine; @@ -33,11 +34,13 @@ using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using System.Linq; namespace Content.Server.Vampire; public sealed partial class VampireSystem : EntitySystem { + [Dependency] private readonly IAdminLogManager _admin = default!; [Dependency] private readonly FoodSystem _food = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly BloodstreamSystem _blood = default!; @@ -51,7 +54,6 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly IMapManager _mapMan = default!; [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -65,17 +67,21 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly StoreSystem _store = default!; + [Dependency] private readonly MetabolizerSystem _metabolism = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent(OnInteractWithHumanoid, before: new[] { typeof(InteractionPopupSystem), typeof(SleepingSystem) }); - + SubscribeLocalEvent(OnInteractHandEvent); + /*SubscribeLocalEvent(OnInteractWithHumanoid, + before: new[] { typeof(InteractionPopupSystem), typeof(SleepingSystem), typeof(SharedBuckleSystem), typeof(SharedStunSystem) }); + */ SubscribeLocalEvent(DrinkDoAfter); SubscribeLocalEvent(HypnotiseDoAfter); - SubscribeLocalEvent(OnSummonHeirloom); + //SubscribeLocalEvent(OnSummonHeirloom); SubscribeLocalEvent(OnInsertedIntoContainer); SubscribeLocalEvent(OnRemovedFromContainer); SubscribeLocalEvent(OnVampireStateChanged); @@ -100,7 +106,7 @@ public override void Update(float frameTime) if (stealth.NextStealthTick <= 0) { stealth.NextStealthTick = 1; - if (!AddBloodEssence((uid, vampire), -stealth.Upkeep)) + if (!SubtractBloodEssence((uid, vampire), stealth.Upkeep)) RemCompDeferred(uid); } stealth.NextStealthTick -= frameTime; @@ -133,6 +139,7 @@ public override void Update(float frameTime) } }*/ } + private void OnComponentStartup(EntityUid uid, VampireComponent component, ComponentStartup args) { MakeVampire(uid); @@ -145,8 +152,8 @@ private void OnUseHeirloom(EntityUid uid, VampireHeirloomComponent component, Us return; //Only allow the heirloom owner to use this - prevent stealing others blood essence - //TODO: Popup - if (component.Owner != args.User) + //TODO: Popup, deprecation + if (component.VampireOwner != args.User) return; //And open the UI @@ -172,6 +179,8 @@ private void OnStorePurchase(EntityUid purchaser, EntityUid purchasedAction) vampireComponent.UnlockedPowers[vampirePower!.Type] = purchasedAction; return; } + + UpdateBloodDisplay((purchaser, vampireComponent)); } @@ -195,47 +204,55 @@ private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent } private bool AddBloodEssence(Entity vampire, FixedPoint2 quantity) { + if (quantity < 0) + return false; + vampire.Comp.TotalBloodDrank += quantity.Float(); vampire.Comp.Balance[VampireComponent.CurrencyProto] += quantity; + + UpdateBloodDisplay(vampire); + return true; } - private FixedPoint2 GetBloodEssence(Entity vampire) + private bool SubtractBloodEssence(Entity vampire, FixedPoint2 quantity) { - if (!TryComp(vampire, out var storeComp)) - return 0; - var currencies = storeComp.Balance; - if (!currencies.TryGetValue(VampireComponent.CurrencyProto, out var val)) - return 0; - return val; - } + if (quantity < 0) + return false; - private bool IsAbilityUnlocked(VampireComponent vampire, VampirePowerKey ability) - { - return vampire.UnlockedPowers.ContainsKey(ability); - } - private bool IsAbilityActive(VampireComponent vampire, VampirePowerKey ability) - { - return vampire.AbilityStates.Contains(ability); + if (vampire.Comp.Balance[VampireComponent.CurrencyProto] < quantity) + return false; + + vampire.Comp.Balance[VampireComponent.CurrencyProto] -= quantity; + + UpdateBloodDisplay(vampire); + + return true; } - private bool SetAbilityActive(VampireComponent vampire, VampirePowerKey ability, bool active) + /// + /// Use the charges display on SummonHeirloom to show the remaining blood essence + /// + /// + private void UpdateBloodDisplay(Entity vampire) { - if (active) - { - return vampire.AbilityStates.Add(ability); - } - else - { - return vampire.AbilityStates.Remove(ability); - } + //Sanity check, you never know who is going to touch this code + if (!vampire.Comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var balance)) + return; + + var chargeDisplay = (int) Math.Round((decimal) balance); + var summonAction = GetAbilityEntity(vampire, VampirePowerKey.SummonHeirloom); + + if (summonAction == null) + return; + + _action.SetCharges(summonAction, chargeDisplay); } - private EntityUid? GetAbilityEntity(VampireComponent vampire, VampirePowerKey ability) + private FixedPoint2 GetBloodEssence(Entity vampire) { - if (IsAbilityUnlocked(vampire, ability)) - return vampire.UnlockedPowers[ability]; - return null; - } - + if (!vampire.Comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var val)) + return 0; + return val; + } /*private void DoSpaceDamage(Entity vampire) { diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index 4480952e2ca..059f935a008 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Body.Prototypes; using Content.Shared.Chat.Prototypes; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; @@ -14,27 +15,25 @@ namespace Content.Shared.Vampire.Components; [RegisterComponent] public sealed partial class VampireComponent : Component { - //Statics - [ValidatePrototypeId] - public static readonly string StorePresetProto = "StorePresetVampire"; - [ValidatePrototypeId] - public static readonly string CurrencyProto = "BloodEssence"; + //Static prototype references [ValidatePrototypeId] public static readonly string SleepStatusEffectProto = "ForcedSleep"; [ValidatePrototypeId] public static readonly string ScreamEmoteProto = "Scream"; [ValidatePrototypeId] - public static readonly string HeirloomProto = "HeirloomeVampire"; + public static readonly string HeirloomProto = "HeirloomVampire"; + [ValidatePrototypeId] + public static readonly string CurrencyProto = "BloodEssence"; public static readonly EntityWhitelist AcceptableFoods = new() { Tags = new() { "Pill" } }; - public static readonly HashSet Metabolizers = new() - { - "bloodsucker", - "vampire" - }; + [ValidatePrototypeId] + public static readonly string MetabolizerVampire = "vampire"; + [ValidatePrototypeId] + public static readonly string MetabolizerBloodsucker = "bloodsucker"; + public static readonly DamageSpecifier MeleeDamage = new() { DamageDict = new Dictionary() { { "Slash", 10 } } @@ -43,12 +42,9 @@ public sealed partial class VampireComponent : Component { DamageDict = new Dictionary() { { "Burn", 10 } } }; - public static readonly List StartingAbilities = new() - { - "ActionVampireSummonHeirloom", - "ActionVampireToggleFangs", - "ActionVampireGlare" - }; + + [ValidatePrototypeId] + public static readonly string SummonActionPrototype = "ActionVampireSummonHeirloom"; /// /// Total blood drank, counter for end of round screen @@ -60,13 +56,7 @@ public sealed partial class VampireComponent : Component /// How much blood per mouthful /// [ViewVariables(VVAccess.ReadWrite)] - public float MouthVolume = 0; - - /// - /// How long till we apply another tick of space damage - /// - [ViewVariables(VVAccess.ReadWrite)] - public double NextSpaceDamageTick = 0f; + public float MouthVolume = 5; /// /// Uid of the last coffin the vampire slept in @@ -75,7 +65,6 @@ public sealed partial class VampireComponent : Component [ViewVariables(VVAccess.ReadWrite)] public EntityUid? HomeCoffin = default!; - [ViewVariables(VVAccess.ReadWrite)] public HashSet AbilityStates = new(); /// @@ -91,9 +80,9 @@ public sealed partial class VampireComponent : Component /// /// Current available balance, used to sync currency across heirlooms and add essence as we feed /// - public Dictionary Balance = new() { { VampireComponent.CurrencyProto, 0 } }; + public Dictionary Balance = default!; - public readonly SoundSpecifier BloodDrainSound = new SoundPathSpecifier("/Audio/Items/drink.ogg", new AudioParams() { Volume = -3f }); + public readonly SoundSpecifier BloodDrainSound = new SoundPathSpecifier("/Audio/Items/drink.ogg", new AudioParams() { Volume = -3f, MaxDistance = 3f }); public readonly SoundSpecifier AbilityPurchaseSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); } @@ -135,7 +124,8 @@ public sealed partial class CoffinComponent : Component [RegisterComponent] public sealed partial class VampireHeirloomComponent : Component { - //public EntityUid? Owner = default!; + //Use of the heirloom is limited to this entity + public EntityUid? VampireOwner = default!; } [RegisterComponent] public sealed partial class VampireHealingComponent : Component @@ -170,78 +160,6 @@ public sealed partial class VampireSealthComponent : Component public float Upkeep = 0; } -/*[Prototype("vampireAbilityList")] -public sealed partial class VampireAbilityListPrototype : IPrototype -{ - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - - [DataField] - public List Abilities = new(); - - /// - /// For quick reference, populated at system init - /// - public FrozenDictionary AbilitiesByKey = default!; - - [DataField(required: true)] - public DamageSpecifier MeleeDamage = default!; - - [DataField(required: true)] - public DamageSpecifier CoffinHealing = default!; - - [DataField] - public bool WeakToHolyWater = true; - - [DataField] - public float BloodDrainVolume = 5; - - [DataField] - public float BloodDrainFrequency = 1; - - [DataField] - public float SpaceDamageFrequency = 2; - - [DataField] - public float StealthCostPerSecond = 5; - - [DataField] - public EntityWhitelist AcceptableFoods = new EntityWhitelist() { Tags = new() { "Pill" } }; -} - -[DataDefinition] -public sealed partial class VampireAbilityEntry -{ - [DataField] - public string? ActionPrototype = default!; - - [DataField] - public int BloodUnlockRequirement = 0; - [DataField] - public float ActivationCost = 0; - [DataField] - public bool UsableWhileCuffed = true; - [DataField] - public bool UsableWhileStunned = true; - [DataField] - public bool UsableWhileMuffled = true; - [DataField(required: true)] - public VampirePowerKey Type = default!; - [DataField] - public string ActivationEffect = default!; - [DataField] - public DamageSpecifier Damage = default!; - [DataField] - public TimeSpan? Duration = default!; - [DataField] - public TimeSpan? DoAfterDelay = default!; - [DataField] - public TimeSpan? UseDelay = default!; - [DataField] - public string PolymorphTarget = default!; -} -*/ [Serializable, NetSerializable] public enum VampirePowerKey : byte { @@ -255,5 +173,6 @@ public enum VampirePowerKey : byte BloodSteal, CloakOfDarkness, StellarWeakness, - SupernaturalStrength + SupernaturalStrength, + SummonHeirloom } diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index b05d8ef1109..9698144f27a 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -21,10 +21,10 @@ public sealed partial class VampireTargetedPowerEvent : EntityTargetActionEvent [DataField] public VampirePowerDetails Details = new(); }; -public sealed partial class VampireSummonHeirloomEvent : InstantActionEvent +/*public sealed partial class VampireSummonHeirloomEvent : InstantActionEvent { -} +}*/ //Doafter events [Serializable, NetSerializable] diff --git a/Resources/Locale/en-US/Vampires/vampires.ftl b/Resources/Locale/en-US/Vampires/vampires.ftl index bc75fae5224..b22d4af0730 100644 --- a/Resources/Locale/en-US/Vampires/vampires.ftl +++ b/Resources/Locale/en-US/Vampires/vampires.ftl @@ -23,6 +23,37 @@ vampire-cloak-enable = You wrap shadows around your form vampire-cloak-disable = You release your grip on the shadows vampire-bloodsteal-other = You feel blood being ripped from your body! -vampire-hypnotise-other = {CAPITALIZE(THE($user))} stares deeply into {THE($target)}'s eyes! +vampire-hypnotise-other = {CAPITALIZE(THE($user))} stares deeply into {MAKEPLURAL(THE($target))} eyes! -store-currency-display-blood-essence = Blood Essence \ No newline at end of file +store-currency-display-blood-essence = Blood Essence + +#Abilities +vampire-ability-summonheirloom = Summon Heirloom +vampire-ability-summonheirloom-description = Summon a family heirloom, gifted by lilith herself. + +vampire-ability-blessing = Blessing of Lilith +vampire-ability-blessing-description = Swear your soul to Lilith, receive her blessing, and feast upon the bounty around you. + +vampire-ability-togglefangs = Toggle Fangs +vampire-ability-togglefangs-description = Extend or retract your fangs. Walking around with your fangs out might reveal your true nature. + +vampire-ability-glare = Glare +vampire-ability-glare-description = Release a blinding flash from your eyes, stunning a unprotected mortal for 10 seconds. Activation Cost: 20 Essence. Cooldown: 60 Seconds + +vampire-ability-hypnotise = Hypnotise +vampire-ability-hypnotise-description = Stare deeply into a mortals eyes, forcing them to sleep for 60 seconds. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes + +vampire-ability-screech = Screech +vampire-ability-screech-description = Release a piercing scream, stunning unprotected mortals and shattering fragile objects nearby. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes + +vampire-ability-bloodsteal = Blood Steal +vampire-ability-bloodsteal-description = Wrench the blood from all bodies nearby - living or dead. Activation Cost: 20 Essence. Cooldown: 60 Seconds + +vampire-ability-batform = Bat Form +vampire-ability-batform-description = Assume for form of a bat. Fast, Hard to Hit, Likes fruit. Activation Cost: 20 Essence. Cooldown: 30 Seconds + +vampire-ability-mouseform = Mouse Form +vampire-ability-mouseform-description = Assume for form of a mouse. Fast, Small, Immune to doors. Activation Cost: 20 Essence. Cooldown: 30 Seconds + +vampire-ability-cloakofdarkness = Cloak of Darkness +vampire-ability-cloakofdarkness-description = Cloak yourself from mortal eyes, rendering you invisible while stationary. Activation Cost: 30 Essence. Upkeep: 1 Essence/Second Cooldown: 10 Seconds \ No newline at end of file diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index 2c7cc007781..399213138cb 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -1,19 +1,21 @@ - type: entity id: ActionVampireSummonHeirloom - name: Summon Heirloom - description: Summon a family heirloom, gifted by lilith herself. + name: vampire-ability-summonheirloom + description: vampire-ability-summonheirloom-description noSpawn: true components: - type: InstantAction icon: sprite: Interface/Actions/actions_vampire.rsi - state: summonpendant - event: !type:VampireSummonHeirloomEvent + state: summonheirloom + event: + !type:VampireSelfPowerEvent + type: SummonHeirloom - type: entity id: ActionVampireToggleFangs - name: Toggle Fangs - description: Extend or retract your fangs. + name: vampire-ability-togglefangs + description: vampire-ability-togglefangs-description noSpawn: true components: - type: InstantAction @@ -31,11 +33,14 @@ - type: entity id: ActionVampireGlare - name: Glare - description: Strike fear into their souls + name: vampire-ability-glare + description: vampire-ability-glare-description noSpawn: true components: - type: EntityTargetAction + whitelist: + components: + - Body priority: 2 icon: sprite: Interface/Actions/actions_vampire.rsi @@ -51,14 +56,14 @@ - type: entity id: ActionVampireHypnotise - name: Hypnotise - description: Come with me, little lamb + name: vampire-ability-hypnotise + description: vampire-ability-hypnotise-description noSpawn: true components: - type: EntityTargetAction whitelist: components: - - Body + - HumanoidAppearance canTargetSelf: false interactOnMiss: false priority: 2 @@ -75,8 +80,8 @@ - type: entity id: ActionVampireScreech - name: Screech - description: Your enemies shall kneel + name: vampire-ability-screech + description: vampire-ability-screech-description noSpawn: true components: - type: InstantAction @@ -102,8 +107,8 @@ - type: entity id: ActionVampireBloodSteal - name: Blood Steal - description: It's not theirs, it's yours + name: vampire-ability-bloodsteal + description: vampire-ability-bloodsteal-description noSpawn: true components: - type: InstantAction @@ -125,8 +130,8 @@ - type: entity id: ActionVampireBatform - name: Toggle Bat Form - description: Assume your bat form + name: vampire-ability-batform + description: vampire-ability-batform-description noSpawn: true components: - type: InstantAction @@ -149,8 +154,8 @@ - type: entity id: ActionVampireMouseform - name: Toggle Mouse Form - description: Assume your mouse form + name: vampire-ability-mouseform + description: vampire-ability-mouseform-description noSpawn: true components: - type: InstantAction @@ -173,8 +178,8 @@ - type: entity id: ActionVampireCloakOfDarkness - name: Cloak of Darkness - description: Hide yourself from mortal eyes + name: vampire-ability-cloakofdarkness + description: vampire-ability-cloakofdarkness-description noSpawn: true components: - type: InstantAction diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml index 67a5ed2a4ef..e01a58ebdad 100644 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -1,7 +1,36 @@ +- type: listing + id: VampireToggleFangs + name: vampire-ability-blessing + description: vampire-ability-blessing-description + cost: + BloodEssence: 0 + productAction: ActionVampireToggleFangs + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: VampireGlare + name: vampire-ability-glare + description: vampire-ability-glare-description + cost: + BloodEssence: 20 + productAction: ActionVampireGlare + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireToggleFangs + - type: listing id: VampireHypnotise - name: Hypnotise - description: Stare deeply into the eyes of a mortal, rendering them senseless for 60 seconds. + name: vampire-ability-hypnotise + description: vampire-ability-hypnotise-description cost: BloodEssence: 60 productAction: ActionVampireHypnotise @@ -10,11 +39,14 @@ conditions: - !type:ListingLimitedStockCondition stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireToggleFangs - type: listing id: VampireScreech - name: Screech - description: Release a piercing scream, stunning unprotected mortals and shattering fragile objects nearby. + name: vampire-ability-screech + description: vampire-ability-screech-description cost: BloodEssence: 120 productAction: ActionVampireScreech @@ -23,11 +55,14 @@ conditions: - !type:ListingLimitedStockCondition stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireToggleFangs - type: listing id: VampireBloodSteal - name: Blood Steal - description: Wrench the blood from all bodies nearby - living or dead. + name: vampire-ability-bloodsteal + description: vampire-ability-bloodsteal-description cost: BloodEssence: 120 productAction: ActionVampireBloodSteal @@ -36,11 +71,14 @@ conditions: - !type:ListingLimitedStockCondition stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireToggleFangs - type: listing id: VampireBatForm - name: Bat Form - description: Transform into a bat + name: vampire-ability-batform + description: vampire-ability-batform-description cost: BloodEssence: 200 productAction: ActionVampireBatform @@ -50,13 +88,15 @@ - !type:ListingLimitedStockCondition stock: 1 - !type:BuyBeforeCondition - whitelist: [] - blacklist: VampireMouseForm + whitelist: + - VampireToggleFangs + blacklist: + - VampireMouseForm - type: listing id: VampireMouseForm - name: Mouse Form - description: Transform into a mouse + name: vampire-ability-mouseform + description: vampire-ability-mouseform-description cost: BloodEssence: 200 productAction: ActionVampireMouseform @@ -66,13 +106,15 @@ - !type:ListingLimitedStockCondition stock: 1 - !type:BuyBeforeCondition - whitelist: [] - blacklist: VampireBatForm + whitelist: + - VampireToggleFangs + blacklist: + - VampireBatForm - type: listing id: VampireCloakOfDarkness - name: Cloak of Darkness - description: Cloak your form in darkness, hiding you from mortal eyes. + name: vampire-ability-cloakofdarkness + description: vampire-ability-cloakofdarkness-description cost: BloodEssence: 400 productAction: ActionVampireCloakOfDarkness @@ -81,6 +123,9 @@ conditions: - !type:ListingLimitedStockCondition stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireToggleFangs #- type: listing # id: VampireSupernaturalStrength diff --git a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml index cb70d333e0e..2f733284ac5 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml @@ -1,5 +1,5 @@ - type: entity - id: HeirloomeVampire + id: HeirloomVampire name: Family Heirloom suffix: Vampire description: A relic of a bygone age, remember your heritage. @@ -17,4 +17,4 @@ - VampireAbilities currencyWhitelist: - BloodEssence - - type: VampireHeirloomComponent \ No newline at end of file + - type: VampireHeirloom \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/batform.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/batform.png index 7fe2d43a4e8a529582016accb8c88e9f9380e9b7..4e01f86eae2b6b69f59b7ffd3358179ff9bf8ae1 100644 GIT binary patch delta 1084 zcmV-C1jGB^2+#sYQ$yL8rvkT>m|+2 zb-7K%?BQg`>rJ9-yY(ZG?|$c;-|u(6^L>Y-DHIB83*EzGMt{7vC6h_wq|<2tKmXuv z=$K*F7Pa74&@~LpDbW69G z%3h$6DpSpUgzNCWNo&SF07yBJzqaAHU;?03F0MXNdo226(|l=i09en*0GPC9lh#Z) zoKC01;m{tj)3WG3x2w|pQf_q>9g9BL*|xr&sDw4x*?;!auk!#{$$LD3Dg)G}`N%gN zEmfy$RMWnmmQZ!K;e~tGfiFM%ha3i>&( z&GJXxX&MarIg${PpNeN7slEvz6)-f~!W3rk7} zKt7*uZ*MORrBGaWPJNm{j^h9bf^wPhejBw{BSr-B|0&v-F z>wnvcbM?-1^-hZ@JuQc2x3IGl#pY;gYC1$9m>;SRR*4QEEX)qWFr4pSS0BN^r zKC-g1LeBYM2d%BG^i-AT&CShJ&tYT(kf@9?$0H<}tUa3VnHb zS$Wy*1$;-R-s34gQy8n&N()P;(*e-wbbmB6DripsE&$~3O!JX{OF;fYq0rRTThd!Q zN~~!9j&e;A9dI#TPGckwSqyO_Fr%ANC81HT&t;`RJ zkl&-iF7=f^DxW}IzO;FeB61Q#LqhiC3u&0^=e?kw}pMR{<%iIiOxzltZr^BjtkHL!=zjQ>98(TdB&a zLNy{%19hcVfuLklXfUE~pqSz{N!dEAH?^^;-NQJO&DxlxNq_USvS(&zzW3&T^G0hZ z6bj38{TD8o@xqo!BmnH}?2zuaU%5P8)6DW*KY+^@GgrQ7!pk$_#_e_+hK7c8^gpis zU3u~GOgv_GmVNd5*~5|~k%-Aim2Sp?^iq4Ulc0}yb(QCNp67|?=iXnh!vAcB@$qqz z92z@YZ9RJEwSUs7u|HQyqaz!?WztKOOmC<2sog?`HE{s^@12w+DUnDpI%P`fluJSD z#+%9X89GvHsk@6%1KzS!)gTWws{jzMt~$iH(~%RRW)Q#%_=<4 zI~$7@r{kL+XMb7M{Y(yY3Q)T)Mn3KG)gG(U zP5WeGy1Q8z?&mMa$Aq(zq zl(`q10)Ga9_EX2nt2_5%Q-DE?0N6|yR8=M8@3d$fL>YTAYLhdW-cGsJQ2 zFjapz)C*wG$_|Q=J-@g5ag2Lb)*wa({NC*Gg%0T2iTif1Mi0)JovWJR@etgNW+ax6Q?+Bp_LE|&w) z+1dG&140Kt=vuTE0bpfA@|a9lW@a1eYKsNDog51e+*pdz!~&R~pLe-j`xY7vzon&x zG=5#YLv}#AACCs(@h zZhz!CMZI5+W#`!NM6ozpTU(!UKU(m`uc_mQE5d;P+k^BLEf`7fGI< z?4Z59ohDXGZ)Pv>Nf!(cYmAc@JtTKUoaS)`uqm~>X8SO70un&t|`(1 zC*u|j)>3G;kj^1X+I@Y!q+tvM0_WZiZ`}7d+;xKQL?jY9qSkX&_8fLo`M~RPnelW@FVFQ8x^^r7*+fyK?a9f>e*yB`=iESp RsZ{^~002ovPDHLkV1kJw4u1du diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/bloodsteal.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/bloodsteal.png index 2631d8c0636eb726ae0a4afed036f4547bab6f57..1d8df63108302ab913cf98d7e6747752800c5263 100644 GIT binary patch delta 709 zcmV;$0y_Ql1=0nO8Gix*008_L?V|ty0+~rfK~zY`y_P>{T2U0nzx%XkOF>ZxD5Xe? zl!ij*bjeaF1fkHOAi-EnkPL;6f*C@G2#(!?G!hM2`v;*A(k0N=EDjwsgS1EoA0<>` zIz%JpbvWU^+w0BqnY1}L+;^Y;&iTIYK6%VAjCv)K&bo2o(tmZG7M)H9!242Ya%$UI zuS5XkGTm3}xA5mN5&Jr z$&M$k-~Ije!+%k~-`DuA#q48O?HXTd7QTm=SF$DPN{}u5(-@Qh_!uLvOzzsWA_Hr? zR|3mRdXaDfDc%@tgdKCk-wy_ZrUG$fB^|(${*o4rL5T(zXO~1YjRRSMlAClTZu>$a zU7#@DL;4k`n++uTN?@6YgL^ML7`U^UE=Ojyh03S25rNj#jt^zWIY+e>Esw4f4W66NFXIz zRv=pct=*IaaVcU9!)HdUoA~;d0uB<_G__bZ>4(^p6|4?s`Fhc@DQOZ-vqp<_Q-Mh5 zm!c+{^nXL_iS#r7i=^bm1~vy*pQ1~84lJJPamP0mP^wbMVhl~=z`pMa12AsikXuxB)46Ugh@&ihykf}rKE*?769ZZYqAPu1=?tf$#oam(LYNNocEhK%9&4GgYZxIb$PPj}X~(baI`;h*Cz2V;=ueNh5-K>36CJ{+jz#SIulRj`+U8vcJ6EYN zU>9lybfncx1$VOf5V^d#Ar%pXNE|9aMLv^xM%J9P0Dpg8Omosg&MYg5D1bDUH4MNk zBPWcXV@IEXYNoF2L?u*VOiI4+zaT1{VgZYYb>uhu9Efu=UbTBk z0ScTdguxsP*hMVQ7m-T}=t4+^#OXLr*Gpw!H+77|=~nEHmmvp$v}IY^2}!rt(h9h) zyVQ$Gx+UD*`70Oj^wB5Swhh;HwS^>=StKU%mtfC+-i&jA7hqWyY}@8ah+W?|UtHO1 zQFiLSQ5(bf_W2>BfzvOKrH@DYVENzhJP(c97<&h6NN;SOq1P#La#G_u(Cv1m-zO(0 Z_y@h#I7cG$r=0)*002ovPDHLkV1m|_Qpo@S diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/cloakofdarkness.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/cloakofdarkness.png index bc4de37643aea7be56377325199795dbdf3e332c..a45cec6973608442329788589ff9742ea0553029 100644 GIT binary patch delta 944 zcmV;h15fPk*29VcTnUbya|JxeR2i zJ;q2bXy@a=%yjkBFP>xPmf+~`?+=WPjkS5dlGkxsbL4!Vc5>)7gUg&`* zAOu0!l}x6R$$ylt>q7Ix=C8`&;Gnqv4>t$y-hQno^a}8{c@#)nXZ`fLu?XnKl5Q+N zSQ`bDe7+z!G)?m&`29}rP9@;hE!$5Bu=dw`1FxHH=Mv^QAHL?;qXIkj0O~2!GpJ`! zKj|1pGWnT6L23KB^pnK%)~u8$Z5;*@W=2Q^=*F^Rzkh^!YV&mHRnN2gjVGzDwXhJETT34fhb3NLFSiif zSqsR$(IatS&bl;iP9@CGg;9EU>>=G)>PWD*z8(pO1qd{?+5FiZX~=VnS99~$?6^54 za!@ELg@2+l9IH3QIqs~aJg=3HC5$v9i1BO(_Cdlt=h&0d?wfZfA3-8b)1uL+-z57WZJpJP zlz+b`s9f$+qR3NU{#_Y?7uvQRdr}NLaKT0W=wY1JYPE@piT|-oj~@6o8jbFMGhcOY zRNWg}9j{!ba*@h~E$%L_f8v8*Jc%?-1KxS}CU7NHsj~rPws<R;fvHevll?2FUv;P6)pnGNT Sgsf`-0000dvJY z+D(@wlw=FJRI`L#hzF;aBIe)(QFmG6YTT|_4{!I)?6_%dHh=ZUu=AVu-hO}Y_x=5O zFVt){o#n*zOq8BweSICMR;%JOYwtJX`LMH`04|)_{PfGG*gX+u1_lN~V`F0h^egED zn}2OIs5bc(eufPI?n0yeNS(Iww|GvTe8+WNv15Uz1T0^__Ck2i^9m*mBrO}BNLsg& z)-8y^7>`C? z1m3Vt02%w7f2mUFSK})2-E! zmpt(LqUW(;bP4is;$T+~GWKiI1HeP~ptIDo@F&!vE{DT`WySo8P{ysPPP%RsiucI~ zUDu^vsZ=`*{8SFfxzXbZV9vfcZcQbvFT|ikcb$WVxzg1@xl)Pgdbv^oLTzqZuYIwm zT(@*JKYwq}j$2bA28E(hC@LevTWxYqxoc?;wf2!zW4bPWV?I|NdL4wfo0zjN0vY=~ zC<6s@K*!$i&H+RQ|EqxmL(hsJj~-75iu}}j$@k=>yOwUD%lzq#Fwobh1}VBzx-Cj! z>1tkj0E`T8c>xGG0o1d)t_RQqRo6X>M4?G&zkiLL#9&{nS^d$=A~{_sDz0-BFwES@ z@K$E+!JA#zN*2wo-57F)nFBKRD*(06dNLq`ELw%1nN}}SPxWMA_s;`=nu<&fP~}Dm;+p~3k={(LBeEsJWBKvn916c=2>%8ks%5h)6`|trM z(tk9qzrWuv`q{ejfRB&;N9=1XQKbtd7x>UP~Lp&+bikX z78_9I9#_ZuYWwQg;zWpl`{Du_`tF;;h(XbiMJIe`y k?ssL!QYw|Cp3CL_0XETbjC-Y?jQ{`u07*qoM6N<$f=|HOkN^Mx diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_extended.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_extended.png index 10ca88140ca3e8cc850ff014ca8c90b6b539cdc4..6e93d2169bf1a66842a659e61a6b67359b9f0f93 100644 GIT binary patch delta 878 zcmV-!1CjiY2ZRTZBYy)+NklF|82>f?AMn}MyB6$hJf-)e`?#%WlU zU8UdQ@y++%e82Dee(%k^Gkbe`tC^nRu{cf)$8pHm+}s54dVl&->-BlIn&|;BZg|gM zT*m2vI9gg-B+5M;ou$28E@v2qVHgo3GKOba+S{A`%{k|sYnt|Y`Vtkv!n8j#KX#VA z8_<2NuDQ7xfTn3-BQgRAX0PDF4oaUVJ^^SO>=+)i#|`g(-*>6Ky`9qkmiekKpVQD( zLqp1@Nk$_Y8h_F7C9}jiA0CS*5(z0-wDo24v;-(D=L376;9Yv55e?ytp7r&GQv>j7 zYATq0H8oW>O)>(1fU{8Er5E-UJtC2p6B9w)vS~usDPk;{4Av}}CV*HniM8nKobzJ{ zR^Gn{FgrR5U~YRGqoXw1wmloF>~EouDh6T4Hgs-#8-IZ7x^}VXGByXa3SrweC3q~P z$|@BK>y{O93L$*ocU>32Qp15@>y}k06sjYj#3LNLySo5<-w!+2EsIL5n&7l>lm@nK z18^Cm#I0D2{GZ}+xUO9+MtW6s1wx1qjQPGFX$yIT5JCu&WbNX`>Ii}t!lCtYIU)(E zQhNmv1b;263WpM0D;5u%(#z#isdQ3;Bh<5EvAVjtlM)bQS&n#u+$BkpBl_4RX={EDLR`g%HzKUB5G z7Nt@t+D^2ix`gQPJVj9=AN|X@TrC8hot<}doqs}b&bx%*oHNE!sg$B9#0}uy$Vk(J z2S1Xc%9OqXyn{^x~o6XY2Hh82lfAuN=dX!ND;2_^N*g*j)`nchpS^2QvthXQU(v3BI zPp%oZZI2t?@K~I+UY}ph^t}Ce_n?jwV{vhjjG394KQ64VVPh0RkN^Mx07*qoM6N<$ Ef^iw5C;$Ke delta 890 zcmV-=1BLvA2apGlBYyx1a7bBm000XU000XU0RWnu7ytkRLP?rr z*xTEq>Fo54_PdL0eWedT$|$~m(~8Rj(X_R-1t|7P?<`%+<#L8$7>3~+_++J(rHhC2 zKQv8KRaI5h+36dU1oJU(r@!8Z?x}SxEiC|4RjnNOBmil>iR(X6`aH4PG13KKblgrE z#V13X0sLdI&VSC%*KZsEpC?uU+`;V{yp&B7cm|*m;g9JL0D2O=qvLic6gtY_WBNlQ z!i@;a1+RvNey=>j%+!>$&P+{}O_K&vPq+$x6`W`QuruO%J24^smQ547P9B5NsQl)O zX#xmFquB5-*EH=Mg3Qtqfcaz+z{37Ml1Y-bZO>O^R)1Nd6MArNeHZrk0l2Pf7YZ(8 z3qZRNwrx{@=i=mDl+Wk4EX$`9LU^9%x-Nh(;jq+iSyn!uua1BM_i-E?902e7Gv;}ouPmeyLI@$KB^%An z)e*=Am4B}Fa@p4s>Pqby_#jJE6;36%Qz)Ef(#z#isdQ0-GrDI5gLQRv7bPGlisIAA z*aLw;AP~6B9@K`D30`CdB9X{?QxoT0QIyI&s5pwEFvb{TobyN|@**=(%RvM1u%Th$ zBt7R`YiOX;xE+bqSfW@gM%#&Y)IA}(ibc-3?|-M?n#$2;NV&RqEIMw>NTtDKs-)zARd=`8p}KRZ;#*MeuvwW=shNQ`oL0EwPU2KW2DQ$&S|ko8e;V+3A-rt zBy)Pn`^)DW|M5OJ?nDA#)0+Ss=UXp Q;s5{u07*qoM6N<$f(P2Sxc~qF diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_retracted.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_retracted.png index f33d58483259f9237f7c5f4fe5a834d68245f930..84ef3097330838e86dd7b5871260b754e8c8118c 100644 GIT binary patch delta 915 zcmV;E18n@a2dD>-BYy*LNkldrnM_40 z&DU`sPEa(}Nw5?4dpI1Odv$*2<9;2PoSYmRU7DCyabP%(Lw`!6(ExDw)`{Eq&a$!5 zB>*#1{&(M>#NmNBW@l$5iaqL`75!?pD$BAg%P}P;qob_ow|{ucIp>_`a=E*=PEZoE zVHlS$h@ZEbpUtc(S>eo+v1W#6;{JCO%`uV`z zk8dwu5MO@%N`D#}8lv!DEdH)P`!I)NJrtx>Dv^>!CX3AT#iyL}iD@;RPD|mWH_sb~ z)qwi5TBvUmJS;xQA`^}1uerHsXaL-DIUIG%z62nfCf4G=bIx}%=!kz`zkdecS1Nv`GQYVQ%0KSh@hg>% zJ$N5%--6C>ZUXQ;&vsppv3cOM5Vmbofcv6z2K9Qq-E78;LWm#;JkJC0G_%vNcC%Tp z*SjO2z+)U+TU!8vAcz{<%_gPTHNjzFuO8U84ZvfJ0$-42vj3v0@I2dfW3}o!10lpC z#)2S-m4AgSLI@!QwPfx1@$Lx1AEK_$w_355P*-}-Acn9+U13*(Gp@Va(D_!Y`Q9S# znqZIaEy!{rkvJ#;K~a>LC5&B?BuSDEvj@GQGr^73Yr3xgcJ!#GX^Nsm^Pu7=iozIU zjA@#t>-vq=Yds!zS5m3@PIgVxcq&D^(bDxEOMg@-6lgioiaIC6uiP#nJNFs>Z9#q-G6+QUaZk~a?h}BduGa?m{!^CduPW+ pm+n8lx|7F&vAn!Y%HZJOJD<3q->ekkDGUGr002ovPDHLkV1m7l$SeQ= delta 930 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 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/hypnotise.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/hypnotise.png index 6f909d223d8e51ded82701bfd13d6e7c54cd4594..8c5d431f5d296d598a3aadfc13d7df4ee958fcac 100644 GIT binary patch delta 1139 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 KMNUMnLSTY^Ya_n^ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/summonheirloom.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/summonheirloom.png new file mode 100644 index 0000000000000000000000000000000000000000..824c3e963094833eeaf780473e426a4f639a8800 GIT binary patch literal 2008 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 N002ovPDHLkV1nlAHvRwr From c862f3aa57066dafbede2a932369aba7e0312303 Mon Sep 17 00:00:00 2001 From: Rainfey Date: Fri, 19 Jan 2024 21:09:55 +0000 Subject: [PATCH 08/67] Checkpoint incase i need to undo something --- .../Store/Events/StorePurchasedActionEvent.cs | 9 --- .../Store/Systems/StoreSystem.Ui.cs | 6 +- .../Vampire/VampireSystem.Abilities.cs | 71 ++++++++++++++++--- .../Vampire/VampireSystem.Transform.cs | 15 +++- Content.Server/Vampire/VampireSystem.cs | 69 ++++++++++++++---- .../Store/Events/StoreProductEvent.cs | 3 + .../Store/Events/StorePurchasedActionEvent.cs | 3 + Content.Shared/Store/ListingPrototype.cs | 1 + Content.Shared/Vampire/VampireComponent.cs | 24 +++++-- Content.Shared/Vampire/VampireEvents.cs | 12 ++-- Resources/Locale/en-US/Vampires/vampires.ftl | 1 + Resources/Prototypes/Actions/vampire.yml | 23 +++--- .../Prototypes/Catalog/vampire_catalog.yml | 43 +++++++++++ 13 files changed, 222 insertions(+), 58 deletions(-) delete mode 100644 Content.Server/Store/Events/StorePurchasedActionEvent.cs create mode 100644 Content.Shared/Store/Events/StoreProductEvent.cs create mode 100644 Content.Shared/Store/Events/StorePurchasedActionEvent.cs diff --git a/Content.Server/Store/Events/StorePurchasedActionEvent.cs b/Content.Server/Store/Events/StorePurchasedActionEvent.cs deleted file mode 100644 index b91fe8ef4c1..00000000000 --- a/Content.Server/Store/Events/StorePurchasedActionEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Content.Server.Store.Events; - -public record struct StorePurchasedActionEvent(EntityUid Purchaser, EntityUid Action); diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 297d1bae4b7..e2748302959 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -4,12 +4,12 @@ using Content.Server.PDA.Ringer; using Content.Server.Stack; using Content.Server.Store.Components; -using Content.Server.Store.Events; using Content.Server.UserInterface; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.Store; +using Content.Shared.Store.Events; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -178,7 +178,9 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi //broadcast event if (listing.ProductEvent != null) { - RaiseLocalEvent(listing.ProductEvent); + var ev = new StoreProductEvent(buyer, listing, listing.ProductEvent); + RaiseLocalEvent(uid, ev); + RaiseLocalEvent(buyer, ev); } //log dat shit. diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 6bd3157cf2d..553326f982b 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -5,6 +5,7 @@ using Content.Server.Speech.Components; using Content.Server.Storage.Components; using Content.Server.Store.Components; +using Content.Shared.Actions; using Content.Shared.Bed.Sleep; using Content.Shared.Body.Components; using Content.Shared.Chat.Prototypes; @@ -20,10 +21,13 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Popups; +using Content.Shared.Prying.Components; using Content.Shared.Stealth.Components; +using Content.Shared.Store.Events; using Content.Shared.Stunnable; using Content.Shared.Vampire; using Content.Shared.Vampire.Components; +using Content.Shared.Weapons.Melee; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Utility; @@ -35,14 +39,18 @@ public sealed partial class VampireSystem /// /// Upon using any power that does not require a target /// - private void OnUseSelfPower(EntityUid uid, VampireComponent component, VampireSelfPowerEvent args) => args.Handled = TriggerPower((uid, component), args.Type, args.Details); + private void OnUseSelfPower(EntityUid uid, VampireComponent component, VampireSelfPowerEvent args) + => args.Handled = TriggerPower((uid, component), args.Details); + /// /// Upon using any power that requires a target /// - private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireTargetedPowerEvent args) => args.Handled = TriggerPower((uid, component), args.Type, args.Details, args.Target); - private bool TriggerPower(Entity vampire, VampirePowerKey powerType, VampirePowerDetails def, EntityUid? target = null) + private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireTargetedPowerEvent args) + => args.Handled = TriggerPower((uid, component), args.Details, args.Target); + + private bool TriggerPower(Entity vampire, VampirePowerDetails def, EntityUid? target = null) { - if (!IsAbilityUnlocked(vampire, powerType)) + if (!IsAbilityUnlocked(vampire, def.Type)) return false; //Block if we are cuffed @@ -66,16 +74,24 @@ private bool TriggerPower(Entity vampire, VampirePowerKey powe return false; } + //Block if we dont have enough essence if (def.ActivationCost > 0 && !SubtractBloodEssence(vampire, def.ActivationCost)) { _popup.PopupClient(Loc.GetString("vampire-not-enough-blood"), vampire, vampire, PopupType.MediumCaution); return false; } + //Check if we are near an anchored prayable entity - ie the chapel + if (IsNearPrayable(vampire)) + { + //Warning about holy power + return false; + } + var success = true; //TODO: Rewrite when a magic effect system is introduced (like reagents) - switch (powerType) + switch (def.Type) { case VampirePowerKey.SummonHeirloom: { @@ -132,6 +148,29 @@ private bool TriggerPower(Entity vampire, VampirePowerKey powe return success; } + #region Passive Powers + private void UnnaturalStrength(Entity vampire, VampirePowerDetails def) + { + if (def.Damage is null) + return; + + var meleeComp = EnsureComp(vampire); + meleeComp.Damage += def.Damage; + } + private void SupernaturalStrength(Entity vampire, VampirePowerDetails def) + { + var pryComp = EnsureComp(vampire); + pryComp.Force = true; + pryComp.PryPowered = true; + + if (def.Damage is null) + return; + + var meleeComp = EnsureComp(vampire); + meleeComp.Damage += def.Damage; + } + #endregion + #region Other Powers /// /// Spawn and bind the pendant if one does not already exist, otherwise just summon to the vampires hand @@ -159,9 +198,7 @@ private void SummonHeirloom(Entity vampire) } private void Screech(Entity vampire, TimeSpan? duration, DamageSpecifier? damage = null) { - var transform = Transform(vampire.Owner); - - foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries)) + foreach (var entity in _entityLookup.GetEntitiesInRange(vampire, 3, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries)) { if (HasComp(entity)) continue; @@ -344,8 +381,22 @@ private void HypnotiseDoAfter(Entity vampire, ref VampireHypno private void OnVampireStateChanged(EntityUid uid, VampireComponent component, MobStateChangedEvent args) { if (args.OldMobState != MobState.Dead && args.NewMobState == MobState.Dead) - OnUseSelfPower(uid, component, new() { Type = VampirePowerKey.DeathsEmbrace, Details = new() { ActivationCost = 100 } }); + { + var action = GetAbilityEntity(component, VampirePowerKey.DeathsEmbrace); + if (action == null) + return; + + if (!TryComp(action, out var instantActionComponent)) + return; + + var def = instantActionComponent.Event as VampireSelfPowerEvent; + if (def == null) + return; + + OnUseSelfPower(uid, component, def); + } } + /// /// When the vampire is inserted into a container (ie locker, crate etc) check for a coffin, and bind their home to it /// @@ -560,6 +611,7 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume * 0.05)); //Thou shall not feed upon the blood of the holy + //TODO: Replace with raised event? if (HasComp(args.Target)) { _damageableSystem.TryChangeDamage(entity, VampireComponent.HolyDamage, true); @@ -567,6 +619,7 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood _admin.Add(LogType.Damaged, LogImpact.Low, $"{ToPrettyString(entity):user} attempted to drink {volumeToConsume}u of {ToPrettyString(args.Target):target}'s holy blood"); return; } + //Check for zombie else { //Pull out some of the blood diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index c6496dfe467..45b9c19bd82 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -6,6 +6,7 @@ using Content.Shared.Body.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Nutrition.Components; using Content.Shared.Vampire.Components; using Content.Shared.Weapons.Melee; using Robust.Shared.Audio; @@ -17,20 +18,21 @@ public sealed partial class VampireSystem /// /// Convert the players into a vampire, all programatic because i dont want to replace the players body /// - /// Which entity to convert private void MakeVampire(EntityUid vampireUid) { var vampireComponent = EnsureComp(vampireUid); var vampire = new Entity(vampireUid, vampireComponent); - EnsureComp(vampire); + //Render them unable to rot, immune to pressure and thirst RemComp(vampire); RemComp(vampire); + RemComp(vampire); //Unsure, should vampires thirst.. or hunger? + //Render immune to cold, but not heat if (TryComp(vampire, out var temperatureComponent)) temperatureComponent.ColdDamageThreshold = Atmospherics.TCMB; - //Extra melee power + // if (TryComp(vampire, out var melee)) { melee.Damage = VampireComponent.MeleeDamage; @@ -48,8 +50,15 @@ private void MakeVampire(EntityUid vampireUid) //Order of operation requirement, must be called after initialising balance UpdateBloodDisplay(vampire); } + + /// + /// Add vulnerability to holy water when ingested or slashed, and take damage from the bible + /// private void MakeVulnerableToHoly(Entity vampire) { + //React to being beaten with the bible + EnsureComp(vampire); + //Take damage from holy water splash if (TryComp(vampire, out var reactive)) { diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index e59c4b9dad3..4c05989215d 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -9,12 +9,13 @@ using Content.Server.Polymorph.Systems; using Content.Server.Storage.EntitySystems; using Content.Server.Store.Components; -using Content.Server.Store.Events; +using Content.Shared.Store.Events; using Content.Server.Store.Systems; using Content.Shared.Actions; using Content.Shared.Body.Systems; using Content.Shared.Buckle; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Construction.Components; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Examine; @@ -23,9 +24,11 @@ using Content.Shared.Humanoid; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; +using Content.Shared.Maps; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; +using Content.Shared.Prayer; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; using Content.Shared.Vampire; @@ -76,23 +79,46 @@ public override void Initialize() SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnInteractHandEvent); - /*SubscribeLocalEvent(OnInteractWithHumanoid, - before: new[] { typeof(InteractionPopupSystem), typeof(SleepingSystem), typeof(SharedBuckleSystem), typeof(SharedStunSystem) }); - */ SubscribeLocalEvent(DrinkDoAfter); SubscribeLocalEvent(HypnotiseDoAfter); - //SubscribeLocalEvent(OnSummonHeirloom); SubscribeLocalEvent(OnInsertedIntoContainer); SubscribeLocalEvent(OnRemovedFromContainer); SubscribeLocalEvent(OnVampireStateChanged); SubscribeLocalEvent(OnUseSelfPower); SubscribeLocalEvent(OnUseTargetedPower); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnStorePurchasePassive); SubscribeLocalEvent(OnUseHeirloom); SubscribeLocalEvent(OnStorePurchase); } + private void OnStorePurchasePassive(EntityUid uid, VampireComponent component, StoreProductEvent args) + { + if (args.Ev is not VampirePowerDetails) + return; + + var def = args.Ev as VampirePowerDetails; + if (def == null) + return; + + var vampire = new Entity(uid, component); + + switch (def.Type) + { + case VampirePowerKey.UnnaturalStrength: + { + UnnaturalStrength(vampire, def); + break; + } + case VampirePowerKey.SupernaturalStrength: + { + SupernaturalStrength(vampire, def); + break; + } + } + } + /// /// Handles healing and damaging in space /// @@ -170,13 +196,15 @@ private void OnStorePurchase(EntityUid purchaser, EntityUid purchasedAction) if (TryComp(purchasedAction, out var instantAction) && instantAction.Event != null && instantAction.Event is VampireSelfPowerEvent) { var vampirePower = instantAction.Event as VampireSelfPowerEvent; - vampireComponent.UnlockedPowers[vampirePower!.Type] = purchasedAction; + if (vampirePower == null) return; + vampireComponent.UnlockedPowers[vampirePower.Details.Type] = purchasedAction; return; } if (TryComp(purchasedAction, out var targetAction) && targetAction.Event != null && targetAction.Event is VampireTargetedPowerEvent) { var vampirePower = targetAction.Event as VampireTargetedPowerEvent; - vampireComponent.UnlockedPowers[vampirePower!.Type] = purchasedAction; + if (vampirePower == null) return; + vampireComponent.UnlockedPowers[vampirePower.Details.Type] = purchasedAction; return; } @@ -254,14 +282,11 @@ private FixedPoint2 GetBloodEssence(Entity vampire) return val; } - /*private void DoSpaceDamage(Entity vampire) + private void DoSpaceDamage(Entity vampire) { - if (!GetAbilityDefinition(vampire.Comp, VampirePowerKey.StellarWeakness, out var def) || def == null) - return; - - _damageableSystem.TryChangeDamage(vampire, def.Damage, true, origin: vampire); - _popup.PopupEntity(Loc.GetString("vampire-startlight-burning"), vampire, vampire, Shared.Popups.PopupType.LargeCaution); - }*/ + _damageableSystem.TryChangeDamage(vampire, VampireComponent.SpaceDamage, true, origin: vampire); + _popup.PopupEntity(Loc.GetString("vampire-startlight-burning"), vampire, vampire, PopupType.LargeCaution); + } private bool IsInSpace(EntityUid vampireUid) { var vampireTransform = Transform(vampireUid); @@ -273,6 +298,20 @@ private bool IsInSpace(EntityUid vampireUid) if (!_mapSystem.TryGetTileRef(vampireUid, grid, vampireTransform.Coordinates, out var tileRef)) return true; - return tileRef.Tile.IsEmpty; + return tileRef.Tile.IsEmpty || tileRef.IsSpace(); + } + + private bool IsNearPrayable(EntityUid vampireUid) + { + var mapCoords = _transform.GetMapCoordinates(vampireUid); + + var nearbyPrayables = _entityLookup.GetEntitiesInRange(mapCoords, 5); + foreach (var prayable in nearbyPrayables) + { + if (Transform(prayable).Anchored) + return true; + } + + return false; } } diff --git a/Content.Shared/Store/Events/StoreProductEvent.cs b/Content.Shared/Store/Events/StoreProductEvent.cs new file mode 100644 index 00000000000..c764cb6d8d4 --- /dev/null +++ b/Content.Shared/Store/Events/StoreProductEvent.cs @@ -0,0 +1,3 @@ +namespace Content.Shared.Store.Events; + +public record struct StoreProductEvent(EntityUid Purchaser, ListingData Listing, object? Ev); diff --git a/Content.Shared/Store/Events/StorePurchasedActionEvent.cs b/Content.Shared/Store/Events/StorePurchasedActionEvent.cs new file mode 100644 index 00000000000..29aa5b100ea --- /dev/null +++ b/Content.Shared/Store/Events/StorePurchasedActionEvent.cs @@ -0,0 +1,3 @@ +namespace Content.Shared.Store.Events; + +public record struct StorePurchasedActionEvent(EntityUid Purchaser, EntityUid Action); diff --git a/Content.Shared/Store/ListingPrototype.cs b/Content.Shared/Store/ListingPrototype.cs index d80c5cf6c83..147daa60679 100644 --- a/Content.Shared/Store/ListingPrototype.cs +++ b/Content.Shared/Store/ListingPrototype.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Shared.FixedPoint; +using Content.Shared.Store.Events; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index 059f935a008..46bbeefb615 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -42,6 +42,10 @@ public sealed partial class VampireComponent : Component { DamageDict = new Dictionary() { { "Burn", 10 } } }; + public static readonly DamageSpecifier SpaceDamage = new() + { + DamageDict = new Dictionary() { { "Burn", 2.5 } } + }; [ValidatePrototypeId] public static readonly string SummonActionPrototype = "ActionVampireSummonHeirloom"; @@ -90,9 +94,11 @@ public sealed partial class VampireComponent : Component /// /// Contains all details about the ability and its effects or restrictions /// -[DataDefinition] -public sealed partial class VampirePowerDetails +[DataDefinition +public sealed partial class VampirePowerProtype { + [DataField(required: true)] + public VampirePowerKey Type; [DataField] public float ActivationCost = 0; [DataField] @@ -113,10 +119,17 @@ public sealed partial class VampirePowerDetails public float Upkeep = 0; } +/// +/// Marks an entity as taking damage when hit by a bible, rather than being healed +/// [RegisterComponent] public sealed partial class UnholyComponent : Component { } + +/// +/// Marks a container as a coffin, for the purposes of vampire healing +/// [RegisterComponent] public sealed partial class CoffinComponent : Component { @@ -173,6 +186,9 @@ public enum VampirePowerKey : byte BloodSteal, CloakOfDarkness, StellarWeakness, - SupernaturalStrength, - SummonHeirloom + SummonHeirloom, + + //Passives + UnnaturalStrength, + SupernaturalStrength } diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index 9698144f27a..ed4711a3707 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -1,6 +1,7 @@ using Content.Shared.Actions; using Content.Shared.Damage; using Content.Shared.DoAfter; +using Content.Shared.Store.Events; using Content.Shared.Vampire.Components; using Robust.Shared.Serialization; @@ -9,22 +10,19 @@ namespace Content.Shared.Vampire; //Use power events public sealed partial class VampireSelfPowerEvent : InstantActionEvent { - [DataField] - public VampirePowerKey Type; [DataField] public VampirePowerDetails Details = new(); }; public sealed partial class VampireTargetedPowerEvent : EntityTargetActionEvent { - [DataField] - public VampirePowerKey Type; [DataField] public VampirePowerDetails Details = new(); }; -/*public sealed partial class VampireSummonHeirloomEvent : InstantActionEvent +public sealed partial class VampirePassiveActionEvent : BaseActionEvent { - -}*/ + [DataField] + public VampirePowerDetails Details = new(); +}; //Doafter events [Serializable, NetSerializable] diff --git a/Resources/Locale/en-US/Vampires/vampires.ftl b/Resources/Locale/en-US/Vampires/vampires.ftl index b22d4af0730..4d068638be1 100644 --- a/Resources/Locale/en-US/Vampires/vampires.ftl +++ b/Resources/Locale/en-US/Vampires/vampires.ftl @@ -6,6 +6,7 @@ vampire-fangs-retracted = You retract your fangs vampire-blooddrink-empty = This body is devoid of blood vampire-blooddrink-rotted = Their body is rotting and their blood tainted +vampire-blooddrink-zombie = Their blood is tainted by death vampire-startlight-burning = You feel your skin burn in the light of a thousand suns diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index 399213138cb..154a4ab1e6b 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -10,7 +10,11 @@ state: summonheirloom event: !type:VampireSelfPowerEvent - type: SummonHeirloom + details: + type: SummonHeirloom + usableWhileCuffed: false + usableWhileStunned: false + useDelay: 60 - type: entity id: ActionVampireToggleFangs @@ -29,7 +33,8 @@ state: fangs_extended event: !type:VampireSelfPowerEvent - type: ToggleFangs + details: + type: ToggleFangs - type: entity id: ActionVampireGlare @@ -49,8 +54,8 @@ path: /Audio/Effects/Vampire/glare.ogg event: !type:VampireTargetedPowerEvent - type: Glare details: + type: Glare duration: 10 useDelay: 60 @@ -72,8 +77,8 @@ state: hypnotise event: !type:VampireTargetedPowerEvent - type: Hypnotise details: + type: Hypnotise duration: 60 doAfterDelay: 5 useDelay: 300 @@ -94,8 +99,8 @@ path: /Audio/Effects/Vampire/screech_tone.ogg event: !type:VampireSelfPowerEvent - type: Screech details: + type: Screech duration: 3 damage: types: @@ -121,8 +126,8 @@ path: /Audio/Effects/demon_consume.ogg event: !type:VampireSelfPowerEvent - type: BloodSteal details: + type: BloodSteal usableWhileStunned: false usableWhileCuffed: false activationCost: 20 @@ -144,8 +149,8 @@ path: /Audio/Effects/teleport_arrival.ogg event: !type:VampireSelfPowerEvent - type: Polymorph details: + type: Polymorph usableWhileStunned: false usableWhileCuffed: false polymorphTarget: mobBatVampire @@ -168,8 +173,8 @@ path: /Audio/Effects/teleport_arrival.ogg event: !type:VampireSelfPowerEvent - type: Polymorph details: + type: Polymorph usableWhileStunned: false usableWhileCuffed: false polymorphTarget: MobMouse @@ -190,8 +195,8 @@ state: cloakofdarkness event: !type:VampireSelfPowerEvent - type: CloakOfDarkness details: + type: CloakOfDarkness usableWhileStunned: false activationCost: 30 upkeep: 1 diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml index e01a58ebdad..e2db2befdf2 100644 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -127,6 +127,49 @@ whitelist: - VampireToggleFangs +#Passives +- type: listing + id: VampireUnnaturalStrength + name: vampire-ability-unnaturalstrength + description: vampire-ability-unnaturalstrength-description + cost: + BloodEssence: 20 + productEvent: + !type:VampirePowerDetails + type: UnnaturalStrength + damage: + types: + Slash: 5 + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireToggleFangs + +- type: listing + id: VampireSupernaturalStrength + name: vampire-ability-supernaturalstrength + description: vampire-ability-supernaturalstrength-description + cost: + BloodEssence: 400 + productEvent: + !type:VampirePowerDetails + type: SupernaturalStrength + damage: + types: + Slash: 5 + categories: + - VampireAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireUnnaturalStrength + #- type: listing # id: VampireSupernaturalStrength # name: Supernatural Strength From da2504fb5598782d338b56fb248b3d56a868c29a Mon Sep 17 00:00:00 2001 From: Rainfey Date: Sat, 20 Jan 2024 02:00:01 +0000 Subject: [PATCH 09/67] Rewrite to use event per power, working passives --- .../Store/Systems/StoreSystem.Ui.cs | 14 +- .../Vampire/VampireSystem.Abilities.cs | 414 +++++++++++------- .../Vampire/VampireSystem.Transform.cs | 28 +- Content.Server/Vampire/VampireSystem.cs | 74 ++-- .../Store/Events/StoreProductEvent.cs | 3 - .../Store/Events/StorePurchasedActionEvent.cs | 3 - .../Events/StorePurchasedListingEvent.cs | 3 + Content.Shared/Vampire/VampireComponent.cs | 93 ++-- Content.Shared/Vampire/VampireEvents.cs | 39 +- Resources/Locale/en-US/Vampires/vampires.ftl | 54 ++- Resources/Prototypes/Actions/vampire.yml | 107 ++--- .../Prototypes/Catalog/vampire_catalog.yml | 65 +-- .../Entities/Objects/Misc/heirloom.yml | 3 +- Resources/Prototypes/Store/categories.yml | 8 +- .../Prototypes/Vampire/vampire-powers.yml | 99 +++++ .../Actions/actions_vampire.rsi/meta.json | 6 + .../supernaturalstrength.png | Bin 0 -> 1756 bytes .../actions_vampire.rsi/unholystrength.png | Bin 0 -> 1712 bytes 18 files changed, 623 insertions(+), 390 deletions(-) delete mode 100644 Content.Shared/Store/Events/StoreProductEvent.cs delete mode 100644 Content.Shared/Store/Events/StorePurchasedActionEvent.cs create mode 100644 Content.Shared/Store/Events/StorePurchasedListingEvent.cs create mode 100644 Resources/Prototypes/Vampire/vampire-powers.yml create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/supernaturalstrength.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/unholystrength.png diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index e2748302959..b0a2cb3c28a 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -157,11 +157,14 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi component.Balance[currency.Key] -= currency.Value; } + var ev = new StorePurchasedListingEvent() { Purchaser = buyer, Listing = listing }; + //spawn entity if (listing.ProductEntity != null) { var product = Spawn(listing.ProductEntity, Transform(buyer).Coordinates); _hands.PickupOrDrop(buyer, product); + ev.Item = product; } //give action @@ -170,19 +173,18 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi // I guess we just allow duplicate actions? var actionUid = _actions.AddAction(buyer, listing.ProductAction); - //Raise a purchase event for handling downstream - if (actionUid != null) - RaiseLocalEvent(uid, new StorePurchasedActionEvent(buyer, actionUid.Value)); + ev.Action = actionUid; } //broadcast event if (listing.ProductEvent != null) { - var ev = new StoreProductEvent(buyer, listing, listing.ProductEvent); - RaiseLocalEvent(uid, ev); - RaiseLocalEvent(buyer, ev); + RaiseLocalEvent(listing.ProductEvent); } + RaiseLocalEvent(uid, ev); + RaiseLocalEvent(buyer, ev); + //log dat shit. _admin.Add(LogType.StorePurchase, LogImpact.Low, $"{ToPrettyString(buyer):player} purchased listing \"{Loc.GetString(listing.Name)}\" from {ToPrettyString(uid)}"); diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 553326f982b..7ab327f032d 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -28,29 +28,196 @@ using Content.Shared.Vampire; using Content.Shared.Vampire.Components; using Content.Shared.Weapons.Melee; +using FastAccessors; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Utility; +using System.Collections.Frozen; +using System.Diagnostics.CodeAnalysis; namespace Content.Server.Vampire; public sealed partial class VampireSystem { - /// - /// Upon using any power that does not require a target - /// - private void OnUseSelfPower(EntityUid uid, VampireComponent component, VampireSelfPowerEvent args) - => args.Handled = TriggerPower((uid, component), args.Details); + private FrozenDictionary _powerCache = default!; + private FrozenDictionary _passiveCache = default!; - /// - /// Upon using any power that requires a target - /// - private void OnUseTargetedPower(EntityUid uid, VampireComponent component, VampireTargetedPowerEvent args) - => args.Handled = TriggerPower((uid, component), args.Details, args.Target); + private void InitializePowers() + { + _powerCache = BuildPowerCache(); + _passiveCache = BuildPassiveCache(); + + //Abilities + SubscribeLocalEvent(OnVampireSummonHeirloom); + SubscribeLocalEvent(OnVampireToggleFangs); + SubscribeLocalEvent(OnVampireGlare); + SubscribeLocalEvent(OnVampireScreech); + SubscribeLocalEvent(OnVampirePolymorph); + SubscribeLocalEvent(OnVampireHypnotise); + SubscribeLocalEvent(OnVampireBloodSteal); + SubscribeLocalEvent(OnVampireCloakOfDarkness); + + //Hypnotise + SubscribeLocalEvent(HypnotiseDoAfter); + + //Drink Blood + SubscribeLocalEvent(OnInteractHandEvent); + SubscribeLocalEvent(DrinkDoAfter); + + //Deaths embrace + SubscribeLocalEvent(OnInsertedIntoContainer); + SubscribeLocalEvent(OnRemovedFromContainer); + SubscribeLocalEvent(OnVampireStateChanged); + + } + + #region Ability Entry Points + private void OnVampireSummonHeirloom(EntityUid entity, VampireComponent component, VampireSummonHeirloomEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + SummonHeirloom(vampire); + + ev.Handled = true; + } + private void OnVampireToggleFangs(EntityUid entity, VampireComponent component, VampireToggleFangsEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + var actionEntity = GetPowerEntity(vampire, def.ID); + if (actionEntity == null) + return; + + var toggled = ToggleFangs(vampire); + + _action.SetToggled(actionEntity, toggled); + + ev.Handled = true; + } + private void OnVampireGlare(EntityUid entity, VampireComponent component, VampireGlareEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + Glare(vampire, ev.Target, def.Duration, def.Damage); + + ev.Handled = true; + } + private void OnVampireScreech(EntityUid entity, VampireComponent component, VampireScreechEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + Screech(vampire, def.Duration, def.Damage); + + ev.Handled = true; + } + private void OnVampirePolymorph(EntityUid entity, VampireComponent component, VampirePolymorphEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + PolymorphSelf(vampire, def.PolymorphTarget); + + ev.Handled = true; + } + private void OnVampireHypnotise(EntityUid entity, VampireComponent component, VampireHypnotiseEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + ev.Handled = TryHypnotise(vampire, ev.Target, def.Duration, def.DoAfterDelay); + } + private void OnVampireBloodSteal(EntityUid entity, VampireComponent component, VampireBloodStealEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + BloodSteal(vampire); + + ev.Handled = true; + } + private void OnVampireCloakOfDarkness(EntityUid entity, VampireComponent component, VampireCloakOfDarknessEvent ev) + { + if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) + return; + + var vampire = new Entity(entity, component); + + if (!IsAbilityUsable(vampire, def)) + return; + + var actionEntity = GetPowerEntity(vampire.Comp, def.ID); + if (actionEntity == null) + return; + + var toggled = CloakOfDarkness(vampire, def.Upkeep, 0.75f, -0.5f); + + _action.SetToggled(actionEntity, toggled); + + ev.Handled = true; + } + private void OnInteractHandEvent(EntityUid uid, VampireComponent component, BeforeInteractHandEvent args) + { + if (!HasComp(args.Target)) + return; + + if (args.Target == uid) + return; + + if (!TryGetPowerDefinition(VampireComponent.DrinkBloodPrototype, out var def)) + return; - private bool TriggerPower(Entity vampire, VampirePowerDetails def, EntityUid? target = null) + var vampire = new Entity(uid, component); + + args.Handled = TryDrink(vampire, args.Target, def.DoAfterDelay!.Value); + } + #endregion + + + private bool TryGetPowerDefinition(string name, [NotNullWhen(true)] out VampirePowerProtype? definition) + => _powerCache.TryGetValue(name, out definition); + + private bool IsAbilityUsable(Entity vampire, VampirePowerProtype def) { - if (!IsAbilityUnlocked(vampire, def.Type)) + if (!IsPowerUnlocked(vampire, def.ID)) return false; //Block if we are cuffed @@ -88,68 +255,12 @@ private bool TriggerPower(Entity vampire, VampirePowerDetails return false; } - var success = true; - - //TODO: Rewrite when a magic effect system is introduced (like reagents) - switch (def.Type) - { - case VampirePowerKey.SummonHeirloom: - { - SummonHeirloom(vampire); - break; - } - case VampirePowerKey.ToggleFangs: - { - ToggleFangs(vampire); - break; - } - case VampirePowerKey.DeathsEmbrace: - { - success = TryMoveToCoffin(vampire); - break; - } - case VampirePowerKey.Glare: - { - Glare(vampire, target, def.Duration, def.Damage); - break; - } - case VampirePowerKey.Screech: - { - Screech(vampire, def.Duration, def.Damage); - break; - } - case VampirePowerKey.Polymorph: - { - PolymorphSelf(vampire, def.PolymorphTarget); - break; - } - case VampirePowerKey.Hypnotise: - { - success = TryHypnotise(vampire, target, def.Duration, def.DoAfterDelay); - break; - } - case VampirePowerKey.BloodSteal: - { - BloodSteal(vampire); - break; - } - case VampirePowerKey.CloakOfDarkness: - { - CloakOfDarkness(vampire, def.Upkeep, -1, 1); - break; - } - default: - break; - } - - - //if (success) - // _action.StartUseDelay(GetAbilityEntity(vampire, powerType)); - return success; + return true; } + #region Passive Powers - private void UnnaturalStrength(Entity vampire, VampirePowerDetails def) + /*private void UnnaturalStrength(Entity vampire, VampirePowerDetails def) { if (def.Damage is null) return; @@ -168,7 +279,7 @@ private void SupernaturalStrength(Entity vampire, VampirePower var meleeComp = EnsureComp(vampire); meleeComp.Damage += def.Damage; - } + }*/ #endregion #region Other Powers @@ -299,22 +410,18 @@ private void BloodSteal(Entity vampire) //Update abilities, add new unlocks //UpdateAbilities(vampire); } - private void CloakOfDarkness(Entity vampire, float upkeep, float passiveVisibilityRate, float movementVisibilityRate) + private bool CloakOfDarkness(Entity vampire, float upkeep, float passiveVisibilityRate, float movementVisibilityRate) { - var actionEntity = GetAbilityEntity(vampire.Comp, VampirePowerKey.CloakOfDarkness); - if (IsAbilityActive(vampire.Comp, VampirePowerKey.CloakOfDarkness)) + if (HasComp(vampire)) { - SetAbilityActive(vampire.Comp, VampirePowerKey.CloakOfDarkness, false); - _action.SetToggled(actionEntity, false); RemComp(vampire); RemComp(vampire); RemComp(vampire); _popup.PopupEntity(Loc.GetString("vampire-cloak-disable"), vampire, vampire); + return false; } else { - SetAbilityActive(vampire.Comp, VampirePowerKey.CloakOfDarkness, true); - _action.SetToggled(actionEntity, true); EnsureComp(vampire); var stealthMovement = EnsureComp(vampire); stealthMovement.PassiveVisibilityRate = passiveVisibilityRate; @@ -322,6 +429,7 @@ private void CloakOfDarkness(Entity vampire, float upkeep, flo var vampireStealth = EnsureComp(vampire); vampireStealth.Upkeep = upkeep; _popup.PopupEntity(Loc.GetString("vampire-cloak-enable"), vampire, vampire); + return true; } } #endregion @@ -339,7 +447,7 @@ private bool TryHypnotise(Entity vampire, EntityUid? target, T return false; var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, delay ?? TimeSpan.FromSeconds(5), - new VampireHypnotiseEvent() { Duration = duration }, + new VampireHypnotiseDoAfterEvent() { Duration = duration }, eventTarget: vampire, target: target, used: target) @@ -362,7 +470,7 @@ private bool TryHypnotise(Entity vampire, EntityUid? target, T } return true; } - private void HypnotiseDoAfter(Entity vampire, ref VampireHypnotiseEvent args) + private void HypnotiseDoAfter(Entity vampire, ref VampireHypnotiseDoAfterEvent args) { if (!args.Target.HasValue) return; @@ -378,34 +486,24 @@ private void HypnotiseDoAfter(Entity vampire, ref VampireHypno /// /// When the vampire dies, attempt to activate the Deaths Embrace power /// - private void OnVampireStateChanged(EntityUid uid, VampireComponent component, MobStateChangedEvent args) + private void OnVampireStateChanged(EntityUid uid, VampireDeathsEmbraceComponent component, MobStateChangedEvent args) { if (args.OldMobState != MobState.Dead && args.NewMobState == MobState.Dead) { - var action = GetAbilityEntity(component, VampirePowerKey.DeathsEmbrace); - if (action == null) - return; - - if (!TryComp(action, out var instantActionComponent)) - return; - - var def = instantActionComponent.Event as VampireSelfPowerEvent; - if (def == null) - return; - - OnUseSelfPower(uid, component, def); + //Home still exists? + TryMoveToCoffin((uid, component)); } } - /// /// When the vampire is inserted into a container (ie locker, crate etc) check for a coffin, and bind their home to it /// - private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, EntGotInsertedIntoContainerMessage args) + private void OnInsertedIntoContainer(EntityUid uid, VampireDeathsEmbraceComponent component, EntGotInsertedIntoContainerMessage args) { if (HasComp(args.Container.Owner)) { component.HomeCoffin = args.Container.Owner; - EnsureComp(args.Entity); + var vhComp = EnsureComp(args.Entity); + vhComp.Healing = component.CoffinHealing; _popup.PopupEntity(Loc.GetString("vampire-deathsembrace-bind"), uid, uid); _admin.Add(LogType.Damaged, LogImpact.Low, $"{ToPrettyString(uid):user} bound a new coffin"); @@ -414,15 +512,14 @@ private void OnInsertedIntoContainer(EntityUid uid, VampireComponent component, /// /// When leaving a container, remove the healing component /// - private void OnRemovedFromContainer(EntityUid uid, VampireComponent component, EntGotRemovedFromContainerMessage args) + private void OnRemovedFromContainer(EntityUid uid, VampireDeathsEmbraceComponent component, EntGotRemovedFromContainerMessage args) { - //Presence check is done upstream RemComp(args.Entity); } /// /// Attempt to move the vampire to their bound coffin /// - private bool TryMoveToCoffin(Entity vampire) + private bool TryMoveToCoffin(Entity vampire) { if (!vampire.Comp.HomeCoffin.HasValue) return false; @@ -463,6 +560,9 @@ private bool TryMoveToCoffin(Entity vampire) /// private void DoCoffinHeal(EntityUid vampire, VampireHealingComponent healing) { + if (healing.Healing == null) + return; + _damageableSystem.TryChangeDamage(vampire, healing.Healing, true, origin: vampire); //If they are dead and we are below the death threshold - revive @@ -488,57 +588,34 @@ private void DoCoffinHeal(EntityUid vampire, VampireHealingComponent healing) #region Blood Drinking /// - /// Check and start drinking blood from a humanoid + /// Toggle if fangs are extended /// - /*private void OnInteractWithHumanoid(EntityUid uid, HumanoidAppearanceComponent component, InteractHandEvent args) - { - if (args.Handled) - return; - - if (args.Target == args.User) - return; - - if (!TryComp(args.User, out var vampireComponent)) - return; - - args.Handled = TryDrink((args.User, vampireComponent), args.Target, TimeSpan.FromSeconds(1)); - }*/ - private void OnInteractHandEvent(EntityUid uid, VampireComponent component, BeforeInteractHandEvent args) + private bool ToggleFangs(Entity vampire) { - if (!HasComp(args.Target)) - return; - - if (args.Target == uid) - return; - - args.Handled = TryDrink((uid, component), args.Target, TimeSpan.FromSeconds(1)); - } - - private void ToggleFangs(Entity vampire) - { - var actionEntity = GetAbilityEntity(vampire.Comp, VampirePowerKey.ToggleFangs); - var popupText = string.Empty; - if (IsAbilityActive(vampire, VampirePowerKey.ToggleFangs)) + if (HasComp(vampire)) { - SetAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs, false); - _action.SetToggled(actionEntity, false); - popupText = Loc.GetString("vampire-fangs-retracted"); + RemComp(vampire); + var popupText = Loc.GetString("vampire-fangs-retracted"); _admin.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(vampire):user} retracted their fangs"); + _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); + return false; } else { - SetAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs, true); - _action.SetToggled(vampire.Comp.UnlockedPowers[VampirePowerKey.ToggleFangs], true); - popupText = Loc.GetString("vampire-fangs-extended"); + EnsureComp(vampire); + var popupText = Loc.GetString("vampire-fangs-extended"); _admin.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(vampire):user} extended their fangs"); + _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); + return true; } - _popup.PopupEntity(popupText, vampire.Owner, vampire.Owner); } - + /// + /// Check and start drinking blood from a humanoid + /// private bool TryDrink(Entity vampire, EntityUid target, TimeSpan doAfterDelay) { //Do a precheck - if (!IsAbilityActive(vampire.Comp, VampirePowerKey.ToggleFangs)) + if (!HasComp(vampire)) return false; if (!_interaction.InRangeUnobstructed(vampire, target, popup: true)) @@ -549,12 +626,12 @@ private bool TryDrink(Entity vampire, EntityUid target, TimeSp if (_rotting.IsRotten(target)) { - _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), vampire, vampire, Shared.Popups.PopupType.SmallCaution); + _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), vampire, vampire, PopupType.SmallCaution); return false; } var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, doAfterDelay, - new VampireDrinkBloodEvent() { Volume = 5 }, + new VampireDrinkBloodDoAfterEvent() { Volume = vampire.Comp.MouthVolume }, eventTarget: vampire, target: target, used: target) @@ -571,21 +648,18 @@ private bool TryDrink(Entity vampire, EntityUid target, TimeSp _doAfter.TryStartDoAfter(doAfterEventArgs); return true; } - private void DrinkDoAfter(Entity entity, ref VampireDrinkBloodEvent args) + private void DrinkDoAfter(Entity entity, ref VampireDrinkBloodDoAfterEvent args) { if (args.Cancelled) return; - if (!args.Target.HasValue) - return; - - if (!IsAbilityActive(entity.Comp, VampirePowerKey.ToggleFangs)) + if (!HasComp(entity)) return; - if (_food.IsMouthBlocked(args.Target.Value, entity)) + if (_food.IsMouthBlocked(entity, entity)) return; - if (_rotting.IsRotten(args.Target.Value)) + if (_rotting.IsRotten(args.Target!.Value)) { _popup.PopupEntity(Loc.GetString("vampire-blooddrink-rotted"), args.User, args.User, PopupType.SmallCaution); return; @@ -663,30 +737,54 @@ private bool TryIngestBlood(Entity vampire, Solution ingestedS } #endregion - private bool IsAbilityUnlocked(VampireComponent vampire, VampirePowerKey ability) + private bool IsPowerUnlocked(VampireComponent vampire, string name) { - return vampire.UnlockedPowers.ContainsKey(ability); + return vampire.UnlockedPowers.ContainsKey(name); } - private bool IsAbilityActive(VampireComponent vampire, VampirePowerKey ability) + /*private bool IsPowerActive(VampireComponent vampire, VampirePowerProtype def) => IsPowerActive(vampire, def.ID); + private bool IsPowerActive(VampireComponent vampire, string name) { - return vampire.AbilityStates.Contains(ability); + return vampire.ActivePowers.Contains(name); } - private bool SetAbilityActive(VampireComponent vampire, VampirePowerKey ability, bool active) + private bool SetPowerActive(VampireComponent vampire, string name, bool active) { if (active) { - return vampire.AbilityStates.Add(ability); + return vampire.ActivePowers.Add(name); } else { - return vampire.AbilityStates.Remove(ability); + return vampire.ActivePowers.Remove(name); } + }*/ + /// + /// Gets the Action EntityUid for a specific power + /// + private EntityUid? GetPowerEntity(VampireComponent vampire, string name) + { + if (!vampire.UnlockedPowers.TryGetValue(name, out var ability)) + return null; + + return ability; } - private EntityUid? GetAbilityEntity(VampireComponent vampire, VampirePowerKey ability) + + /// + /// Cache all power prototypes in a dictionary by keyed by ID + /// + /// + private FrozenDictionary BuildPowerCache() { - if (IsAbilityUnlocked(vampire, ability)) - return vampire.UnlockedPowers[ability]; - return null; + var protos = _prototypeManager.EnumeratePrototypes(); + return protos.ToFrozenDictionary(x => x.ID); } + /// + /// Cache all passive prototypes in a dictionary by keyed by listing id + /// + /// + private FrozenDictionary BuildPassiveCache() + { + var protos = _prototypeManager.EnumeratePrototypes(); + return protos.ToFrozenDictionary(x => x.CatalogEntry); + } } diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index 45b9c19bd82..cc7779f0dfc 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -1,12 +1,14 @@ using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Temperature.Components; +using Content.Shared.Actions; using Content.Shared.Atmos; using Content.Shared.Atmos.Rotting; using Content.Shared.Body.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Nutrition.Components; +using Content.Shared.Vampire; using Content.Shared.Vampire.Components; using Content.Shared.Weapons.Melee; using Robust.Shared.Audio; @@ -32,13 +34,6 @@ private void MakeVampire(EntityUid vampireUid) if (TryComp(vampire, out var temperatureComponent)) temperatureComponent.ColdDamageThreshold = Atmospherics.TCMB; - // - if (TryComp(vampire, out var melee)) - { - melee.Damage = VampireComponent.MeleeDamage; - melee.Animation = "WeaponArcClaw"; - melee.HitSound = new SoundPathSpecifier("/Audio/Weapons/slash.ogg"); - } MakeVulnerableToHoly(vampire); //Initialise currency @@ -96,11 +91,20 @@ private void MakeVulnerableToHoly(Entity vampire) private void AddStartingAbilities(Entity vampire) { var action = _action.AddAction(vampire, VampireComponent.SummonActionPrototype); - if (action.HasValue) - { - OnStorePurchase(vampire, action.Value); - vampire.Comp.UnlockedPowers[VampirePowerKey.SummonHeirloom] = action; - } + if (!action.HasValue) + return; + + if (!TryComp(action, out var instantActionComponent)) + return; + + var actionEvent = instantActionComponent.Event as VampireSelfPowerEvent; + + if (actionEvent == null) + return; + + vampire.Comp.UnlockedPowers.Add(actionEvent.DefinitionName, action); + + UpdateBloodDisplay(vampire); } //Remove weakeness to holy items diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 4c05989215d..2f725eff018 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -38,6 +38,7 @@ using Robust.Shared.Map; using Robust.Shared.Prototypes; using System.Linq; +using Content.Shared.Store; namespace Content.Server.Vampire; @@ -78,22 +79,18 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent(OnInteractHandEvent); - SubscribeLocalEvent(DrinkDoAfter); - SubscribeLocalEvent(HypnotiseDoAfter); - SubscribeLocalEvent(OnInsertedIntoContainer); - SubscribeLocalEvent(OnRemovedFromContainer); - SubscribeLocalEvent(OnVampireStateChanged); - SubscribeLocalEvent(OnUseSelfPower); - SubscribeLocalEvent(OnUseTargetedPower); + //SubscribeLocalEvent(OnUseSelfPower); + //SubscribeLocalEvent(OnUseTargetedPower); SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnStorePurchasePassive); + //SubscribeLocalEvent(OnStorePurchasePassive); SubscribeLocalEvent(OnUseHeirloom); - SubscribeLocalEvent(OnStorePurchase); + SubscribeLocalEvent(OnStorePurchase); + + InitializePowers(); } - private void OnStorePurchasePassive(EntityUid uid, VampireComponent component, StoreProductEvent args) + /*private void OnStorePurchasePassive(EntityUid uid, VampireComponent component, StoreProductEvent args) { if (args.Ev is not VampirePowerDetails) return; @@ -117,7 +114,7 @@ private void OnStorePurchasePassive(EntityUid uid, VampireComponent component, S break; } } - } + }*/ /// /// Handles healing and damaging in space @@ -139,7 +136,7 @@ public override void Update(float frameTime) } var healingQuery = EntityQueryEnumerator(); - while (healingQuery.MoveNext(out var uid, out var vampire, out var healing)) + while (healingQuery.MoveNext(out var uid, out _, out var healing)) { if (healing.NextHealTick <= 0) { @@ -186,48 +183,59 @@ private void OnUseHeirloom(EntityUid uid, VampireHeirloomComponent component, Us _store.ToggleUi(args.User, uid); } - private void OnStorePurchase(EntityUid uid, VampireHeirloomComponent component, ref StorePurchasedActionEvent ev) - => OnStorePurchase(ev.Purchaser, ev.Action); - private void OnStorePurchase(EntityUid purchaser, EntityUid purchasedAction) + private void OnStorePurchase(EntityUid uid, VampireHeirloomComponent component, ref StorePurchasedListingEvent ev) { - if (!TryComp(purchaser, out var vampireComponent)) + if (!TryComp(ev.Purchaser, out var vampireComponent)) return; + var vampire = new Entity(ev.Purchaser, vampireComponent); + + if (ev.Action != null) + { + OnStorePurchaseActive(vampire, ev.Action.Value); + } + else + { + //Its a passive + OnStorePurchasePassive(vampire, ev.Listing); + } + } + private void OnStorePurchaseActive(Entity purchaser, EntityUid purchasedAction) + { if (TryComp(purchasedAction, out var instantAction) && instantAction.Event != null && instantAction.Event is VampireSelfPowerEvent) { var vampirePower = instantAction.Event as VampireSelfPowerEvent; if (vampirePower == null) return; - vampireComponent.UnlockedPowers[vampirePower.Details.Type] = purchasedAction; + purchaser.Comp.UnlockedPowers[vampirePower.DefinitionName] = purchasedAction; return; } if (TryComp(purchasedAction, out var targetAction) && targetAction.Event != null && targetAction.Event is VampireTargetedPowerEvent) { var vampirePower = targetAction.Event as VampireTargetedPowerEvent; if (vampirePower == null) return; - vampireComponent.UnlockedPowers[vampirePower.Details.Type] = purchasedAction; + purchaser.Comp.UnlockedPowers[vampirePower.DefinitionName] = purchasedAction; return; } - UpdateBloodDisplay((purchaser, vampireComponent)); + UpdateBloodDisplay(purchaser); } - - /// - /// Called by the store when a new passive ability is purchased - /// - /// - /// - /// - /// - /*private void OnUnlockPassive(EntityUid uid, VampireComponent component, VampireUnlockPassiveEvent args) + private void OnStorePurchasePassive(Entity purchaser, ListingData purchasedPassive) { - throw new NotImplementedException(); - }*/ + if (!_passiveCache.TryGetValue(purchasedPassive.ID, out var passiveDef)) + return; + //I am so going to hell for this + foreach (var compToRemove in passiveDef.CompsToRemove.Values) + RemComp(purchaser, compToRemove.Component.GetType()); + + foreach (var compToAdd in passiveDef.CompsToAdd.Values) + AddComp(purchaser, compToAdd.Component, true); + } private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) { - if (IsAbilityActive(component, VampirePowerKey.ToggleFangs) && args.IsInDetailsRange && !_food.IsMouthBlocked(uid)) + if (HasComp(uid) && args.IsInDetailsRange && !_food.IsMouthBlocked(uid)) args.AddMarkup($"{Loc.GetString("vampire-fangs-extended-examine")}{Environment.NewLine}"); } private bool AddBloodEssence(Entity vampire, FixedPoint2 quantity) @@ -267,7 +275,7 @@ private void UpdateBloodDisplay(Entity vampire) return; var chargeDisplay = (int) Math.Round((decimal) balance); - var summonAction = GetAbilityEntity(vampire, VampirePowerKey.SummonHeirloom); + var summonAction = GetPowerEntity(vampire, VampireComponent.SummonActionPrototype); if (summonAction == null) return; diff --git a/Content.Shared/Store/Events/StoreProductEvent.cs b/Content.Shared/Store/Events/StoreProductEvent.cs deleted file mode 100644 index c764cb6d8d4..00000000000 --- a/Content.Shared/Store/Events/StoreProductEvent.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Content.Shared.Store.Events; - -public record struct StoreProductEvent(EntityUid Purchaser, ListingData Listing, object? Ev); diff --git a/Content.Shared/Store/Events/StorePurchasedActionEvent.cs b/Content.Shared/Store/Events/StorePurchasedActionEvent.cs deleted file mode 100644 index 29aa5b100ea..00000000000 --- a/Content.Shared/Store/Events/StorePurchasedActionEvent.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Content.Shared.Store.Events; - -public record struct StorePurchasedActionEvent(EntityUid Purchaser, EntityUid Action); diff --git a/Content.Shared/Store/Events/StorePurchasedListingEvent.cs b/Content.Shared/Store/Events/StorePurchasedListingEvent.cs new file mode 100644 index 00000000000..89ff18e1eef --- /dev/null +++ b/Content.Shared/Store/Events/StorePurchasedListingEvent.cs @@ -0,0 +1,3 @@ +namespace Content.Shared.Store.Events; + +public record struct StorePurchasedListingEvent(EntityUid Purchaser, ListingData Listing, EntityUid? Item, EntityUid? Action); diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index 46bbeefb615..a293825d84d 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -49,6 +49,8 @@ public sealed partial class VampireComponent : Component [ValidatePrototypeId] public static readonly string SummonActionPrototype = "ActionVampireSummonHeirloom"; + [ValidatePrototypeId] + public static readonly string DrinkBloodPrototype = "DrinkBlood"; /// /// Total blood drank, counter for end of round screen @@ -62,19 +64,10 @@ public sealed partial class VampireComponent : Component [ViewVariables(VVAccess.ReadWrite)] public float MouthVolume = 5; - /// - /// Uid of the last coffin the vampire slept in - /// TODO: UI prompt client side to set this - /// - [ViewVariables(VVAccess.ReadWrite)] - public EntityUid? HomeCoffin = default!; - - [ViewVariables(VVAccess.ReadWrite)] - public HashSet AbilityStates = new(); /// /// All unlocked abilities /// - public Dictionary UnlockedPowers = new(); + public Dictionary UnlockedPowers = new(); /// /// Link to the vampires heirloom @@ -94,11 +87,14 @@ public sealed partial class VampireComponent : Component /// /// Contains all details about the ability and its effects or restrictions /// -[DataDefinition -public sealed partial class VampirePowerProtype +[DataDefinition] +[Prototype("vampirePower")] +public sealed partial class VampirePowerProtype : IPrototype { - [DataField(required: true)] - public VampirePowerKey Type; + [ViewVariables] + [IdDataField] + public string ID { get; private set; } + [DataField] public float ActivationCost = 0; [DataField] @@ -119,50 +115,67 @@ public sealed partial class VampirePowerProtype public float Upkeep = 0; } +[DataDefinition] +[Prototype("vampirePassive")] +public sealed partial class VampirePassiveProtype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } + + [DataField(required: true)] + public string CatalogEntry = string.Empty; + + [DataField] + public ComponentRegistry CompsToAdd = new(); + + [DataField] + public ComponentRegistry CompsToRemove = new(); +} + /// /// Marks an entity as taking damage when hit by a bible, rather than being healed /// [RegisterComponent] -public sealed partial class UnholyComponent : Component -{ -} +public sealed partial class UnholyComponent : Component { } /// /// Marks a container as a coffin, for the purposes of vampire healing /// [RegisterComponent] -public sealed partial class CoffinComponent : Component -{ -} +public sealed partial class CoffinComponent : Component { } + [RegisterComponent] public sealed partial class VampireHeirloomComponent : Component { //Use of the heirloom is limited to this entity public EntityUid? VampireOwner = default!; } + [RegisterComponent] +public sealed partial class VampireFangsExtendedComponent : Component { } + +/// +/// When added, heals the entity by the specified amount +/// public sealed partial class VampireHealingComponent : Component { - public double NextHealTick = 0; - - public DamageSpecifier Healing = new DamageSpecifier() - { - DamageDict = new Dictionary() - { - { "Blunt", 2 }, - { "Slash", 2 }, - { "Pierce", 2 }, - { "Heat", 1 }, - { "Cold", 2 }, - { "Shock", 2 }, - { "Caustic", 2 }, - { "Airloss", 2 }, - { "Bloodloss", 2 }, - { "Genetic", 2 } - } - }; + public DamageSpecifier? Healing = default!; } +[RegisterComponent] +public sealed partial class VampireDeathsEmbraceComponent : Component +{ + [ViewVariables()] + public EntityUid? HomeCoffin = default!; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float Cost = 0; + + [DataField] + public DamageSpecifier CoffinHealing = default!; +} [RegisterComponent] public sealed partial class VampireSealthComponent : Component { @@ -173,7 +186,7 @@ public sealed partial class VampireSealthComponent : Component public float Upkeep = 0; } -[Serializable, NetSerializable] +/*[Serializable, NetSerializable] public enum VampirePowerKey : byte { ToggleFangs, @@ -191,4 +204,4 @@ public enum VampirePowerKey : byte //Passives UnnaturalStrength, SupernaturalStrength -} +}*/ diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index ed4711a3707..8870e6b9e91 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -1,40 +1,55 @@ using Content.Shared.Actions; -using Content.Shared.Damage; using Content.Shared.DoAfter; -using Content.Shared.Store.Events; using Content.Shared.Vampire.Components; using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Vampire; //Use power events -public sealed partial class VampireSelfPowerEvent : InstantActionEvent +public sealed partial class VampireToggleFangsEvent : VampireSelfPowerEvent { } +public sealed partial class VampireSummonHeirloomEvent : VampireSelfPowerEvent { } +public sealed partial class VampireScreechEvent : VampireSelfPowerEvent { } +public sealed partial class VampirePolymorphEvent : VampireSelfPowerEvent { } +public sealed partial class VampireBloodStealEvent : VampireSelfPowerEvent { } +public sealed partial class VampireCloakOfDarknessEvent : VampireSelfPowerEvent { } + +public sealed partial class VampireGlareEvent : VampireTargetedPowerEvent { } +public sealed partial class VampireHypnotiseEvent : VampireTargetedPowerEvent { } + + +public abstract partial class VampireSelfPowerEvent : InstantActionEvent { - [DataField] - public VampirePowerDetails Details = new(); + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DefinitionName = default!; }; -public sealed partial class VampireTargetedPowerEvent : EntityTargetActionEvent +public abstract partial class VampireTargetedPowerEvent : EntityTargetActionEvent { - [DataField] - public VampirePowerDetails Details = new(); + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DefinitionName = default!; }; public sealed partial class VampirePassiveActionEvent : BaseActionEvent { - [DataField] - public VampirePowerDetails Details = new(); + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DefinitionName = default!; }; +//Purchase passive events +[Serializable, NetSerializable] +public sealed partial class VampirePurchaseUnnaturalStrength : EntityEventArgs { } + //Doafter events [Serializable, NetSerializable] -public sealed partial class VampireDrinkBloodEvent : DoAfterEvent +public sealed partial class VampireDrinkBloodDoAfterEvent : DoAfterEvent { [DataField] public float Volume = 0; + public override DoAfterEvent Clone() => this; } [Serializable, NetSerializable] -public sealed partial class VampireHypnotiseEvent : DoAfterEvent +public sealed partial class VampireHypnotiseDoAfterEvent : DoAfterEvent { [DataField] public TimeSpan? Duration = TimeSpan.Zero; diff --git a/Resources/Locale/en-US/Vampires/vampires.ftl b/Resources/Locale/en-US/Vampires/vampires.ftl index 4d068638be1..4274692292e 100644 --- a/Resources/Locale/en-US/Vampires/vampires.ftl +++ b/Resources/Locale/en-US/Vampires/vampires.ftl @@ -27,34 +27,46 @@ vampire-bloodsteal-other = You feel blood being ripped from your body! vampire-hypnotise-other = {CAPITALIZE(THE($user))} stares deeply into {MAKEPLURAL(THE($target))} eyes! store-currency-display-blood-essence = Blood Essence +store-category-vampirepowers = Powers +store-category-vampirepassives = Passives -#Abilities -vampire-ability-summonheirloom = Summon Heirloom -vampire-ability-summonheirloom-description = Summon a family heirloom, gifted by lilith herself. +#Powers +vampire-power-summonheirloom = Summon Heirloom +vampire-power-summonheirloom-description = Summon a family heirloom, gifted by lilith herself. -vampire-ability-blessing = Blessing of Lilith -vampire-ability-blessing-description = Swear your soul to Lilith, receive her blessing, and feast upon the bounty around you. +vampire-power-blessing = Blessing of Lilith +vampire-power-blessing-description = Swear your soul to Lilith, receive her blessing, and feast upon the bounty around you. -vampire-ability-togglefangs = Toggle Fangs -vampire-ability-togglefangs-description = Extend or retract your fangs. Walking around with your fangs out might reveal your true nature. +vampire-power-togglefangs = Toggle Fangs +vampire-power-togglefangs-description = Extend or retract your fangs. Walking around with your fangs out might reveal your true nature. -vampire-ability-glare = Glare -vampire-ability-glare-description = Release a blinding flash from your eyes, stunning a unprotected mortal for 10 seconds. Activation Cost: 20 Essence. Cooldown: 60 Seconds +vampire-power-glare = Glare +vampire-power-glare-description = Release a blinding flash from your eyes, stunning a unprotected mortal for 10 seconds. Activation Cost: 20 Essence. Cooldown: 60 Seconds -vampire-ability-hypnotise = Hypnotise -vampire-ability-hypnotise-description = Stare deeply into a mortals eyes, forcing them to sleep for 60 seconds. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes +vampire-power-hypnotise = Hypnotise +vampire-power-hypnotise-description = Stare deeply into a mortals eyes, forcing them to sleep for 60 seconds. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes -vampire-ability-screech = Screech -vampire-ability-screech-description = Release a piercing scream, stunning unprotected mortals and shattering fragile objects nearby. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes +vampire-power-screech = Screech +vampire-power-screech-description = Release a piercing scream, stunning unprotected mortals and shattering fragile objects nearby. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes -vampire-ability-bloodsteal = Blood Steal -vampire-ability-bloodsteal-description = Wrench the blood from all bodies nearby - living or dead. Activation Cost: 20 Essence. Cooldown: 60 Seconds +vampire-power-bloodsteal = Blood Steal +vampire-power-bloodsteal-description = Wrench the blood from all bodies nearby - living or dead. Activation Cost: 20 Essence. Cooldown: 60 Seconds -vampire-ability-batform = Bat Form -vampire-ability-batform-description = Assume for form of a bat. Fast, Hard to Hit, Likes fruit. Activation Cost: 20 Essence. Cooldown: 30 Seconds +vampire-power-batform = Bat Form +vampire-power-batform-description = Assume for form of a bat. Fast, Hard to Hit, Likes fruit. Activation Cost: 20 Essence. Cooldown: 30 Seconds -vampire-ability-mouseform = Mouse Form -vampire-ability-mouseform-description = Assume for form of a mouse. Fast, Small, Immune to doors. Activation Cost: 20 Essence. Cooldown: 30 Seconds +vampire-power-mouseform = Mouse Form +vampire-power-mouseform-description = Assume for form of a mouse. Fast, Small, Immune to doors. Activation Cost: 20 Essence. Cooldown: 30 Seconds -vampire-ability-cloakofdarkness = Cloak of Darkness -vampire-ability-cloakofdarkness-description = Cloak yourself from mortal eyes, rendering you invisible while stationary. Activation Cost: 30 Essence. Upkeep: 1 Essence/Second Cooldown: 10 Seconds \ No newline at end of file +vampire-power-cloakofdarkness = Cloak of Darkness +vampire-power-cloakofdarkness-description = Cloak yourself from mortal eyes, rendering you invisible while stationary. Activation Cost: 30 Essence. Upkeep: 1 Essence/Second Cooldown: 10 Seconds + +#Passives +vampire-passive-unholystrength = Unholy Strength +vampire-passive-unholystrength-description = Infuse your upper body muscles with essence, granting you claws and increased strength. Effect: 10 Slash per hit + +vampire-passive-supernaturalstrength = Supernatural Strength +vampire-passive-supernaturalstrength-description = Increase your upper body muscles strength further, no barrier shall stand in your way. Effect: 15 Slash per hit, able to pry open doors by hand. + +vampire-passive-deathsembrace = Deaths Embrace +vampire-passive-deathsembrace-description = Embrace death and it shall pass you over. Effect: Heal when in a coffin, automatically return to your coffin upon death for 100 blood essence. \ No newline at end of file diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index 154a4ab1e6b..94d65e6c59a 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -1,7 +1,7 @@ - type: entity id: ActionVampireSummonHeirloom - name: vampire-ability-summonheirloom - description: vampire-ability-summonheirloom-description + name: vampire-power-summonheirloom + description: vampire-power-summonheirloom-description noSpawn: true components: - type: InstantAction @@ -9,17 +9,14 @@ sprite: Interface/Actions/actions_vampire.rsi state: summonheirloom event: - !type:VampireSelfPowerEvent - details: - type: SummonHeirloom - usableWhileCuffed: false - usableWhileStunned: false + !type:VampireSummonHeirloomEvent + definitionName: SummonHeirloom useDelay: 60 - type: entity id: ActionVampireToggleFangs - name: vampire-ability-togglefangs - description: vampire-ability-togglefangs-description + name: vampire-power-togglefangs + description: vampire-power-togglefangs-description noSpawn: true components: - type: InstantAction @@ -32,14 +29,13 @@ sprite: Interface/Actions/actions_vampire.rsi state: fangs_extended event: - !type:VampireSelfPowerEvent - details: - type: ToggleFangs + !type:VampireToggleFangsEvent + definitionName: ToggleFangs - type: entity id: ActionVampireGlare - name: vampire-ability-glare - description: vampire-ability-glare-description + name: vampire-power-glare + description: vampire-power-glare-description noSpawn: true components: - type: EntityTargetAction @@ -53,16 +49,14 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/Vampire/glare.ogg event: - !type:VampireTargetedPowerEvent - details: - type: Glare - duration: 10 + !type:VampireGlareEvent + definitionName: Glare useDelay: 60 - type: entity id: ActionVampireHypnotise - name: vampire-ability-hypnotise - description: vampire-ability-hypnotise-description + name: vampire-power-hypnotise + description: vampire-power-hypnotise-description noSpawn: true components: - type: EntityTargetAction @@ -76,17 +70,14 @@ sprite: Interface/Actions/actions_vampire.rsi state: hypnotise event: - !type:VampireTargetedPowerEvent - details: - type: Hypnotise - duration: 60 - doAfterDelay: 5 + !type:VampireHypnotiseEvent + definitionName: Hypnotise useDelay: 300 - type: entity id: ActionVampireScreech - name: vampire-ability-screech - description: vampire-ability-screech-description + name: vampire-power-screech + description: vampire-power-screech-description noSpawn: true components: - type: InstantAction @@ -98,22 +89,14 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/Vampire/screech_tone.ogg event: - !type:VampireSelfPowerEvent - details: - type: Screech - duration: 3 - damage: - types: - Blunt: 10 - Structural: 40 - usableWhileMuffled: false - activationCost: 10 + !type:VampireScreechEvent + definitionName: Screech useDelay: 60 - type: entity id: ActionVampireBloodSteal - name: vampire-ability-bloodsteal - description: vampire-ability-bloodsteal-description + name: vampire-power-bloodsteal + description: vampire-power-bloodsteal-description noSpawn: true components: - type: InstantAction @@ -125,18 +108,14 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/demon_consume.ogg event: - !type:VampireSelfPowerEvent - details: - type: BloodSteal - usableWhileStunned: false - usableWhileCuffed: false - activationCost: 20 + !type:VampireBloodStealEvent + definitionName: BloodSteal useDelay: 60 - type: entity id: ActionVampireBatform - name: vampire-ability-batform - description: vampire-ability-batform-description + name: vampire-power-batform + description: vampire-power-batform-description noSpawn: true components: - type: InstantAction @@ -148,19 +127,14 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/teleport_arrival.ogg event: - !type:VampireSelfPowerEvent - details: - type: Polymorph - usableWhileStunned: false - usableWhileCuffed: false - polymorphTarget: mobBatVampire - activationCost: 20 + !type:VampirePolymorphEvent + definitionName: PolymorphBat useDelay: 30 - type: entity id: ActionVampireMouseform - name: vampire-ability-mouseform - description: vampire-ability-mouseform-description + name: vampire-power-mouseform + description: vampire-power-mouseform-description noSpawn: true components: - type: InstantAction @@ -172,19 +146,14 @@ sound: !type:SoundPathSpecifier path: /Audio/Effects/teleport_arrival.ogg event: - !type:VampireSelfPowerEvent - details: - type: Polymorph - usableWhileStunned: false - usableWhileCuffed: false - polymorphTarget: MobMouse - activationCost: 20 + !type:VampirePolymorphEvent + definitionName: PolymorphMouse useDelay: 30 - type: entity id: ActionVampireCloakOfDarkness - name: vampire-ability-cloakofdarkness - description: vampire-ability-cloakofdarkness-description + name: vampire-power-cloakofdarkness + description: vampire-power-cloakofdarkness-description noSpawn: true components: - type: InstantAction @@ -194,10 +163,6 @@ sprite: Interface/Actions/actions_vampire.rsi state: cloakofdarkness event: - !type:VampireSelfPowerEvent - details: - type: CloakOfDarkness - usableWhileStunned: false - activationCost: 30 - upkeep: 1 + !type:VampireCloakOfDarknessEvent + definitionName: CloakOfDarkness useDelay: 10 \ No newline at end of file diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml index e2db2befdf2..dcdabd52b72 100644 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -6,7 +6,7 @@ BloodEssence: 0 productAction: ActionVampireToggleFangs categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -19,7 +19,7 @@ BloodEssence: 20 productAction: ActionVampireGlare categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -35,7 +35,7 @@ BloodEssence: 60 productAction: ActionVampireHypnotise categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -51,7 +51,7 @@ BloodEssence: 120 productAction: ActionVampireScreech categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -67,7 +67,7 @@ BloodEssence: 120 productAction: ActionVampireBloodSteal categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -83,7 +83,7 @@ BloodEssence: 200 productAction: ActionVampireBatform categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -101,7 +101,7 @@ BloodEssence: 200 productAction: ActionVampireMouseform categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -119,7 +119,7 @@ BloodEssence: 400 productAction: ActionVampireCloakOfDarkness categories: - - VampireAbilities + - VampirePowers conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -129,19 +129,16 @@ #Passives - type: listing - id: VampireUnnaturalStrength - name: vampire-ability-unnaturalstrength - description: vampire-ability-unnaturalstrength-description + id: VampireUnholyStrength + name: vampire-passive-unholystrength + description: vampire-passive-unholystrength-description + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: uhholystrength cost: BloodEssence: 20 - productEvent: - !type:VampirePowerDetails - type: UnnaturalStrength - damage: - types: - Slash: 5 categories: - - VampireAbilities + - VampirePassives conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -151,18 +148,15 @@ - type: listing id: VampireSupernaturalStrength - name: vampire-ability-supernaturalstrength - description: vampire-ability-supernaturalstrength-description + name: vampire-passive-supernaturalstrength + description: vampire-passive-supernaturalstrength-description + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: supernaturalstrength cost: BloodEssence: 400 - productEvent: - !type:VampirePowerDetails - type: SupernaturalStrength - damage: - types: - Slash: 5 categories: - - VampireAbilities + - VampirePassives conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -170,6 +164,21 @@ whitelist: - VampireUnnaturalStrength +- type: listing + id: VampireDeathsEmbrace + name: vampire-passive-deathsembrace + description: vampire-passive-deathsembrace-description + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: deathsembrace + cost: + BloodEssence: 160 + categories: + - VampirePassives + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + #- type: listing # id: VampireSupernaturalStrength # name: Supernatural Strength @@ -178,7 +187,7 @@ # cost: # BloodEssence: 400 # categories: -# - VampireAbilities +# - VampirePowers # conditions: # - !type:ListingLimitedStockCondition # stock: 1 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml index 2f733284ac5..c199709e239 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml @@ -14,7 +14,8 @@ type: StoreBoundUserInterface - type: Store categories: - - VampireAbilities + - VampirePowers + - VampirePassives currencyWhitelist: - BloodEssence - type: VampireHeirloom \ No newline at end of file diff --git a/Resources/Prototypes/Store/categories.yml b/Resources/Prototypes/Store/categories.yml index 3243c4ec3e9..64413de9716 100644 --- a/Resources/Prototypes/Store/categories.yml +++ b/Resources/Prototypes/Store/categories.yml @@ -70,5 +70,9 @@ #vampire - type: storeCategory - id: VampireAbilities - name: store-category-abilities + id: VampirePowers + name: store-category-vampirepowers + +- type: storeCategory + id: VampirePassives + name: store-category-vampirepassives diff --git a/Resources/Prototypes/Vampire/vampire-powers.yml b/Resources/Prototypes/Vampire/vampire-powers.yml new file mode 100644 index 00000000000..0512ce713c0 --- /dev/null +++ b/Resources/Prototypes/Vampire/vampire-powers.yml @@ -0,0 +1,99 @@ +- type: vampirePower + id: SummonHeirloom + usableusableWhileCuffed: false + usableWhileStunned: false + +- type: vampirePower + id: ToggleFangs + +- type: vampirePower + id: DrinkBlood + doAfterDelay: 1 + +- type: vampirePower + id: Glare + duration: 10 + +- type: vampirePower + id: Hypnotise + duration: 60 + doAfterDelay: 5 + +- type: vampirePower + id: Screech + duration: 3 + damage: + types: + Blunt: 10 + Structural: 40 + usableWhileMuffled: false + activationCost: 10 + +- type: vampirePower + id: BloodSteal + usableWhileStunned: false + usableWhileCuffed: false + activationCost: 20 + +- type: vampirePower + id: PolymorphBat + usableWhileStunned: false + usableWhileCuffed: false + polymorphTarget: mobBatVampire + activationCost: 20 + +- type: vampirePower + id: PolymorphMouse + usableWhileStunned: false + usableWhileCuffed: false + polymorphTarget: MobMouse + activationCost: 20 + +- type: vampirePower + id: CloakOfDarkness + usableWhileStunned: false + activationCost: 30 + upkeep: 1 + +- type: vampirePassive + id: UnholyStrength + catalogEntry: VampireUnholyStrength # Must match the catalog id + compsToAdd: + - type: MeleeWeapon + damage: + types: + Slash: 10 + animation: WeaponArcClaw + hitSound: /Audio/Weapons/slash.ogg + +- type: vampirePassive + id: SupernaturalStrength + catalogEntry: VampireUnnaturalStrength # Must match the catalog id + compsToAdd: + - type: MeleeWeapon + damage: + types: + Slash: 15 + animation: WeaponArcClaw + hitSound: /Audio/Weapons/slash.ogg + - type: Prying + force: True + pryPowered: True + +- type: vampirePassive + id: DeathsEmbrace + catalogEntry: VampireDeathsEmbrace # Must match the catalog id + compsToAdd: + - type: VampireDeathsEmbrace + cost: 100 + coffinHealing: + groups: + Toxin: 2 + Genetic: 2 + Airloss: 2 + Brute: 2 + types: + Burn: 1 + Cold: 2 + Shock: 2 + Caustic: 2 \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json index f83abb42edc..9aee7ddc690 100644 --- a/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json +++ b/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json @@ -33,6 +33,12 @@ }, { "name": "cloakofdarkness" + }, + { + "name": "unholystrength" + }, + { + "name": "supernaturalstrength" } ] } \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/supernaturalstrength.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/supernaturalstrength.png new file mode 100644 index 0000000000000000000000000000000000000000..39963eb9ef919024a8a44bfe724053e02fd6d120 GIT binary patch literal 1756 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 Date: Tue, 29 Oct 2024 09:55:25 +0200 Subject: [PATCH 10/67] refactor fixes --- .../Vampire/VampireSystem.Abilities.cs | 21 +++++++++---------- Content.Server/Vampire/VampireSystem.cs | 2 +- Content.Shared/Vampire/VampireComponent.cs | 4 +++- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 7ab327f032d..9335885dae8 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -24,6 +24,7 @@ using Content.Shared.Prying.Components; using Content.Shared.Stealth.Components; using Content.Shared.Store.Events; +using Content.Shared.Store.Components; using Content.Shared.Stunnable; using Content.Shared.Vampire; using Content.Shared.Vampire.Components; @@ -335,9 +336,6 @@ private void Glare(Entity vampire, EntityUid? target, TimeSpan if (HasComp(target)) return; - if (!HasComp(target)) - return; - if (HasComp(target)) return; @@ -452,9 +450,8 @@ private bool TryHypnotise(Entity vampire, EntityUid? target, T target: target, used: target) { - BreakOnUserMove = true, + BreakOnMove = true, BreakOnDamage = true, - BreakOnTargetMove = true, MovementThreshold = 0.01f, DistanceThreshold = 1.0f, NeedHand = false, @@ -617,8 +614,11 @@ private bool TryDrink(Entity vampire, EntityUid target, TimeSp //Do a precheck if (!HasComp(vampire)) return false; + + if (!HasComp(vampire)) + return false; - if (!_interaction.InRangeUnobstructed(vampire, target, popup: true)) + if (!_interaction.InRangeUnobstructed(vampire.Owner, target, popup: true)) return false; if (_food.IsMouthBlocked(target, vampire)) @@ -636,9 +636,8 @@ private bool TryDrink(Entity vampire, EntityUid target, TimeSp target: target, used: target) { - BreakOnUserMove = true, + BreakOnMove = true, BreakOnDamage = true, - BreakOnTargetMove = true, MovementThreshold = 0.01f, DistanceThreshold = 1.0f, NeedHand = false, @@ -718,10 +717,10 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood private bool TryIngestBlood(Entity vampire, Solution ingestedSolution, bool force = false) { //Get all stomaches - if (TryComp(vampire.Owner, out var body) && _body.TryGetBodyOrganComponents(vampire.Owner, out var stomachs, body)) + if (TryComp(vampire.Owner, out var body) && _body.TryGetBodyOrganEntityComps((vampire.Owner, body), out var stomachs)) { //Pick the first one that has space available - var firstStomach = stomachs.FirstOrNull(stomach => _stomach.CanTransferSolution(stomach.Comp.Owner, ingestedSolution, stomach.Comp)); + var firstStomach = stomachs.FirstOrNull(stomach => _stomach.CanTransferSolution(stomach.Owner, ingestedSolution, stomach.Comp1)); if (firstStomach == null) { //We are full @@ -729,7 +728,7 @@ private bool TryIngestBlood(Entity vampire, Solution ingestedS return false; } //Fill the stomach with that delicious blood - return _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, ingestedSolution, firstStomach.Value.Comp); + return _stomach.TryTransferSolution(firstStomach.Value.Owner, ingestedSolution, firstStomach.Value.Comp1); } //No stomach diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 2f725eff018..9b07dba4e12 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Atmos.Rotting; using Content.Server.Beam; -using Content.Server.Bed.Sleep; using Content.Server.Body.Systems; using Content.Server.Chat.Systems; using Content.Server.Interaction; @@ -14,6 +13,7 @@ using Content.Shared.Actions; using Content.Shared.Body.Systems; using Content.Shared.Buckle; +using Content.Shared.Bed.Sleep; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Construction.Components; using Content.Shared.Damage; diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index a293825d84d..f2d0be129e2 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -77,7 +77,7 @@ public sealed partial class VampireComponent : Component /// /// Current available balance, used to sync currency across heirlooms and add essence as we feed /// - public Dictionary Balance = default!; + public Dictionary, FixedPoint2> Balance = default!; public readonly SoundSpecifier BloodDrainSound = new SoundPathSpecifier("/Audio/Items/drink.ogg", new AudioParams() { Volume = -3f, MaxDistance = 3f }); public readonly SoundSpecifier AbilityPurchaseSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); @@ -160,6 +160,8 @@ public sealed partial class VampireFangsExtendedComponent : Component { } /// public sealed partial class VampireHealingComponent : Component { + public double NextHealTick = 0; + public DamageSpecifier? Healing = default!; } From c629c503eb5f645962e74d26bacb3bf7af059639 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 10:34:10 +0200 Subject: [PATCH 11/67] so many fixes --- .../Store/Systems/StoreSystem.Ui.cs | 28 ++++++++++++++--- .../Vampire/VampireSystem.Transform.cs | 1 + .../Prototypes/Catalog/vampire_catalog.yml | 31 ++++++++++--------- .../Entities/Objects/Misc/heirloom.yml | 6 ++-- Resources/Prototypes/Reagents/medicine.yml | 19 ++++++------ 5 files changed, 54 insertions(+), 31 deletions(-) diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 579839330ef..ba1472a287a 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -139,6 +139,8 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi } var buyer = msg.Actor; + + //var listingev = new StorePurchasedListingEvent() { Purchaser = buyer, Listing = listing }; //verify that we can actually buy this listing and it wasn't added if (!ListingHasCategory(listing, component.Categories)) @@ -163,8 +165,24 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi return; } } + if (!IsOnStartingMap(uid, component)) + component.RefundAllowed = false; + + //subtract the cash + foreach (var (currency, amount) in cost) + { + component.Balance[currency] -= amount; - var listingev = new StorePurchasedListingEvent() { Purchaser = buyer, Listing = listing }; + component.BalanceSpent.TryAdd(currency, FixedPoint2.Zero); + + component.BalanceSpent[currency] += amount; + + // Sunrise-Start + var ev = new SubtractCashEvent(buyer, currency, amount); + RaiseLocalEvent(buyer, ref ev); + // Sunrise-End + + } //spawn entity if (listing.ProductEntity != null) @@ -177,7 +195,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi // Sunrise-End _hands.PickupOrDrop(buyer, product); - listingev.Item = product; + //listingev.Item = product; HandleRefundComp(uid, component, product); @@ -244,7 +262,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi if (upgradeActionId != null) HandleRefundComp(uid, component, upgradeActionId.Value); - listingev.Action = actionUid; + //listingev.Action = actionUid; } if (listing.ProductEvent != null) @@ -255,8 +273,8 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi RaiseLocalEvent(buyer, listing.ProductEvent); } - RaiseLocalEvent(uid, ev); - RaiseLocalEvent(buyer, ev); + //RaiseLocalEvent(uid, listingev); + //RaiseLocalEvent(buyer, listingev); //log dat shit. _admin.Add(LogType.StorePurchase, diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index cc7779f0dfc..5b67b9bf6de 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -7,6 +7,7 @@ using Content.Shared.Body.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry; using Content.Shared.Nutrition.Components; using Content.Shared.Vampire; using Content.Shared.Vampire.Components; diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml index dcdabd52b72..97cb9c093cf 100644 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -164,20 +164,23 @@ whitelist: - VampireUnnaturalStrength -- type: listing - id: VampireDeathsEmbrace - name: vampire-passive-deathsembrace - description: vampire-passive-deathsembrace-description - icon: - sprite: Interface/Actions/actions_vampire.rsi - state: deathsembrace - cost: - BloodEssence: 160 - categories: - - VampirePassives - conditions: - - !type:ListingLimitedStockCondition - stock: 1 +#- type: listing +# id: VampireDeathsEmbrace +# name: vampire-passive-deathsembrace +# description: vampire-passive-deathsembrace-description +# icon: +# sprite: Interface/Actions/actions_vampire.rsi +# state: deathsembrace +# cost: +# BloodEssence: 160 +# categories: +# - VampirePassives +# conditions: +# - !type:ListingLimitedStockCondition +# stock: 1 +# - !type:BuyBeforeCondition +# whitelist: +# - VampireToggleFangs #- type: listing # id: VampireSupernaturalStrength diff --git a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml index c199709e239..f2e58637b94 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml @@ -10,8 +10,10 @@ state: icon - type: UserInterface interfaces: - - key: enum.StoreUiKey.Key - type: StoreBoundUserInterface + enum.StoreUiKey.Key: + type: StoreBoundUserInterface + - type: ActivatableUI + key: enum.StoreUiKey.Key - type: Store categories: - VampirePowers diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 1bccb018380..deb6be721a7 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -1052,16 +1052,6 @@ physicalDesc: reagent-physical-desc-holy flavor: holy color: "#91C3F7" - reactiveEffects: - Unholy: - methods: [ Touch ] - effects: - - !type:HealthChange - damage: - types: - Heat: 5 - - !type:Emote - emote: Scream metabolisms: Drink: effects: @@ -1099,6 +1089,15 @@ Shock: -0.2 Cold: -0.2 reactiveEffects: + Unholy: + methods: [ Touch ] + effects: + - !type:HealthChange + damage: + types: + Heat: 5 + - !type:Emote + emote: Scream Extinguish: methods: [ Touch ] effects: From 9a37308f3aec283e998b609359947e55c668d7cf Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 15:00:27 +0200 Subject: [PATCH 12/67] some fixes --- Resources/Prototypes/Catalog/vampire_catalog.yml | 2 +- Resources/Prototypes/Reagents/medicine.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml index 97cb9c093cf..ac03d0c94a8 100644 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -134,7 +134,7 @@ description: vampire-passive-unholystrength-description icon: sprite: Interface/Actions/actions_vampire.rsi - state: uhholystrength + state: unholystrength cost: BloodEssence: 20 categories: diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index deb6be721a7..03e5af7fe8f 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -1061,14 +1061,14 @@ effects: #If vampire - !type:HealthChange - condition: + conditions: - !type:OrganType type: Vampire damage: types: Heat: 2 - !type:Emote - condition: + conditions: - !type:OrganType type: Vampire emote: Scream From 2f5a98d16e52f5bad7ea5e5b1c70c19bf0addf40 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 15:03:29 +0200 Subject: [PATCH 13/67] some fixes --- Resources/Prototypes/Catalog/vampire_catalog.yml | 2 +- Resources/Prototypes/Vampire/vampire-powers.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml index ac03d0c94a8..c81ae0dabb9 100644 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -162,7 +162,7 @@ stock: 1 - !type:BuyBeforeCondition whitelist: - - VampireUnnaturalStrength + - VampireUnholyStrength #- type: listing # id: VampireDeathsEmbrace diff --git a/Resources/Prototypes/Vampire/vampire-powers.yml b/Resources/Prototypes/Vampire/vampire-powers.yml index 0512ce713c0..17ff5bdb7aa 100644 --- a/Resources/Prototypes/Vampire/vampire-powers.yml +++ b/Resources/Prototypes/Vampire/vampire-powers.yml @@ -1,6 +1,6 @@ - type: vampirePower id: SummonHeirloom - usableusableWhileCuffed: false + usableWhileCuffed: false usableWhileStunned: false - type: vampirePower @@ -64,7 +64,7 @@ types: Slash: 10 animation: WeaponArcClaw - hitSound: /Audio/Weapons/slash.ogg + soundHit: /Audio/Weapons/slash.ogg - type: vampirePassive id: SupernaturalStrength @@ -75,7 +75,7 @@ types: Slash: 15 animation: WeaponArcClaw - hitSound: /Audio/Weapons/slash.ogg + soundHit: /Audio/Weapons/slash.ogg - type: Prying force: True pryPowered: True From e936beb2ef72b0514aac07f1f31e6ea8f0588b16 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 17:39:37 +0200 Subject: [PATCH 14/67] new textures, status icon --- Resources/Prototypes/StatusIcon/faction.yml | 11 +++++++++++ .../actions_vampire.rsi/deathsembrace.png | Bin 0 -> 651 bytes .../actions_vampire.rsi/fullpotential.png | Bin 0 -> 658 bytes .../Actions/actions_vampire.rsi/meta.json | 15 +++++++++++++++ .../Actions/actions_vampire.rsi/mouseform.png | Bin 0 -> 873 bytes .../Actions/actions_vampire.rsi/mutation.png | Bin 0 -> 689 bytes .../actions_vampire.rsi/stellarweakness.png | Bin 0 -> 1241 bytes .../Interface/Misc/job_icons.rsi/Vampire.png | Bin 0 -> 140 bytes .../Interface/Misc/job_icons.rsi/meta.json | 3 +++ 9 files changed, 29 insertions(+) create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/deathsembrace.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/fullpotential.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/mouseform.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/mutation.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/stellarweakness.png create mode 100644 Resources/Textures/Interface/Misc/job_icons.rsi/Vampire.png diff --git a/Resources/Prototypes/StatusIcon/faction.yml b/Resources/Prototypes/StatusIcon/faction.yml index e8400480083..1d36a9fa408 100644 --- a/Resources/Prototypes/StatusIcon/faction.yml +++ b/Resources/Prototypes/StatusIcon/faction.yml @@ -63,3 +63,14 @@ icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: Changeling + +- type: factionIcon + id: VampireFaction + priority: 11 + showTo: + components: + - ShowAntagIcons + - Vampire + icon: + sprite: /Textures/Interface/Misc/job_icons.rsi + state: Vampire diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/deathsembrace.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/deathsembrace.png new file mode 100644 index 0000000000000000000000000000000000000000..d74908cd402f538ec79feaf688e648a8dec1260e GIT binary patch literal 651 zcmV;60(AX}P)uXf1dkW$1l0gr!qPW!+>d;c)@kT zMLs?$d;WR}^8*zyD(k)As^hX z{r)*^a69nl_ha{_V-A?Hg9`A%Aj3z{^)2wi&vOkx1F^}@L39rvLG9}1=?ojsK7>L7 zczP?+f56d#fHW?2kOkg+!Y%NtP_%j>0k_xfUB>M zfc*y4@8-A#Jo{U~?Eqj~Tf+@ArY`|w(ko`LqRLDHMU^pj&(1{!1cSk+h_WBWu z>TjB;19S%B>8Q}bLEz*Pnt_Y^H<-pRW{e1A09bx^fbYe(h^K-5Z~m6e(01NMDXIyqdl$b;h161_MAowwRyg4_?Y`2ghEg%YY|9mS(1 l0Bt8mg-WGD*M&j>{{gIo-u<;m3pW4&002ovPDHLkV1f>OGr#}< literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/fullpotential.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fullpotential.png new file mode 100644 index 0000000000000000000000000000000000000000..7cf40988b4a4be70edb35012796153e94828a420 GIT binary patch literal 658 zcmV;D0&V??P)y<{`Wh(uH=Lsjt~piGp7 zr437zDajTw*+T#+6WNZze$LtC;yWkqPqN;{vETRQz4JL|aL)O#TS493KwkL1AC0He zDFEQ+`XhGs?@$8Ars|t8Ai2Q<%`7I?e3o>&*tN;(|)SBEKjNd8H;)kWM3Dg zEm-;%B+*~Na%_%(qQqEQp3Lh&lwgH5U}Xcvn!F|d#Q?F23DdGH*tV^86CdcEUuu8` z^uJw|Nh|(H0_rQFsFi6U%Ch9jFahA((O=lMtzCsIkjDAR7=Ta#%d%n@ zO-K}B7{`Ajwnb2Dn$;|r87)|CNo4eOutSR=Qir7u5s?QEo`NpIUi7r!CFl@Q9STpC zEYS+l;vqZaM`j_dCTwLNFcZ6K?#287-wxx9vmd(e)JpVRX81qvJpcdmf1ll%R}n&p zv3@_Tp-rq;=(=v*r&1{Zo_6lxz{wtp$NK#MbOh(P`C#w5iLi$$!t3>-G&VMhT;x6X z?iZBQ-`oU%rfGBp=PbN9bD<1zxeJmD`LSGe{mo6Zh71wsuY5siYikqc9?5oHTfRtF zA|1Q11KuPzNImawZbH*EbX})N>Iqv+h(%7gv+)ydBj+?M0W#fBh(%NvT#tk5TEXp@ zj0ihd)(or`SV1o16#@e{}~MU00@O+d2=%L zK+=JTm?aqSQFFYGn&WjAnVmc{7ZI~k0;FC>7D3f%gz}w{L9#>~-n$z`R2b{Y?b06! zh=^Gt;TY{xF=&8YOB(gZyjklK9|!nR^o^0hmlkprA#Cr356^B99f|{R z<#u1%H98beBhPf`uLYPQ0LDML=xjL!KxfM-#y=UKM|Rso;h2<&BDdR(s;W7Yxdy4a zY>BVaqX29vISqgwe?)cJQrfy;z(*(?%R7vg1G}0p=T#dF_}HmdP~+K1bSTc|G7m4F zKcULIpPgz2!GMqUGb`k)g`G?Klgx~n=}AgTw{ut|d3fs*`wtyuTUj|jr;>oVO>(~g zSa#rI{VP;eO;eW3x==XAh&Ip6^d#f4w*Y*2Kg`dmBqQ4Vs$JV_nH<-yeg^DsL-+E> zzA57F-JHcLOAj(L2kpJX7Iw$GkIGV5t8;sECV5A24rR3OtS}SNfd=c~2)^ZRz32Jf*{c8Pz4N~N|L(iH?|GIGLfrNXoYuBkmmJ6Oo+pzD z0PSx(?CoC?yX_YMs8=5O^7Yl)AXG_+OeTX>EEa{%yxDB7)YP_Z0LJ4n^~$5y9t(C{ zwQ+D<+qOBaO(9Aj?y#!Ws*r26F7u#l+h#l-<2VjmQJuw_VLX6Ev$uepl_qDUS7xeu z&Ful5Zp1yfAiDN{Q8Jk{2Uuh%fp*0c04+vKgBSsba%uX|Xk%%BZrt}{QocA7phy5w zE zJ+hqfU^4V_r8rDS3^`XIqJSd-?vx_5V>sB6yXFT=)Clbo+{xPS>fEsMNnJ7Gh2iJh z%YctrQvuEAG*wsJlQ-z6%|LKNEvv4Z!OsSA%flG}cPi|a!`$;6s87!17UPN&0P X>N4rSd&!(j00000NkvXXu0mjf!kteN literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/stellarweakness.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/stellarweakness.png new file mode 100644 index 0000000000000000000000000000000000000000..e9aaeb59cfdebe7147a916f9970092f5eb669c7c GIT binary patch literal 1241 zcmV;~1Sb25P)B_ra@6oGdsZPlmpLdqSUlBq2+9Sx9(ri9Y$D zshW^te8DUu{E50<1KlQzvSbSeXNG7C$ZqLu1M6QO+H-bq?`?4je3PcQ{z7w=$hXE6lYO6Bk3xxi z)F>gmZbk(iecqThbBZkjA_wco>Hf@usUC#)0#jN&3RTL4d*5v32LS$^FQRkXLT(YT zOrE8&OQz#p{h4joa7}d2NR+-Xk~(;A*kqdPT?x{2!0REmxM8O>o@; zJOuF=)|pb^4sK&ZaF^_ha6c&sVq>aJ0v_tuObfca2W>YppX>FJgE`ff=RjVcXt{~F z=Yx%t@$sj}7#shbUM13&JlxyY(q{W4Y*!CevQ*G{P4N+pMp;~3WO#VkBEmzE?+dr^ z5agff>+2KH3}Y>n(CJko#N%-m7Z-^}ql}D<5Rb>{RU)P$8|Zl4GoqsI zeoT69jDQdUNih3R8=Kk zT6=GF6CfP3uLBu@qeBCHmAGILA#!UZPbQOcteHHStVcjqRRHEvE};G*`)uQd^o^ml zYrka*dX)&%sU@aUOO25=QUFcUWM7bqaaC2(G>uK3K6WV;=29+6-V7(ed;6~t3WYd2 zG$0LY#F(OB^w8KBIiHxA0AQ1+Pe#BSavbYlGWL9JRr9$W^c2}cEJFz3Zd>CzAN{0 m9>t#cZhtQFiO!AvKN(i`ii)i){T~H1k-^i|&t;ucLK6VZ@-K`4 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json index 14c4e356941..11dcd61f94e 100644 --- a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json @@ -205,6 +205,9 @@ }, { "name": "Changeling" + }, + { + "name": "Vampire" } ] } From e4fd88d55cfc2dad43070fb8e1add82b16749773 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 17:48:08 +0200 Subject: [PATCH 15/67] vampire antag role, mind role --- Content.Server/Roles/VampireRoleComponent.cs | 8 ++++++++ Resources/Prototypes/Roles/Antags/vampire.yml | 7 +++++++ Resources/Prototypes/Roles/MindRoles/mind_roles.yml | 9 +++++++++ 3 files changed, 24 insertions(+) create mode 100644 Content.Server/Roles/VampireRoleComponent.cs create mode 100644 Resources/Prototypes/Roles/Antags/vampire.yml diff --git a/Content.Server/Roles/VampireRoleComponent.cs b/Content.Server/Roles/VampireRoleComponent.cs new file mode 100644 index 00000000000..5242a143fcf --- /dev/null +++ b/Content.Server/Roles/VampireRoleComponent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; + +namespace Content.Server.Roles; + +[RegisterComponent] +public sealed partial class VampireRoleComponent : BaseMindRoleComponent +{ +} diff --git a/Resources/Prototypes/Roles/Antags/vampire.yml b/Resources/Prototypes/Roles/Antags/vampire.yml new file mode 100644 index 00000000000..8a3a04f0c74 --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/vampire.yml @@ -0,0 +1,7 @@ +- type: antag + id: Vampire + name: roles-antag-vamire-name + antagonist: true + setPreference: true + objective: roles-antag-vampire-description +# guides: [ Changelings ] \ No newline at end of file diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml index 3a0c52acc4f..8fc61da26d2 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -187,3 +187,12 @@ - type: MindRole antagPrototype: Changeling - type: ChangelingRole + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleVampire + name: Vampire Role + components: + - type: MindRole + antagPrototype: Vampire + - type: VampireRoleComponent From 45617033cb1ef1071d025cd69ce723ee84bed40d Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 18:30:08 +0200 Subject: [PATCH 16/67] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BD=D0=B0=20=D0=B4=D0=B0=D1=83=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B7=D1=8B=D0=B2=D0=B0=D0=BB=D0=B8=3F(=D0=B5?= =?UTF-8?q?=D1=81=D0=BB=D0=B8=20Null,=20=D1=83=20=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=20=D1=81=D1=82=D1=83=D0=BF=D0=BE=D1=80=20?= =?UTF-8?q?=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=84?= =?UTF-8?q?=D0=B0=D1=82=D0=B0=D0=BB=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Server/Vampire/VampireSystem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 9b07dba4e12..dcefbef814c 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -126,6 +126,9 @@ public override void Update(float frameTime) var stealthQuery = EntityQueryEnumerator(); while (stealthQuery.MoveNext(out var uid, out var vampire, out var stealth)) { + if (vampire == null || stealth == null) + continue; + if (stealth.NextStealthTick <= 0) { stealth.NextStealthTick = 1; @@ -138,6 +141,9 @@ public override void Update(float frameTime) var healingQuery = EntityQueryEnumerator(); while (healingQuery.MoveNext(out var uid, out _, out var healing)) { + if (vampire == null || healing == null) + continue; + if (healing.NextHealTick <= 0) { healing.NextHealTick = 1; From 5d6c769407f33ad602c574ec60ec58f29a970603 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 19:12:02 +0200 Subject: [PATCH 17/67] fix 1 --- Resources/Prototypes/Roles/MindRoles/mind_roles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml index 8fc61da26d2..87c597538c0 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -195,4 +195,4 @@ components: - type: MindRole antagPrototype: Vampire - - type: VampireRoleComponent + - type: VampireRole From bbb21f7e29d794feb1b1693edf23a7d92d6054f7 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 19:12:09 +0200 Subject: [PATCH 18/67] fix 2 --- .../Prototypes/Catalog/vampire_catalog.yml | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml index c81ae0dabb9..8cbe3a95096 100644 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ b/Resources/Prototypes/Catalog/vampire_catalog.yml @@ -164,23 +164,23 @@ whitelist: - VampireUnholyStrength -#- type: listing -# id: VampireDeathsEmbrace -# name: vampire-passive-deathsembrace -# description: vampire-passive-deathsembrace-description -# icon: -# sprite: Interface/Actions/actions_vampire.rsi -# state: deathsembrace -# cost: -# BloodEssence: 160 -# categories: -# - VampirePassives -# conditions: -# - !type:ListingLimitedStockCondition -# stock: 1 -# - !type:BuyBeforeCondition -# whitelist: -# - VampireToggleFangs +- type: listing + id: VampireDeathsEmbrace + name: vampire-passive-deathsembrace + description: vampire-passive-deathsembrace-description + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: deathsembrace + cost: + BloodEssence: 160 + categories: + - VampirePassives + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - !type:BuyBeforeCondition + whitelist: + - VampireToggleFangs #- type: listing # id: VampireSupernaturalStrength From 6bbe4f79d83d9c450b94fec218a1b5858f7c9f89 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 19:12:36 +0200 Subject: [PATCH 19/67] fix fatal, fix build --- Content.Server/Vampire/VampireSystem.cs | 2 +- Content.Shared/Vampire/VampireComponent.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index dcefbef814c..77a019af41c 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -141,7 +141,7 @@ public override void Update(float frameTime) var healingQuery = EntityQueryEnumerator(); while (healingQuery.MoveNext(out var uid, out _, out var healing)) { - if (vampire == null || healing == null) + if (healing == null) continue; if (healing.NextHealTick <= 0) diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index f2d0be129e2..1c1201ac768 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -158,6 +158,7 @@ public sealed partial class VampireFangsExtendedComponent : Component { } /// /// When added, heals the entity by the specified amount /// +[RegisterComponent] public sealed partial class VampireHealingComponent : Component { public double NextHealTick = 0; From 52079be9aa467c7f6562fbfb332880754fe04d1d Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 19:12:48 +0200 Subject: [PATCH 20/67] breafing audio --- .../Audio/Ambience/Antag/vampire_start.ogg | Bin 0 -> 111531 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Resources/Audio/Ambience/Antag/vampire_start.ogg diff --git a/Resources/Audio/Ambience/Antag/vampire_start.ogg b/Resources/Audio/Ambience/Antag/vampire_start.ogg new file mode 100644 index 0000000000000000000000000000000000000000..8d8616f6a44d5d22ec04a0042ef134f90a7a22ce GIT binary patch literal 111531 zcmeFYby!tjw?8^JjWp6Ap&%hhm%wI&BHbV*B_iF@wG{-Vr5jX2FeoLZMCnGll3 zW|)_Y`y)G#%LJ@@A20s^Kz@b&{jY{;Ue^5Y>lw8Z6Lh?QG@p*xFqN%Cv$qMMN$$Ssm>j{ZkKy`_Dgz{M`pIfB=Bq zgO2BZz6VVxIfJ|w{XCO=ufqntyc)%GCLuMGE)I`4w!B>VmnK}#`s4J$Wrc7#AF9v@ zIRds&4jIGv5Kb8=_EHi`v%5?qm;bMef}|~!D@SlGbY4J$IAl@cm?>02(vTcVTVV|) zldnz>`6vk$W*1e`3c-_HVG8}u%N6nbhd?z=D2^ys)bj;__Fo}H@-NdvltqsjLT%)H zX#VMgT}~-31W&G=F7&<}^aOa4)l{Kfa_<>Jc_ax~LRm!~Q-e4Fa^G@Ayydz=&QN{@ zv;#nn(57m0W9oR#)KSJnL_lJM3jhodK;@Jo@|0p0l;YWq;yM0?rB}tgUFCm+(BJAx z0BC6wE4WeN|E>2J0D@eJhIUB9o#O zirxI=8to>YN%Dj4c`@X&=Iivq46w_>7 zoyjEs?;?N8{RJ5sFM>QQ+H#7UU~t)%^*+TT2BB3s)QSKhLrA9l+Q5o!Ug8}BjwCl7 z70!PE#{vifwii@7O8<3v^pY)%&b^bnF!#9%Ptp6gQ9@n372SU?cQ!s&kV+8?{vf}5 z>(dV%T_@2ILx(NN<@As*4~!i?ebrIn&QH|2@)ti=2zz-v`p0qFmn}bf$z7P~eud}# zKX9^MP7MH>drSiq*Zv)LnffR=v?-y$HzXuFq^qFsX+4_kX;c+xGWNn06*vurV^2%N zWNV}U5&juEw9Z)}V*h6#+^uvT-I6QpiRAw^OlU%ZE0yA3dQ&cER~=;U8RXU67ty>b zqfMnSB%(h+ZK^Hqq%CGWBxe}g;z zI(PiML=3w!!nU0UAn-0%WW*d-%)CTAyK+2-UA&EN^82*()wFD$;=e@z`DKyIBci}3 zqR1!Wy-&=_%Xr(gk?pYCo_4p90Ze0%r%{DZ8ROyP^)ef({h{Ctu&)09=};dM zQNIcSO9TU-#_l z*u0w9B7NBZy8qw&AqGVd`_fSOS0hkIJJJ+8_bC2#&)bJBGJq|u4RMeE_3?l2s}$<% z9!0315BvW&@c(rL$e~x=Z|JmOqhct(4qjdbu1b{bgz-2%*hLCTqBCvRCxuIM;0rh`l!474}4Y5W5WhkRx+mf^qG5LBX1 zge`(xA@ydHu=>mE_b3Vo|6S`pV-RPibA?{u0&j@_`xmMzAXnNo!Ksr7!zt+kM}cz8v;j+jMdTZ4vd<0=r@! z0d;+9NM8AU342Jvk2&xn>?014#{x_dkBrine6u+t3X0PIX+>c`L|s>3IljO*8xof6 z(hG{qdDZu&FJDo5`uZ>am0VoB26d!K1(ntx_WX0%0@P#iTJ7IXhxF9-Jxy673hdI0 zi;F)K-P5Fk+B6;Ye75}RtzB{PhuYiZ>X*%Vni}CZDANtQ?h_5XBQ&IuJ@khIqqv3k%5wKoea7m2RiNO}0F8hF11a zD*2ln`GY#eta&;*+|;3ZDm)zT5_Rmk^~rU(ixRKM^Kc~T05J9p8#-TT*|w_yRx*GT zxPST5*0`QWWz{RmNud)VxSW}wBDe&JLCW0p(A#`DFY&L)qoZ<}Cdr-uSMEvFQGp~S z4Ha&V&|W#t{KP>>KDr8k-e0uewJ+h^koya_4h#^-SY5&mA(hPjcec3F3vMd^GXcdN z|Al7~G>wZzcfKA0Gy_ldj3dkBUkWa>=lwEv{OQpv$HkU3@`yLvgH)cI z`{x5IZSGJV9TmPx!$_4k@8We-J`Ed22^Qsz7)O3;eH5k4{pFW&q#(@=NA;qwWA-1) z=&T%g_`c9tMg1f&f9%LtFkw&9N^rx3mv7kEL7(8H0PhfN zaPN7v@JIG6>KHs)hH^>(;l@f15eY;y5LM*qL`gzaK`Kx1Mh{B+8)Hk1x}=`J`j3tZ zl=e3kr?d927`X~})@6-L6^T1PR3YRDrPSf(&Zkl#V1qKDG(8o4?&y2PIR zQZO_zK~HFjN}gDLjmcAu8;>uEPQH~cZ_HiwjRz^j73BaD83zD14z4h0`VIbgkUz!3 zQDg*2)_YH0-fNL}_0b|Er_hBV9zB=84fG5j=9xZ3q`+>k%zegY!(k(k<0Z{S;xJ{p zwp?}b`A(wI&&}h>3bhLW3D|)i@FAd}80fZqZ0qRy)F%KL`Z6jGVvx{54+0>~l>>3_ zo`?q>Jp&`tHD(r8w(B?8AvXSx3eJiII5_`ERF@na2j?Fh8}aFHeQ>E`U#4%8^@u!# z!RQ1;l;Mh)YZx318FbQMs4=t{Qs~MAoirF0%nb}1^rOXaV@RMo77P)F8Or0p&_QWr z7)lHyh8Q}pK_@+w!iiypVzf{R8h{nhuJGPkYT>)R(e;1&24JmLh#EX?my~^Xqx_wA zCccx760QY2MmoTs`R(Jw698!lPjsd5Bg{- zevj<|=v~G9NExj;^{ze3{03jCRoTDDmJ-p)-E?Rpdh^w-GB_6&+OG6&rCWtfR6EVq z;K`pcmYeiGcm33c(9<)cJsD>;Owz&CsWmc-6Pk_3u3COMYv$fp2b1lGj*Al2{GW!z zb*7x>+wEK<0#pk36@!#@1i21IirSg1IIg5&trq-nviXXcpLgPAl)Q~w+%;KPh?dab zdte@5^X=zM(x!j!SnkNkY4z&b&1^O!Vt>8PR2G@SR^-84y!QWai_WqOJ zvgK2q(ic@H^XtlcJ>s#w0%pUbT0BAZ%SDGtoU`+O9R@r#Zxz9v$<3_~lL;2peEnz*hdw18YwG z&3Uy4qw~Bp){nmRUL3v^h^-x-b${27^bLM4&W)TEM|BLO^&Q;??%eqxWNU=Iim>iH1B;5t8O*peb6G#zqex&_h8J>IHq*)JvvrJ)9mo__HQlwMT;XfSa3T_^maZ&&`h7n?2X1aJGMNzkbAl z?&`gPYge%soN~?sQ)9zrWYuun?X`3}uAy1HUgvyP-a9ri&`xUlQ#te)e5HOESIs7_ z=lz8A_5HicL+F#N)l3;@Z)Ebv!gHU~TY=h7eBk3ifMl1+Ty}ev^p{AmrOk_cdO*%L z*F01oPhKhSxV5yWU4*6m>f^g_ReES%KNc!9+Ti>)F-*Mevmj7nU=5=~M*UGu=4c}2 zD9tDKy5p`%_1lsPelF8fC&H4L^^aRCOs^=ExSMWs-*#s;0jbkT=w|xCGHunZ^)AYQpS|#qdpU0|a zCtPF0K_A=br44k#o{ERqe3or%TUJEb<(x4|ne-*`ECU1_gK`x>){`BqW!i|%8RUnUeRxts1^!|toCG4(@ zS?^mq{dQRYxra0*C?kmNUGm_Hzc{FQ`XsP*SgWS*db#+xDpii7=Ztw0!c+F~x2r0| zNLNhYtP82Y=Ewa%FU~tVI&gA{R%~kL9%2`Z?dJiDt(@S`L8q_lr;pI0iy*fI1}r2t z1(fYmbVcX@`=sT`nyi;2Fl}Hi!Dc5=)#tk|-1)Vki zq}>V)A@*DSI77lC5fcuefvKU@U?F>jU6X}SI+h>J@Z{NCTiA|F7zrrrvSr-u&uz1J z{fcmvhis=vl{BM_+OQ8Nj75vmft&p*!e3vR7Jn*jYFmsyxo1$qWD@B&wzi)x>9t4VRfYK2jOEs-IES*TOL{A6=kWO|*kM z8=_1cdF1RJtI~?yIlSJF5HjIFLJ~M2aQn*1pg+hdD>gJFRgDoi57U(WV3QSEE0xC# zAl;%^_A$nYQj3CG-`TMDcI#KF6KNlv;_u)?%H}UWb7LvpY!-9=eX+)<%^fdUy_+sL zi}l!n5Bc-&wpuQ_3p`QindqIQCmVv>PlOpPe=9yq@i2TN1-o-LS-+h&6r4zVpd;qT zWZ0NhtWukUEwiy}*Sn8e4$ihCM(2cE0;Ds%MqI6hHLPT4`e-`qENrpRMD-;}I~`7C z9zNKJtK$U-N30hXIU(c#aBNc_=9U0lI&`arrDHH5Y=O8E0|OC+5GK`o^jkRNyHqck zT|P>;5chv`+L6g4MJl*F)Vj-}TK5^x-MG$`Fz<4qh;y)M7hcQpD%GH5_#`FeJsode zSu>+(G!bb#r!Y-BNSgcGrL8LslC*sr8n^pCf0}M{`Fx6bIBg|+o+|sL#3oi|J6M;@ zsyO5OdRd$G7}@dzGOLpYsW*Y`cE4KCiNaO$rH#DXDUz|U_wFL_847OF+@|JVSpe)$ zG%N+5J!o~RAp6u3bZb00@w}kfGPNrEo%DTe`5@ zpYH5L|yYwF_y0&U01PKsF5TOg;0kq$Jm0G8vDWqOsBZK4SBDCz$vd&O{wQr&aXW;wg{Q zPP*OF>fF7S%-i7e8{^BxMs>9nyfnV@rQ@%?#cQ2qJr@Tz*}me`zJ!s|}VtMCtg9;F3l_ za={aZ%;=@OpDHd21@Y69^$irVhCRNSnr9BsVty1MdA>UI7UTM0WKMcE$a50Mo7u05 zDthLZuG8e%hIsp#&O4sHF$Pk>`#nW}lzv`(&_0`Q^XuU~du;4K>_AEyW@o^DM>+RB z{h4R#xoAN7CieDM{G+P<2A3QDjxPR>!hFlB_m_7!uP>Y*SAyKCsVlxr=2HbEb*2tebont2BmK1akBs&%_)H(mq%^_GbTS_-EZ$|pD~GX!X4Ga{`${8?VB zKm3}xF0=R<@3(Yh7uKD!TR0Vl!y4?0eXn)7c=iXbKKL&{uyR?8{dQn|vnFnk!2Qu)Z@|I%3bn zA9`^2M_2X(P@F9^@Wf2J8b(~M?Fgb?zjmBo@4{!M1;K2}c%?{wkgW#qv3aV=`+Q#Y zy2eeHaT&pcwB1TY+hxK4$$Xm|%%3BSG+Nl$B;Q;rAsu(UOq$3>uixw`yirHhjH)%* zRu8_~`6I3EOl!IE9()X|C5cQZO+Tllv^~O%vT=e`>QidnNBG7v)yXpDkS(YWwXk zE@jDE$wppjcPSq*& ze7p3P;N9P<`t_AXK>yWnj&i9=5XeQ{U4eih*KXJT&8MRBB{=_Be~n*O6lwyyNBeUa znFTgPF4f2MF(E6fgi1HJPt)%FY>zJ?v-MlvzW7W(;HZHPCr-IvVJ> z^o46#!%oFW5WD9~oA{@N8f))@DZh>dZkBs9ecKXugZ$mo=E0bUxfBdEH8vyy?99hl zI&1#h%d-Jb*(sIA#3U+{IBfyrBW1i?E%-&!i?@jE1QniNi&?*cb08rR0=aw50(>Rc zG1Al5oz;X0){t}6YD#O9-!Las{^q)b8E-|F>Dy6U|jTE=%e7l^jI#wt7m zB){FGtu^{S`N339j-v9R>>tY=l^4I1>n5sSkTsYtV!{k6o$n_sA3iXXG)!MLW6s&G zI;)p)>HFg(lRElkw{X_ZA*3^mM<*#^)wVE@q5At_PC|lgcDP6CGG9QtIUgT(zj)%E z!#3GoF?FuU?oC>!C)qb)2p#L0UgzIi(_|QlWlvrI?v}>>f-kRPY_GLT%P?Uh9J0DD zB0B_9WAQ+|XdprQ+(~l-#mKxX9H{xRT7M@v%KG}VtBNjGfusz{ znK94g3Ex2S5A>!NS$6kXyiJB`(H{I2z%ewlQg!4u+_@#E)BEXZy^9z6n)|c!w!@>{ zkH_d#zg@S(7wB*G+nSBeEV7;G;Al68t=YEqY>eTtfJ9zy_JgOzD?i-s4;P*^26yw5 z?aNNmi?mAWk|xwwcz;J&E#;i6J6mue`1i%d=M&DVXJ?*h__ER4Xwp1BG*BLhT%9+; z>b*#1&=LZY!8s+Spy|zySO#zPx5A&0U8%{O;X;hk&?1EVmL58WT|rCxUa~ni_fFXP z>6sBsd8puI;!Kg64jPpepKN~^Uas72EKtsemUrpICN;usj`uWzwqT3l8>ZdfhH@X$ zSJ%U41$=1x?vXv!nTTOQSB2U*pu9vT?mO0UvSBq#Qe1N4H!9~bi++9MQum}>6vel{ z8R^#;J|yU{sI~<`*wn1w^F)r!2#C7w(o2ekz~Xa<`}gZW~mmw%;JL5Oz24IvPp13 zv6Kd~O;*cwPtLqg+20S@Tkl#UyAs%#jcJ3W+8kMte2djzqE)9AP~Zc=*mgLT5OiLuHPPVVY zc^*P$MBq-a)tq^EQ;L&}m*mA)@%^E1&Muq5`Fnr7{;Xx-Yl@S$t#itjefFs_Qp-6q znf}bY73ptQ6I)-g?Z5a{#UZ_xv2b^OV!Rp~p)l*I8S&9QqGVlQ;EqD{#4kJH($FpG zUB@sy5~L9PR8e<*VOMWSO|!nEAQk7`2TjaY(O3*&TBTO7ufwi>o&J@xMi0`LlFNbA zvnIK(G=;Qim$y2xKJheX&o`+86Tm^QU;AnEBby+8)8Qw7l}Yoh zT2Abkgm5>qo%4wr#52Wgimq0s80xOH+a~4Te_n^s@jWl!y%SjCqCr1$V z^?*s#sNm7LK=-35v%aY+g7o6m-yYNAE3vYwE=_8y)d2^po6luzT!YBy$+N((nhQ@U z?3^%@rk~9EEEg5)&vkMKjA8A5lmM}6&0`~50Ymnx6r{-EAp=v?xFLSKA3a*4e8G%2 zY1P%w;Ce2KaTbUdV8sc-1+4Nne4<~->75m!{Y~}bnnb{G1V1%5dG;0151Ct=o-Mje z%jb$b@KGRVJBR_mLuQaM0lD9NtKlK8xn!X!R-$$nB9BFUj=Gy zWz%PAXgd7qj#z+TLb{~asZ5r7pO0b&x|TXBji+5ajO_-WL@ncL20UJ^ZQIETSt_Zb zM*;JzL_~!S|iwUOPvZ!0s=kjeN7Kyxfihj*d@vC_b zzhU*T;Q6L`yCNe`AB6Q`?1RO3?$$CO(vCW=Y3cT!dN!7mWnQ?*h=cv^#;u$9taW!h zYYz8dfUYz@c%!P}224JgL=XG$Gnio&WANl&`r28_y8Px9%GwLI{g+-3=t@_A+j$^#_Bg7c?QC7lIPm9Y zRaLc*NyRBf9Kqy0EtA%9eG_2%o&$dr-txdYM z!z56;q3>!PWV8dHK1mj8&}xL?YXpA})}W3vnlo+?iVUc8crV$ycAi$Y_Gdjq2tHlt z7L{$c5VG4NyZPbWA^89RAzy}U0P_rsaCFNDD>VQfzu3V_EM{5UzVck?gr1m^vd=cp z`8x76p7z}I5!NxXr`UGy1vuxW2FMp=Be8*Crw3#^8SR${gybSMu|fZ}D*#S(``F{r zP20^)-#??1yE41He31nS&4V|CL?>$uuSw5rlraVxxepT}JM4#$m$u4wk^Osni}jev z*ZD6pmK5~K^+=)`WcKbpWj#9Xd3Rhn-)WWO`(uBpH%W8NJ3G1M@#*eG*TwobgYET? zzs5ase)h3=N|O0j=QN)rLVCdQm(1RlM3S(SLr-IxdNf9$Pi(_|t+LJBd#NRGFTG;< zj21u8Fp>q+AZfE<*{e46P#~e7Yu`jOr>!3G439tsqdDxM_>g$0OOY7Fb-bw0#6sqc zQ*RL|UCC83Lc01WBekNC*}#exz7nvT>Pa-1aYe=~^ zKGszwcYzY1xw+5cozp-p@%7E#=B@0yvF`N#X*Cqna^r4k;T=!(8SHnp?61a~Q}q6o z7klN@?(gPFMfxqeD$Pe5siz147Sbcqz`Ch`FC*-I@%iaT$e0x$L}0^WNP{b{`HP|# z;8`##UGEr;xDH(X1Bf#+D5)SiQ=|jgXP@SzJ_1X}KB}_|FGm0HBnuzED?uze?cHjp z=(*1_s@eYZWkz+pU)2f!5QMQofROVxi-wHPPCuV^GMfpGM9F4fnf4kK3GlD@Q}7ML zY|R8;5jU@$T}GKG^ypfd9Zi1i5)#hK(G*E)&3=ZlAmJ)>O5OJ||AeMH8=WK)6+oF{ z-28v5RS~^>qZxWupY$*nWSxG&%w=Yph!0IUom@xCk~Lx)mcL~M9(|MSwdE7;)vV!) zTfcLhp@#=)_YqqTAlDxrws`(BUidLX6AV!+NXk;|$5YIcbG^TCZkrOY!UdJE;!1Cj z!;k=hGk5EJat3b&TZ4InzzQ-0za#E0wF1b;>!a7JIhcOgT?cHYVvsV9-%yvea0`Bw z|Bbr2c+|ya%mMWlmZPS{hLuq@lUr+hLbT!1>6t)o3i*u}mnPk&wPqdZ2)Ak}zWcTP z-Yws~t(JBdeZ0T!l~A&;+UOz>Zii8rkt)o@FeXs?^ZUNYl)(!(^FB!f2lI`JwxXQ_ z-oxWj_Dt=Y8(I6aTf%F)>jE6hzlf5iR<`T0**)yizBeiyt~imTEl1)+;H;cX%n0>L zhrDu~?Bx#%GGbOG@S_#r3)we4i~KU{+0)?L@CY(;@BK0eO=CvLyyck@8TKruR06z= z;dp>m2*Fc=S;1f1E5h12Yk?R5a&!2NBkjGD^sen-ELTWmAwa;3{7HVWB7_A39#(za ze?aWvrkd9vhI=n<^DVm5^M}D44xegR!qCa{`hq_k~wKh#Kt+UJ4>`@N>)|_SI`u0)VeB(u*RiDSBs*J}4g<||8FGh0R6b1bY zw@(N7FZMAcR%7s4uTP9T;X|%3XUu%kZNf;Rj>Z#qj$5#RIb&~AssAnp{U+1be+lEwCMb*8}{BgBNjs1rFfU}Ui-PTyi{vVy2DGqeDORCv&(A~`jnSh%xdS>jx} z6;a*AEUg#mzhh)fZf!KE>#Zo+MGgMkLMAGoX^DHH8K@Bg`U@o|BfdZPf|K!tx=2}+ zr_mS_=B33PP37^ZdY{0tv&~g^S^o{e6S$~usb;hDXxEg*K-ISLO~%RjMJM7mf!2BY zxwNFpCnCxy9-;#^Jk}CS8_WAFb62%%m}KEmW`;og?hbQoNy7e-YuG+FZ>@w!2kSvXxp65hCV=UO z5U6y;yKj5NElQ%VNjHqi2|o%O-IaAYQ< z@akC2?xdEx+#`@s^K!oGZdPk4RJvCzb;ii7`9!X;%5NZ3h;(R%`Q$ODNNRV7zG6bY z-u_YH&mfcTScN6tmcxwqB`(!4Z@-w;N9O$)Z)B|$Zh*-LeVOG(V{+jZ-?2jDyM-C) zRkmwUIcA;G00o_vfT@X?V4x5;KeT0e0ySi_)dolN#OaWi9fJy z?(6{^$p%ORKDLh((`hwT*wu^-;Q!8e&rkUquVHd?=|(0v`cjCy{dK+F#t`@UBy$i; z&CKL}Tv-@Lfy$~K>S~vljgxbYD^4mT+<{U&iOLz-%5bqvvMK3Cf7iMx-hAGARM(Mg zmCM(C_iT)R>F|h0kVMsFK)_bc{9(h<&P1K@*@F_RJ8evMw&_adL{a@X!r_}vi47eA zhIc6Yb5ovo1TB1!)bjm4`>t`+_W1)OY5jadL7fCg^EvLz3!UNRpnzkC>}aZ=?>`bJFw1s_t9&&^>5s$}F}Ah~F=s)T`Yhq`7@SM^b?kmp zy6~zaX}nrk`umvuxKH1AezwB!{-(29AB_Ea$eaN5N(7av6@{(j>Wa6oH;+Cn>ukI? z_`PS`YW?b;qbJ8MRb=xY8uM00Mq6l9IRdlxE&GlhF+EwcbaMXsJ4r!1$JYA8;p@92 zYjk4$I-Z4v*xr1pPSKOCLN_AlU)mPbi1wNA z&p?$;69{NeY_e!(C)6h#22Wz=VuKF+Mj8(gbrL<*npT~rhmXtM{(Qk3o>@&%`tuFn z%A?*xrx!phRG#*auG9iTX$)=N08TiceM zgl3#P2OuskykX2!mlg+@J)M{KLSxFN`Rg+1Vjn zn%Z6NSQsCXuzVEGIWXA}cRF}4Cay3y0e8#^K0}Exdo7-QJ;GGYaNd|j9nWE0@V#W? zm-|GQvNKf*boJ2WM)V#oErl0HA{l8Dee+X7{d%N+OP}kHi2K!Vg;0Zga^maKs6npm zv-3o;s?q7@i{fyG`O;vzjOfYp^Yd-v>bAZ@LS*;}s<3UsB396uAczpL{J{sZGX~7_$k6OMrsf-c4MS$lNNMo?nxi$(|k4TYm#Ni|2 zMA2$UM%Hk#+xm3ORPgmrzi)H{m2$e|;ieP;13ES?DY2h_tJ zU%}Kh&7dG6zBt_7zOaXdE_vDR%=O$62O!V)oP$Z;1i1Uw zbg7#>>RI*=Pqj`4;Q__0zXlRvRDz0W{PadNpveTFiLzG>?7feQ)8OLg!8Ig}Lr0I^ zstIhRrQAqR(=u-WeQ*`#TGI=dl_0Y8QX8Tp8dX)DSJrH7*|Y8rDBHC$!W(*ep`-c4 zNU@kAd~NrQtV5r{zL|A`ciIfKoWjXW=POtLojd9K9W|T2v$K`KQIgv|2K)1=-QVLj zvgl@fBb}Usr{Gny*`p`P%PrZ&`}aPNyHr_}7cZebwy-mNH*UmSxv#La_dpC6AkbAo zZZ)I3=ZYUl-B)fl9QBi4n@LSqxeSOTFR{JNfuzcTip~I)e<_sBZ zr*4Iorw^?2vNfD0znos2#U8=ihLd0&Oj~L>1&wrOsJ^Q3>9bgv$K?Lh@>J~5u8Y6) zMtI*?%jbRXoq)4VwjhZgJAR#00ZSJg&r)2-Y=DGl!<3lC#@Y0qv_y;~ofSkR&nE|N zHA=^TQoXw>_b>d6KVc!yD>%kkxwJVBb6z5GN31NB-&`U1a%tUfmwR|(!wsmA2#v;T z{R@JdC7~si7O)yxVD=$EnHrcenN4YqVN#U9d@H&RePZNPlEFk^)bQwYg4o36X_$9!!gma@o-7*Kw*<{Ej9iX3!{|v#v!{enSEmPas>zbUh98yl1G+n z0#1(2r;$Akfe(VQyV)*WXy=FMs{)q-CCIQ5Dl$h`v@jg{gj)JVIT#t)5sW@kFVbH2 zCD!1*ht$F>IJ^o{BnN~*X1oYjOEYQdmh}p-b#efstHkdZ0kl&+R8op{Z%9`!s1d*6 z<`c_-ak$R;@~efKlx%vbK!!|8N>B`;(zHIh?SlQmG)T;(Dq*LtOwV_Pr%LKq%C(Z7 zQB#9I=AM#HTgJ!jWX{nBdGQ8^9*r{JW>8}jzB5YM1D^XEsNf9yYsQ`}f!?1OicdXL zJtlfi|JZAw7S-bVjg)^j&5HhnjehC-F-(PxUS6sL|uR3 z#N-p7nZ|Zk0j}}4JJ7}}h&F4O(L5vtX&Z+u^KF^2yh}C1*8pJr*%}A&wp1r?}_FkUMxKU zLB#Mb?+Kv^)ylvJJ^a;DtKLsF@AK1XFem#>b3XbqYWCRCm~kNyvJ5(b4GR{HeKR;hV-$p-V3viii%?)rMi|YoT*#oTllevof1% zo5^YX?#nmxI1jVUODr+eZEfeK{Lb3vohT>T;S;}zbI#20)3A#>JKN{M*M2uuVtRqb z{fH&5HsIpq`JP&VLd;R+zZ&N)XC z3dsAdk#^%LHqlsRm%BV3Zv&ZJSjk|fQhplJI!*AHiQ$JfU+AqMim@#>-xljA*}0kf z%q^DX3WhKTlK$)aVGI{PI_a%yMYK!v!XP3-q#qo7*P3MMhvCm-WKI_Qv=184!k-(G z^|ouh{Y94dQQPjjK^8^6#tRoRQ#zI#YBNFIyAx+m4=*d>-tb@xx~A_5oM9-gC4)ISSi++;xiXPfvMfyv+u;zn1o2^OP0l zeBL5nL;dG{^({>F-jmL}#rn+dAj~0~4fbO9Iu*WnkoWlH)8mGgYu^e))kdl_wHaeE zwIXsMVy1BqXd3F=r~Ug1UaXMCqPjoHR&_oVdXYWS*q+U&U6vPtkL>~w0n zhJVloJJ&JY{BF;!aQs$~h(~afrPuf(D@1a5)MheR{Aw?dFQqq)lwwXc~FL`b~%(GYneqI#tsX$c`eO|ZhHt| z#iyn7KlXD0Sq~>pUx}UHsdw-iDqX+R85|I}zdhZsDY#4mGIiHco4&WNW2ArA(B*0a z&w~SM)|9F+8+Ur$y~hw+95ZhaCzrs9T5Rh7y=RRL5X_<_@#a)2svI4o;lYr54|85l zX*8BawLCN+0dbsE8RaxQSFSk%&@}Et5q!@~XbKaM#!Pj~5`f@F$xVln!6f#Rm7DsE|eYDrP_ zGj#WE7bCKNb7f~%@8pjt_CyV9De=9g&N z7wdJl+Uf7Vl?Fe=^V!`$?4G=6@@XpKC-b&&Z|#$vPP-ekN(zv`?3>}1hw3rYc{X;T)d{JuLUKUUvZh1b zZpBcgZ~S@OJ1yz}Wd8W64^_qXBvZ`QeGk8KKb}Kfi3_*<)9~hX8tNf0rVIBUdUQ2K zrXSCb#Q66c3}uJojl)Sx~))m&Zmyhv%b_jD0eoz7c2RQCl zB8~n>H-}F+P1ApD}FD-M@KS;9;AouyJkkkD&$kqPR znVuX&fw>8#(PPNb&@Bmc5@QG;@BB*-d>W|OrH?)@hVX9@D#$bc8k9;3RilHNVa8m6 zs*^yT`c!7TGxM5bBveBle{PX#E=qla(f#&zA(A{QuW(awG%i+Ax)jBJEYrfxU&SY) z(Zfx6w>n?g+FkT*lBPkbp>9HFArjh6ixJ#9+dvGag+@B>9{!v{e zNP3;#HctPk)84h4!lPUW$q)J(GZI60N=ly8&^uelcob-?Z)PvAr=d;PaFiq*&np^J zw_KJ&#BDaJq!PlkSA!Rl85YExmV{u5Ba)ivnc(HmQ7==n8Q$kTTZ^TQg&mEPrT7xv z^yQ9MxDCAnw@*?IJ$C9|!06eVQxn+DpFv+F?HwszoZZmTNm8QJ*Sy02Yth^iTfG19 zje$ZF=L2OxxPzU-5tOn*4~T(93tz)f1fl>Zn+e|e7aHMRzTnYa;i+c|6S!Pb#dS2b{ z@qGicVDp#sTa@+&xnF&q&0CwI1s$P>3^;hOnZh3zKUkP}pK_Kx1jdE#@-F7#nxr(R zKw49o;%R!m8DRQL3*%E>9{AE3IDJ}ul>J~6LMVsfMvhetctJ~yk~S1_-j1ljWJ;ZoEDuo&Y9!oRp_5it2d(sx-d*rn*!3lnqP&vjH zpnJ`67W`tFXn6A1VjI(snSces@y*9prjWi1tpYqm?HAjt(C3hGPSsJ5)M4OpqpFf_ zCkYlE94?#OOyTFmOw2sJy@UUoh$*~qk3R4!QP^oHTE-C5$SW>ks@);^@a0d~R?%Ii z_;%Jy{|a62jG!lW4r@$|2YtOv?vZ`{HZIj(t7o^lno}ISmkT{TZZorTvA(pQe>LER zYL1=SSx~l%7iGm_}7OMx3Vsu6h`EdbB7Ch=z4xTrpHBNwKob-(Lw%bN0d_UgjOoveyvlaS?C z+u3#uxq~(?`2g9M8UvrbY0zu8Qo}FbaHsU^&-*-lqI)1d-z#;w~?Qu|j6Yc0yP-i*z*!r_Xp&k#4jlQ3I?OJXPdVSQ%LeIkf z{czz=n>K6XjCil(=|RYG&*n4-`hLNEX2o5nyO+HXV&uFP=-w*3NpsvoNqucfDH&WO z2bD6w3vaFToW?^S<-@CR@fso5y?dbA2agDVA>7!9wyz5ibE?JazB?U=Y_byQ&{- zuP%|Kyei*$)72uir|X^>E#v zBXr;B3t!aw_90vf$4EG!Jg9WAz2C0`-#j^+Xn%8bk;FMFh~#iaou!r~Q<5G{)(;Zc z_oFF?&C^5{EWL&@KM;it@RRuA8+Vje3C=h-`sMhyrF!giJntbEv30zXU;h*asw8th zR!1tAqZ*+7ErBD|HS$ayWvgFJjenS%Cnits>;=B3gIM z9w;lBTMAOb8t_s}K3WnNYCoIdg7cb9ZU4|VPF7PSYkai(u;E6GaB5}M>))sq2En#& z?V@b|jn{5F+{w1XL?VSeZ!Y(K1X$M0lfKp@1g(BznF?AiyX$<$d$G9qNSS4Zs~LaQ zb&B^BRbMOx!5f?jT6ek?!Bhm=3|ZrI#=T{GV6RpYYDBaJR)9>FrRIY)1|7X_KD z(jzODF9uaL#7MYIIvC9p-BwYD+g6TiPP1Z1EwfcZee+&%oK@I(;RRRKiwF7*wn$79 z6y#-0kI-4nAlL5e*(l`UP<7I9Aw!Q`yV%&BRxrg6vjl|qU+AJr)uTc>-q7ZSU}K*-gB7(rC3i z*Qiq^e24heP9trf1u9{H*{1WUjy%PWzeZIi@dkS_J>SW7YT;>Fw?_{T$5$sVX2m8N z>$h1*b!SPpvz{~8?)}^~Hv87cdpdvMAY5?Lb70kQG7n#7J|ker)$9}3jG>@6tVRbo zcCU;d2#qrwyi(XAfqT@_$?^va?Z?Ps#{uDmZtsI`#*oM|s}FbJTTdHo+#0wgg@;JE zPyv#`XnoJSbz}V0z_5FFlvAKoIeRwYjk?2<*w>;3Jb-unNh#QkmNn<3fP>vm?g$5g z{E|E?K)^nUT`PFL^n?pMzoo?V8@sgZH^fVRB3aAp5Erw=4!m!_6d4$2W1)%6AR{wr zxl`duSo6DcHR^}qk$ks_rajf8@TNJ-Z~`6wbOjYvsL z=jaUsECfM173q{lU;|MmIRvHIK%~1i#@P11et+lB4hOj7oZb7J=l$gC^*k|Z#d$KX z<(^&O0JZQ(FM7??M&Aua@p+IjEbTqRGTLYno;okHz1iBd5}bF0eRD9b(aGw~{A%UpfCzE8q$Q>A z*K%s;dgsArOnfut}W?pq83-B&=(7f8Oj-H>?*o z`vQCbSM96Hz4ebj_*B&r;yIjvu03B`)!lc;DHqB{TcuCZBHvm;JU0yJxq*=%;$d4q z@??e)5F~T{RBbrVo!=KQagM&KFOvcUFN~f2Nou@;O_P(bg0BaBdi7PPh!8ejW2{%` z-&FR`^rF;j3~QtA)y%|3MYeRAtk30kJPC};Bh)2Li{AVk8%+*X=_Tm7zM{apg&2o@)ixYfD7f{QPUl>;82&}nmy3kp zMUBM(?j{zUo)_aGSbEfs{Nw#lGAdQgI2bRt1-BT~zoT@&Wve|R2md-v z)!#qDD=tttqdUt{?GWmBt_%zHTVa^bB};$!Akn~JbDPxTDX8UWz}bKiTvnlw@q29e z%%0QiIU-mJJHk`&ZXYy7Ed} zkkn4h!_&>PYSOTk;s<9^6{g>P)?%C;i(?IB(E-H*l(Em$su$VRw{8fNFf10{{+FM2 zZc8ll``>^IgF5@9svY)Qo73S}GuNPWN|iL*wNh7-S{m4b)4*4E)L1Ly{0Zk5n|KL? zwz)(#sQ=9Jo1?|kAOmr-7=N{E+q$J+@#A^AhMXAT%y zd0XJ+$xVC8?YZP4uqXoLADC&-cT&Kwv{aU`~4nYow3|gn>+Gzo~AAyKZ zR16JJlbv8uY|758-KjHzonqG5Zt%{IfA#kz-%Y4|2u-ZZ5t{ znh_r88NE%7U3jqnO7Rz*yl7>*(HCV-(o!E88$LCJYmF0@1)2RqA0*-zo95lOuX{fJ zah>}RcRSXI+hSeQyf}BKV27pDujR8sD1u-0U}kWIis3J;kx*Vg!hkfW|3rkm4ak0=hB>~Zd2#jCawFJEY;cXCcXGy`T;kOKLqCWG?*A19Gl zv5tWgjtjh=ekR?7`-!4AwwB~BpfPIcdU&&YMbc3ZA~G4C{Bu9RLcud5+5gs6B5U`& zFMG9|%E~nCJpY3!{hWfLP_U)VluESo;0*79OAXJA8+PP;(=@_C@rG7Q?(nqlHDe_* zkDSt8=xy5~JPyUK+Cs`|K~EE}dE_^E%s-B0^Pou`IQa7+$!jOuCHFM*1eJgefA|^Y zD99<;#ovE2(7k%I@BNB%HOOok%1~n;ZcIvvzL==KQa*96Q2=ocUR0{W0H`J_a}0Ox zwF&K819+ARJC4 zK$x3C+S;>jW%Z;zP3W#XeIZ;bJqd-N)Jpzp$J8){32Rt~GR}YaaOpfKi+R-@bl+mZ z#<{RTrLoq`XxSAO*IT`e)QaWk&mUng%F6P8bV)+n)nnvp)T#siI1@b_rUg-lA6MGh zga(bbzC`sSGd(?n`m!WymrwAg!5H7|`&mz=+HJjCBTJG6hT&fVE8Sx%xnA7>`ktU1 zDAVzf1ef-inean8`b0ieSOW@6QP|jG;Z~WChK_CNl|pj|qV1}pK!{DQEI|+9`|)9| zz}nCQF#9{V+(H!~xN zORZvDjv6POi^DLN3}gM! zfBxvLdzBiTxM}!z=KiU*?x}sfX}*CDR!vs>ec-O_Ch(Mu_Z4!_#cZ_&ZQc)tZpTy} zB?GB1pqL4F1Pwb}ZPyk&leXA$?b@5E(zmtBTP`={NNngHx z?S35#JBZXvfqp%^whMBF5>=!Ib?7O~*eSgSe38zE1!j1z*Dx}lR^KXJiwVL5U|+1? z#JVfq)5sgI5PR~k`7*EGsjfKi{pQE;iN?-X;wTLHP3I(OW#JJzCR@wBAfah{Z?y|m zD^uhT$3>qWYbGwMdi#@`?3RxsPimvyrs`*YL*qPt)S9#ir}^i*d&SUFrd2je zGAqEt@ZOY!lW_g|V5`euKG$d|X_8*@>Y` zHm|(uAkunrm5-hN`sec5k7sUy!azVd9gPj@JF5s18jG>jdn=cmKVfR$l%h+8txE~p zf1M-REM~}!$#AgbZ*nm;C%-VCcJt*@fI2hyG#fF2?f)U`LuGeMLq~*cc*lCJ9BaVl%g&_q zZ&A@!6_*EuE^@hgGA_P->>l2i#G^4jyryY7>t&>D}k`m zbiEcpl)c!s(K=eJv1|IS8ChxB8vS>o9c2LvGq5UH8IQ#f*PK5Uc&O)jMRits!OMO9 z4f&`$)YIiHWdgAshs#`<#TYNo#k`47-a3Wm&p0I2^0@$(ZBzP1Rh6mZOBb8NPIm~k zQ7*G;wFiLf^n^jnSNgw0BnvtK7&(smHA#N#j+HReQZS)k1}9;Xds@0HsQjNHM*DX3 zkFK9fL4Br6Rfdl#YqQZ0r(BAozhRmx|COn+Y*f9IhN0px_oxZ92v8R2l2quEDRV!Q zqj>@Df|I=LIO8yq&iofT_4kV=-T_M2qrR)RyB6p^e`D0Z@$7~^`02oZMMuly*(ta5 zvWrNB%M)cCF+r#J%)j|h{B^e#3J`?4;5M7n2%hphuIbpNhArZsL@Vv5^0b%INRjqe z&ruRiR1`+`w|=W7jYYI;X7F{Jx)vO_3ztQg&p6NGb5{n5A=ywoW2vMEflL*C!|yW{X4uOFG2eovFp;80iG?$rp$`6G^Q-hbZqa3EQ& zU}WC7_h*kDypTUusi|%e&T*wPL^I+dyt29Y?}$=OGVI>G-G&FM3h5K#pxui3NI3dh zoo6_lO4YFJdbSvp+008XC-Qzj zU(cBooFAdp`Mu3NzlRMAHjyV8`+Fr&dah1?_d6%XfH1qeylwm@0bsJ7P%9q_{e}s&sv{O z6#S?zjuM^(H!YeSu<*oVKhM{D5*4V$JTAXmJCAODDDt{&nvk13tAU;aLYdJ`-K=;Ua$ zM(_h=f9#Dz@da7YjKH}Fa{Yb_#D~^6^D%nD&5q;A)qU92yHEdi5;^v4`oUW4$bp6~ zmx!|i!t}y3XKVX=5V34+B)scPU0d}fAfE-9?lDO#hUPqe+w+=Y7?peRd${+|ZWM#& z?&osF3$Ucb)y=T!(v*}lk2t`2pj}3HJxg7@><7G#qS^vZezmtElS;P({BqEHgpQy? zn~I0a4+#rzxy*g{2#aO5F26#`6?z}RF}|{t2XL#+2EX}(Fzm{NbWCCC!t%o%yCZX~ zI5697$Xjs)*nq(t2_G?%nSSo8=Bhk3? z4N7bHc4-9ZRsjT1-yk2Zbpy*~)KTFvr-?wYe2-8%nK|^fD*{9YmR~u0C|P2pD$gRm ztJs}^)PJ{$wF)nPPR}TI0?>=}SRK)cSh96#Jz)a&wT>Dd-%MokDYQF;6f#i+@(*F_ zVREcd$2>{a_&nuA{m<3s8l&!hqwhEz#eYVJSTHTzv;dU_m}?QXVUH1c^>$Q?;P<^Y zn;RYW!`c{sBGKtwSrxR#i%uy>k2E5oU8rp<8}B}2YumXG_Vy?bR<_njwbb=wBHzNP z$r6&$BKy;V>W)FkGIMQn(e|4x;RC~xh~CM?=!mRfq1xZ-BkqB=gQUry+U@zm7nK25 zm}Ac@^`}pKOb+~wy-KbY<+xE8NATUy?wAcN1;dS!;2f$Srw+Ac;M6Bujs>th!Z$^K z+`+ogp2HeGatpP!wID5dLrjlmU^|yCSz2D(ppt7XZV|exq&?5_y|}IeS{zB8>}DPu z1fkmp4_X&I6$foamju0IK(r|%lm$Bkd&HOOG=2Fv9^F9Aixp%hmZ2_a(O~Pko;$0_ z_ur{%X*Yzdos0jM{O)!8J)X_M_%&+O9d7QA>aI^3%u&9>AK>GS^&JbO?R&Yi$+x3d zO2+gZ+Nsli@7fwD;i^d`A=8hRq9{}Sbcvy*P5#vb@7y)VE`;`LtiZ#Tm(x}T7;b$( z_?YkAsJ{Jl+0j9|xb2W;WL3CmlmGtM@%shE@rwHjQ_S07>CI}a6u2!;JoM4>DPo1# zmX-!-)A4EN+t#WWV+Y~MW@OvEtB}^VFSGAtcQ>Uj9?24GSOLmS9d? zUzc@k#4k;mDIVByi?43{BXWuk@!TyUl|U_Bgx}X`vW3iL?z9&ofYm-5~Cd8!^#xkWEU>B`1Hm>`_6RNn$Yk2!)|^4ke5^ z3S7-MLYXEZZA`N6K(4xF*Xj<}kj z*jXw1E+DnY_+16Yl4@D(fT~95v9M(T4lLN-Bnz0SAftB2_l4i^Vrj-c)@=`-Pcmrc ziO9r19)}lSpNtwCm%chn^RLNjTASz{)u}tjAGuP8SJ;>)Y)fdeKJN^iQr0exEfipJ zKZ0UySU)tt4dl?(k+OnY+Y8P3BnAKB<__E0Y_E0!wv^7X=`Yaz9%=4I;f-3^uZR7a z@cjIt`S_qEGSBalI~X9ZYIemr0J+%5U^len7>-x%`QX>m2_cTGxm1ST?tV?;F=gz^ zQtDWo1az!_M^i&KNweVTa60U1^Cur&WD$7=Mxjq>$NT)ZK|ZmdDqm`%+VLNQ39&QP zF6vzjh|w`Fcc3mpkpJ2BWQ$X>?&mmrX6WhMoW~<|+AFP5-3^o%!#bs{P0-niw16M0 zl*p%mSEo+v>YjGte=aR!efqDZX$-*^n0{QxRwtst89c`!f>!KNvfiCHjP+%6)F1Rq zrEZs_ZpaIoV=nqCrZK5K`(tMNPQV)CSNVw`S&E8qJLrR9Ge!qoDr&+k7nx9oR|yntg8Dj>}`x zgpluZ2H=o@Kr?*#s zc1$m+P!Kn zvS-Y6R|Ow5z>rGRs6BL6lzPii&9$G_)(g(OrbjUZ-eF+y&9Cp~AIs<9tIiHz!W0y# zg55%di7&WP`S?gx*}u5%*s&9I&nU!`+>u6#iB$rIk548wqjTl2WH7^kI{6fGEOp@Z zuPpjpZK*?1>2B>?q6q-N0hh2eJA2*PH}Ob;1|ORV;T@i+^*qxkczN26g)SzXWfUZH zBL>e`JiK+LixJB#^iM8@nPGHfutqEVa{JBgrl#Fptp<@A-5mZX*Q7@Vww*xg?{B1lRQv_F-5 z4JwF>oqPOnIy#KlnyKyBF{c{p+dAX%b|-_MvNyDM(18u0>x%y3+6493>J65oZk@^| z(Z1H2`*Zxzkp)o*npeFnuGDuaKauH4j_jxgHbq6H%oMM|U?^{l-C)Gn0mhDp4>)TY zvL&kQ3JG=mNo9$S0B>~N+Y;(*p+h-%$0{OnQX_A6?^>rNa^lM+1T9!)MEp#wJLUiz zSZB_;{NnAN91%csZ(814s&S%Kfo{$%dOTM1%)Z#mkf29S{gTMUNi)~cD&D$w=lct{ zBCXGs%)cbRv08%im|uJxy0~22huea^%+k1zu8LT3!rJV3`<|^!tVNp8S)5OY{ulk! zvN}8yIcFgBYe++$Kyq1&5Yo+G)mfi7S+Q$EY0X~jo%G+qZ{ot@NsyBGo<);11FK6# z3gSu5+wT)rF0Lc%3!_>ihlgDW_NH1c1qlnAMB>pJRNnoeZW=YF_9)QrsopA|$-$$T zX400cWXxmDu>813g%Q`bzVGES>jaKp3hoRtZvxM{$o#Fv{_usaEo3JU}c zFpvCpL(I^Y6RD>+A7FBfkZ9^;6Q6$gsJ#X%fH2V&y>%NDPyAu5@52=JqKl8cV2J|* z2|U`A$R0fj^6HIc>(%ME#-mHc+`C&uyG@cYaQ@s+Mswmpdlau0$@_cyX4>-a3=|Gp z3m+yuRBbzoH9omCYN1jh0;90qiY!U-bxFn@2?<4>!VGiJ4AIt5_&ihd5oQqlhOP6EA{?%AYT zLT4QIw{pX1wuoHQPIBjG|3q2hB_NX?2?7$krWogs5JloAoQJ{yLaNTOOA#Wcv+?Xt z*G2WZTHp}~U>Wt8_Cqkm4lHPqpUpvNPxI;721U0o9n~+!>bSShc-4Lo?Q?$dAJ{Qq zV`W8-uPRCIk0s1mQ1_3%UQ0(OnWgJa3aSU;MrEF|`zFj&c0EIat=m#mwz{nzK&7OF z#=HJ@#0%Z>ctqMDt4y=_e(@#Y6dHIq8Ozqx&Qc9K2_SPIo(4%$5~CM8#9K!rn99bC zQs?Ncxef<=2BK#D=Y?oSg07XqZ%lNDLWRz#0|0@%lF@R)k~~E|*`@0ReNQ;=`k-(% z)xJ|0;f6a;^YAyk{2slNT{H0(ugLxKj+Llu^m@2xSI|a|(s&v z8C_*^W@hJgK#x3AC4OT|)z!)ccCZsVmO~w6Psh%uJ!9<2)4-Iz`>p$05m#!lmS9*7B3}lV@-DbDgjH^&n5d*rRuOJ8Jx5 zWQkPEXw5;4IsfWorz6o|*Du|2`H`s__O`EkKPsF&4OOk0dA*^gk|W^Zp)S{dqE@Rg zcwj=hs=JYhRc<6V)Z#rrj35TS^<9L}>CFO|qAY06=5muf48=F z1#@7OlgWJqewV*l7KCR0`sbmkT@rz8(U2GV%o0|tLiWf(+)Edcw(|-`SeujKwu@kU zn56}RF@QKk+VSu0-f#5yQW<~kpNj1ovAU0ay`?d6SHPP$zd$HC_gh**Of8X;-N`;6s3wMg}AU|Hr( z#l0A@@*RD%;_P2Z4wgnYH%`u~BDA1kMh+107^&JepF&(p!({C1AceK>2PFXqdn6w7 z?FPg5=F5#g|0XIpdJTAT+0pOJHUumLl_jD5}R%0~= zJ6GdKJ0Gl9qaW-AXfTl22}}&~W)zM>K9saefZ3V#qgqAd!c!N-rC$T#nvcoltatdw;Ms`y&@ z@9>1Le5fKGAF06vtCMW9tA!o4HS|`Z$RjA0qwP88iGc?;?(0V;KgjNU;jiO>tJegr ztG%OJJ4%8sHiWS*?qD**H26_^?IVp@7KpFTei9F4%1^mdb0z;Gp#fP3j=^tpOx!#Q zBK(#zp1%dAe@%ZxalNTfk`A-_I<4lPX3fSWJX$s$J(Lf zJMrtb*n2aSp=wfVUrmd=P2S8&=fhrDCp?jr6*@397@HjP%JoG}J|E?Hm%mu1;ovEXsf^3}7&h>wq?h1F?V zrNw>sA56WimZ!0R$Tj=s+&z{(1;OFrO00D4oh7Syyf2wLrw*z5CidH@YA= zX$hfy^!8;1t9u?+;%DeV)kt25v}sY%q$+fyD7>1*8g@IVxi5POGwnG2Wsd?Rd0et* zcwKEv)><#vfDA9Njq*F(aXHjD*xEjYQ3JL8f`_*yF5bN5Y_rqdJh50TJdhg`LH%a2 zk~yqOKuE6o{fvHI^hwGK^4E+pS)0*Z(LA@r>)50^qQ;2I?z&fcVQk+AT3-IMwBW=L z(y-UHNG7!uBr&x!tZy8e%Q0y(y|56cQcvrS_bs`~Eh-Mr?DG7ek#|JU0?uUCdeolZ zgmN8%1ttHu-W1N`S8?ir5?k-hX zvSdG(x^v6C*SB|g&U<~gF}IymJU2{}+sDh(-+(wz!%UV_a`(-VnEi7iI_E|Y{2M&G z#^;V$^^$0~4xRC6y=WnD>UZUvMHKq1J%3F0y>lG@+JdIJ0tr@}4ocgJjW27PH)40S z&&#r*VE**V*28a&O=O|Q&Zfe5W}6F!1}B7j^oj#xePl|MqGfQ3@AD5YAjW_WA(6T(8eS478OWA1$TO0dJxL9KmF&s5_S;N z_SKDK5d_5+ew!k0G`di|pr;QDB81<{v&W9;4BB*_wi-prO{%|^(NR9D8m2V&)tN&K zP<^D4!s28%zM{-vkRqbw)rC^ci54nPjf3Sd#{tgQuY>q(+AePXXBIm%u@HSq{7srd zY2M9}kS8=?usWdkNG;KMx@U4}*H)Wzz)O+ybVb;YChG9bqx&xWR$QE~K#2&VkecxJ znRK4)8T+hUe6$-n-|jSEg?e|+Nixcqb+(nzAhrIpI6)gH)4~=~I(tU}e&`>ZANf#o zRs$Xs5p%GCkMZewsC|Q=)esAvoMlTbCmiLYotcU%@7uXDyg!xJ3&9Us9QcRkm&6Go zwjLVai74x%l3R@4h5m=ucq6CP^r2q92548H0%qp(MK9v`Zi2`O}6H4^_I>B9v zdL)BV+ZP3Obb#d~?-vVMF$2^8&jw)q7jFC?8-Nb{*}rrl&YF4w9L3K-rZRvSer6Ea zPY)vfX+ZoxBlw~Rubv05Gg4{6D{SCzY#`?UA47m2{G0!O$bUhQ3BU+m69B)@g8%XF zeJ<*GtRS`-_K1DttM5bBxuD~_1?8OY>GhP54H<*8d`3@o|NBuP2j0WT`a-@1@0`ZD zPV7B5@(=2I>eE;44!``jsqen0mG)77oW}M@%nB{xyAcKXl}nwn5gYzT`(7*Lg>Brx z(DX(`;9oo+KA)~ZZ{1XS=Hwc5QKe7W$?#uX5z;F=S$t}ZbRjj|`k{C77q znk9&XQct9vnJ)9`9nn_fq~p%lUH&2K=3>XjHCvz0B_*YF{&Mny0SM@>iz9tKxATNa z-Y;u@HSG8+?68^NDEE4=m(=vFcPnU~jU+CH7THSw z>enY3fwi^%8r-B@wT){xWmnfGvffVfx@?yBs#4i|waJBCTJ ztB*!Gw9+9t`wvX znXdi?`?*)O2s)7wwzYeE#T3@7$@Kd?95y2F>2CmsOyGF^O|4TYWXheu8U2{{nM=^o z0-D)*ZInseaMXuE+dD1~+IZvX{#V5V1knPF?c^ImqrN>sC{($fCC)=>MLo&cmO|9F zLY*$ahm+o#wF##?GA(PspmVYPYnNJPN$-d2TL!Jm=Wao;P5CW(oahM8B+~z4{#ct&N(QD;YR0)T--G;j%Z+~b# zPtrP2cJB7b*p{ul2zwXq-?_b0rhO(M z7x^%fZ^7rL`qt8YFD(mW{76>B+D0}0;ltnBj0v3sw*6~w6=;@?y|b(qd%9z*y^enb z_z{AKGYzGrnKHTDJNoPn(|$ z&zoiY*iBcL={8u@?%H5Bh!hI8hBE2hY3vA2wkDpyYhj5l#;=4}V;yRIcS4VyL?rZM zHs0YdS=#8QbDG9^Yq8h!{hFXEJ3V*s9T$5u8iLzn9__5-}tsZ z%pENE9zGOG#azThU5$iUBNrjfk=_&n%hObiRIxPuxQ#5nF}S+;AlGT$Z-Q$Nx`0!NL%Z*0UK7uJb82Th_I~uIIi?heaLl$Zp4V-= z`dPpk(JP=$nB2>oV9_Gm>~|BNa%~(kPJFj|;}A{VPZ^^e=yzje*80BhYYUl282Lt9 z!?A)s1rumYt!1VEKnpRfe5_+jA-km#!=lS;B0Hr(LNWKsxi2fja}FX(i4IC#Km@co zH|rB)OrBRgr6{+g!Yd^&aCRVk7KQY8Q3M9#(7M5QwfdZSHd(r2E8(y)h24foJq_yp zN*)w1Y*ojhuqr{8w}Ksb1JEL#@=*GF5w~t>owipL0&JhO-_vt=UbfUutx(~rkTLnN zW+Z2&qQZVG7QzIGtzOgV*ap&l>eBWb&eu62+##nl0JOvYkM)Vbdp6or@0QDvC3&Cj zv(lwUrVH79oLo&ycubGb|ECjX(bFFOn=n6Y6rpc}OKKuMx0O1OCUu=VsR|`nbwO~m zgSgv^F1hXa9xX)~cw4l$I#rYB+&G;2*Sf2Pl8T-o!z(>hmp_TxcgqE;DCICLuWkHn zsW_h2g6F(mb1~iM9CS1S5@%HsKbEGrt11BD5!;WR*562_BCVJYWczEeD=L~8p~nwq zS_4P!9nS!sz3YfT)Q47b3z~Lv#T^qtfDRW$c+C2het|$Jy2+7}`x4J95~#Eh-^$P0 zovY*%HP}rOvo-CageTt=@4pRH^^f%tt@oQzpudMi6h5w5hSd48h;jqJA0nwW_H*yA^vmd3Np`Zh5^? zEgNjWM9!8R3?Eo;wZ`2+Ny*9Sd>!`8_Tk7J_G}YS1d>HQJZ-}Ai) zR)i2!GF=MmUFD45|W=Fj-xfEfmYHn7VA z!WFP{9@F=(zbZ|YC?;rpuC1E;{HJU_X8S+CyrhupPPWs=vYl$*(@ezw$G6fs2{x}j z7;stnqp2(~u#BfrM*oDN4*pnAO=+sN%e!})?KJ&8JkUA%{u5BS{iZa1@Qn2Sf9<@g zsDt;)x;cZ}<&R<=r4D;kIrU;9W|^bl9$K_&?H;M6{uIT;?m(%{9{<(8_%&k2`nC-c z20?U$nbx?S_0AHj;i#J{Sf-|1U6TlAA1DG4mz;x zG`DDdsQS^-p5x_67iT7|F*ViK77o$U{ssU{aBnf?O~2R z=kJlTENy6^-tgU%HG?Uy0V7$tm8lSu#etalTs1)@47_UTk|)sFUGFT?u4-?Hta#z# zPmMhxp5RrlToF+I?zAjbdBair4rS6sJ?B8zAGBvkoBTmZ7WaOdrF=iCqnW8?+N*a& z{oZczgco9BzjwFnJ~{w$42b0zC(wEVtLUb$KKx-y0OB*51@tzwzsM}Q^I+NwuBTbF zj^?jM20xdt3_HO$i{~;-OVL>cLuXiqX6DrptkA|f@jZ<@27oxfgPTt63AvhEo|Fkod7x&{pxW8i< z{*SO(6&$Qk4eLb0Kf{QvGce`ch~p7{-WK8#e)XUu3y0y;g+Skam-xMt`WATRTK#gW zvB{7_XChCrE8W-sl&0#vt1`mA7+F#QFxiT*UG z(3#XU>x3R_wpu~b!SL^&cv))X$gP!tqu|EQ6eDZB)f2Fb8G)l+!h7vTBOGl^`&c>+ z+g5eGFV(^+#xYOcWJSdhr3#Poy&CgL`5U=vU52U23aDS3CdW;c=f?g#jxI)q*JsCb zkGZk=5eTv1jAiqMD1R{^lS~XJeL*YAtB}cUF#fSwPJrvjL@}DeakY!VE}dcIoNtWL z&HGBHe^~0p>G90=b@zKEeO^G$Iw~=|pnVh{W6vmdacW+L1+Yf5Ls^i=Nr%gg29H}- zy2t|gleJYvKU6FBpaNCa(AU>y=bQOnf1GnM*L~3_@=p|q4``lPdWF_EzKcmEOT@=( zi*o~ZKB2=O1km2SM}+Qg6P!T;g1sx<3<^u;gEyrT%K7L!(=c{(MxX5r$ui5vuVUF3 z^S`viY%KLW9LL*qd)21YYhOY8-jr#cQoP;W`G(VOPguF%TN zq)fnvQcmzxlkbB)wM0`wMAw!>urGuB1(p!E6X=hSQQ#ubhuc(Oa?1xRq1PGdHKHp_e zuqLLd8V^iX?9IJqmAR@KG;MfCwvu0@>e@?@fBE0x_YtP_5U=;&SufhMk%!tFmkjt1 zenwXDOZ_VCrenivq~E^w@1a{M z_D0CL)p~np7yQcbXJHQ#u9ijYBXg$*Nd%MnzZzP<&dutS#jY?#~AU}Xz&;w8Q# z$2i)j!0Tl`2}=tu6QgqVEG8Nnh#O^%N#_lq!z7a8m{tb3GN*$BeLxmumB!~k_zS#j zwI@=(^dDCf`x_>0E^L3>dSPO7$KAG!lOs(;9P;d+CY<_=@kDgJiT75;2I9%!iJA$YKRgR z3^#(+aKCWzR8;g+^t8uYbBbju>lw3%kHhAG&PRPa^7Ds7ae__^w5iwmb{_onD#Xmx zdEk%T+X#H?Nxs=@iC*i6`IXu=*-w5#zUs~`eS4|I3BBupin-SF{3cE3AwR%pr(B5Q z&@awrzZusBa?E2$pdE$llY{MP%6MG`0f_!@?Q8l-F`qc^e?yHHcN%A|+6G$m)P|30 zzntT<+_!kQ|MIHJlb1eP2F2~&4$~>c9;TXC>(7EnN$_g{9Zn|ShDZ*_C)p6_OA+a!@U>b?4Gw)*=qaH5c5H9{e9QH1~QL8i;w zg4mdg!GYzz@Mu#+K66hN0BA+KHks@lKkn$>zdY#n-@)nEpD}V zArdAWhh7WMZsg+LPAF_2AhN51AzgZRFazaP(|;jIaFRyKUmmKDuvuEgCyTKnm2)`H z$JXyp9&FX3_A~Y+0g6na8AJqW7`mzI6s04L5W5&%<=^-mZ0aJ9cfMJS7@Ev$aSKy4 zHY^|hE!zA^V!0Au*!}&<;te=NoS(Jd%jw=ZK{nuDdE`ys(x`FPjlk>U4?^7PeP4Ax zt~~OxpZ-r5-;SRk)R6pFUtk)Hyo!%To=zlotRqQ@|VXlFOx%O z@vOh53Wia#SXJBT6PRkeD41j;(6#qVZcy zY4qCWSBIbT9N#Fb`$}pCWI;^YdBF6!&j%@>3m~5X7TLDHf@^bRI)CyAe2)jP*O6(p ztBT2jQgWL4bG5lMd|H0DcOPcvAsC`wrg^GNOBQ@((g*%GX)(Zj3kn-|=es-cr$FVm zNllm)OV25`{}bW!{^y3O(Ms6w6rRPc;??h~(Gz*?M@5){y&{J#t?7$J0wYiLkl-G? zXZHC^MmSgl`w%d=<`4Ud?r}_Oko#NS*ipkeujYSbv^Q}uwat>NyVE{7Vi*gqCl>9O zKHj+fboPf_3%1ucGZcOtg%8HrVzKEf0SL2MEl$l7lhtbTn>r?ap6E!OIc4wd^_SG5X}4MagShV%ivMYr{$PWks;rIVH6*5s-a z!OMYs0Q5R^z-0Ao`~p+-_r<1DV-1=oOjtcRa>C<{Nv9o~myu89^Ng9mMWO$SGDm4+ zwiSJg^%<(icWymtl@Nf{_#1X)MDn%c4!#?F{)p`BKAdgYNksrU-S|KT}-CQH`hG;#A3lLaR1no?+$#`kb`$a@rq7FG^O^PRkQeY zeVSvaS5=WU9iX!%V#9Ens3&WF<89FC4TSG87)aZsQ>oQDFUGFzvVoxHy2MNRD2Qi2 zH{26IYGRKmd?j)A&873KLb7Wbn{29G4RNzRz4S4XSuPn~)uNJuH~*Q4NkQ^qOCe|V zk#Y=XfqFExk<_mO8(N!;KgJahgMYRBrb2NU5=2T!s{VQImA(x~!wNE%Iv0^ZqjZb> zc-__gtEh8*kx2*Vwry75kaTZW4UeWAYRxzr{WIhir15|h@umEh*khG8NF!FVtF$y> zKc+yz9TCwJ-flQXn!<=CO2R;scZ5-O(%k-4tBZ(~;Q;QgTa$L4wshX(OAb713o%(g zz-d>oI^|>)KE5A#6eQHA`3VQM0ytPyfFmX{6_MMe>dUbpf0}PZS>cV>DMuftwEXVPzyZX$n;fI%5a@>#Oy5Tj)YmK zsrsRoQDX0Fj^7tI_f7+%$TvKu3mnXxAO}xq2qfY7d}2{IyKo0zaE?G)a8Rb`kM>ua z>)Vo(-$$pKa$6d(+p(4MI+o9at#uskE5z|~0O~3Ql^j5JjWNTqdJbLg31yK3L7-o4 zdG~221!NonSsKwZdZbccz?Dfz6iCi}!f;S&@r3p&jkzI%OV|Fj96l%5tGU){nl6TX zLuB#JaR@`PVAQUL!w~H7jA!#jEXkNRD(OwXS;<)P|96>_M#j?qJ(I4U&2xhFVVC_W zGc}D3vBx&H_E*y9s7Y{H-#k5ce~d>`Zt*LA+{Oo?_&~MV9>)STtH(YSrS{t^lBpU| zyi-C{5;b6%80e9yCJQe)jpZiC{38)%=m5t>^#DCg2ry@(Q6csYM(Whrj z#_vQg)C?~9@T-oUZjE7X44+)b;ifJk?hGH!g5Z_mbPAL<`Unov&noUwUmw=uI(_;0 zn4LI|nG@JpfHL8uW^uqaj%(m-GEE(s7C^A-rX|1`=xkp?1Rjks7Al&gDf;7>O(kza zpqj{&+T#no6`4?uj?Trom;{-BlZ8l)$d7}n4j*1HGlzMRhHdOiu62tcz_WoE+8s1| zNBiW68tW3u4Oe|5tuoiYd*|2hhhi%`TvL21q-^a#W35uuDDH@?=aw-Z9qub{lo5j} zPqy1XI0!+pqjOjCQ?+S{HAHC{)g6?!9Jtt(FBg*(Rd5j5T!cT~z}}zERiFp@_Xq?9 z_uhH#q7mkhqlMzijR;iJm32RQRTl-q2Ar&>Hg)o-1cJaO_pt&xCVH2GQ`E-jb!hw2 ziAVmZQ!B|=6`xiyNbbmm&mi$A5`-;_=>IYNg{s+2yxIMq_q@Sm7gSz@!GeGOVL!x) z?|AOkuCk5|YrZD78Rs`g&ATF9 zVxT-k)W->@*Rt{XoOcrQ9F?VSCo)^MzAbGCm?JKATyN^cF~X_)R_$XuUxEavSO*?; z+!j2Tn2`rbA%7&paLM@vY^yLNJJ+wS6GG@7?WAXu6yn zY0jw%0CkSkz&G+^z}_Qpa~)QUdfwkSs;rs)LS$bmC9hed2z{Sxdqe)ooE~0zz_mI| zACLrNieSQNPde2CfkQ!5AN*NVV@_NgmTB z*BbMBGF&4#u5S)NO-k=%vC^zcTDkWYw8#nuA8Sqmhs~K8NI%Y)XlzuUaUp*f%Iyz3 z;3Oi^Md^ zTc~B~=gtGmmJG|xq9GNMjWAxV{jq}z^6w$uU0u+B{7F#lW!+v%klM_7;5qkp-yBK? z{_u*6=I{-Zsgb|C!&47pm-2HM%0ht~q}0Dxv^1GY5c5yh!-r*@?sD)CUdVuJ#>|a> zI9mC!WLwZ@zM)DG3QklNuyI2qDS0{{G*)d%{VUNgu~|t^zFWpT**hEcd*Hq?N-xCQOXmBlm1jIYqQV1k+NUi>g)w&k z2)OH8*qi0mUY8+{VP?9515I||wXmdwba?pDc17t@ z=Q{Sh=kTTK{T^b#-!)JHt#te|-x>p6?WocGdHY&2tE2M3t=7jy?D!KRY9n%=B3WNQ z(vVbZyK3E9P#E&uZ8rSN^QA(*ItHB$-Kw}RA(l?H;>hMcOv!);^;q9lP?=CQf-Y-B zh_eFO%25ZKXJyK_+Op&U1s&wk3vHoBP_w#&b}0(vR?!{X*wex=bmJc4*WMem)-3%C zl{`x+7xfG*EN({&aNL6?OM7*l7ZzmH;@2Vv&H45}ok1}4EbJ;GE@jJy%^u$T@pm?( zaP%2UrRZm<`fmuqOfumS9pf1g0|AQB3P3M9*cwYo`{z=?zbMy`V3P8H+0s82Ni-wCZ77dd8EKWc5HFG2WSHD%%jtIo>_rfwZrpF^RL%ohMxc0F6RU_RANQ4! z?W@NsnHZ+OcNlnWWPW)CkLw@KRGL2T>iV~62ZQ;8H>ysti`Kg4CaqO{iFg90!``>3 zxNT6#7B{*6hQVeHA0oK3HvHB`;Es>mm-oAcP?A;zIkHS5MSC>H@mJ#zd_r}}XM}v@ z=o7exPdL4A(hiVlbPCbh=NhD)kF8U8w` zM%_^k`B3`NRPb6AM4@A)B8sPTZ?lccjcS|CdEo{mNYO9GO)e@rK)7M^$wJ%MyYvgf z_c+(}QFa^8IfQjTDba%KESu7}mR13pI1?jtmV*6WGipTMiiswX7nQ?lxV zgzUFAsBxPW>t&TgA6CtJAGck_dNwp?Y|l-xbOGl^{k&DIXEv@z=b!r(8V z*es;-8Dt%>Gr}BW-#976y3%$oPmv9-Io)&L)snyS+JPmkC*5V;qv}C&nH~&m6F@sW z*l#oChxx~e{h=?@^j<9HwPAiCyDn2r`D)e1Tpz*=NTGjwLuJ<_C8^^!i3;U7V;)6_|V8%>R748#@tNaGyU`6ZLi>O~7;YdMS?FsXvxVd!AOaM0=0 zUFr}9I1jASk{Dwc1q&2GA5cU(_HS{jg#l`fg_$FRaFhp14>tM)UwMDePVsQOK3A?} zLj3Nfe=B-WbZ|P(r1XxFL;Zd97lHB%K{P|f8AehZan}f~b1U$nx;g4F z0e|F@xRbuvgDK!Na3c?)PdhROedIlkkYL;)czEyclqPA8Qw0mkBU7(d_m=>&6SjZc zSB#X+eX-Pv$ddTZRr|Rhe!>iXeSyA;P-Z;X_ZU=Gx!{~p{grmkza7LS%Y@`CZNAWC z_w#**FhXIP1sGs2xpKOflDae(Z!+vIUW0@sc(|TSg6FPv`LZFd=tuJ0H39wF^54(T z-5Gbm6-RP@mavF|U1aEPaKGh!3S)#h+EcHRbV@V2YFg41{we>fQ&UZ}KKrRvU?fj- zfM@$yfL_Mf$N>0n5vzN5gg^9Lxu*Ww!$8lRt9fcQv)W!w`hK9u?4z~r zx48S3(>p|JwvLKJ0{PIYyPJD;aGLZzGB>}6n-LJMDQ2gh0%L1>VHU0Ho*;{c*L^hO z;#Vre<)8EbjZAb+kJ9*?CsR<@oA+XPCq`&aI5c#A$K8m&Mk{m`h&0kdRsWQ|Ej^3S zDQ;@($v=_{G)KGXX`Bu=;2ah-ERu3+G0M9{qXQ1xz-xxp9B0JObJ<2Yg79yY7HnJH z_6YlyVsn)d4`cq8erFFV*vCJ-zT%)LLv>}KW%IzU;5~G{>6?<41HwJkOpxmE*#}v@ zHq^HrxvEfs7^$AvJtWUj{7Xogv!20f_gw`3&_BS%Ny)ROEFaY!`3xsD&nyM%sy(dO zyf%GKMm1qg0tk~IhT;@w5B8t;!6Cdn{nM1W5a14sQc`JPA8!PdO9=WZ%Dry9cDJ{# zAStG_&XE>&lPpRL2%lNaU6#dqJmFk;ZOAYM;xAz3pK&)&9?&OoCPuFU)5df|$u9zWVu(9MYcsX3RhjSU{>dc(%Matilqb)XY#4vL z{(bguvHe{C)8Bp3!E@m%7BfCh2uY9i6kT&lh%a@r8!;v7oC~JM_SY9F>m5w6Zi8n^ zd12^xsB}{1_7VYF-3~fq`eTLxNk zPPN+=X|x95(FeoI!<70$h(GT>7y?7D9A^FzurVoDxd#^X*5Mzpky9ip33rj^J>>9b ze$<3{8BOXBu2W?vL@SGaNQch8Hb7I{X;=t3qoc^gjjlE&4&83IwX9sG3TIMl5JoQYL?jqO*_BDQdYK=D-Dn7qnviejR z>B$WPXgIY?8m6i9Ev?k9DTFL3nI27cn9F%hwACF8>y7~JX>**SaY72S zlPpt1ELnd8vgZ@pak2zt6yiR?HD~D{5_zphqme!T6Enc17K0m8U|rU6+Fb$)Vrc#- zxNGz;PRCkUoF8N;ju{`(7PPA{Yk|;u)Aup}XXtvT8|-9+JM0RgS?3!s`5Uhb2*RS7 z0gMJB5!v}gc=m81I6NJC){y(tS0~GwT?}pE+`Ids>#3VmR-+0Vm49&!@LI1zJuOQ^ zQWcmMrxX1iCCxop34q{ zMEsBqDhm0|FN}Ipl=~#sJqdMJ=5Qzi{&!9zGNdAo*n*r}O}AF;?&SyE4Bi%yR>(t` z)hGG&)E$ES_Sw}{?xhov%M-Sm@QlhvM4mKlPutW9l^}#1{E$EWg>7cx$V!oQly$y- zNn7G1gd3;OL_jiR{{ z=CWIQU=W@y2;`I|H@y&{0N|+la{a0Tt*pB#>*eG=XDVkP{ZLH|F6&7`>F>Nj`Zg)i z3ti8?rvw;q(BC`rFXX+D6?x9#Kn%06*^ZBEeo5%-j3bx`C|O;I$h6&=w6>kWlNAJB z_k7f#SdfdX2NOuc81}fb<3Q&gVuq3HrHKc@cM^Sr%UfH@RJJDW>n&|ZILJW>wG@hl zUh=XA$5-uKqYLjC9yk*`b_#3KFH)jjqQ)qq83`#d@zc6oz_lsNQaGv{Dlqv2I0$zXrp)-Y5y*yDj+q^;Y|my!V(0G-CI z%W0o)iCxwyAY*(!q94#GkUhZ*`#0MrB0n0szFk&POyc6B03Q3evHXpZl=_pWr29ON|4+zx)lw4cktS z!JBHnK0lESd_bvt-R~TF?)mgVcmTF%D)?w)E4Uc8*p)99va!`WdVVrDZ0&b7mYHor z%g+sJV`+u-vP?J4cvR<@8MdCO#XwQ`*qfISeD3#8)}b(uUSqR)V;{fOi~)lDR@M}x z>D^_{C>v?+-XDWyjBCN9lLK&Yw8m`mg`|n*)Ab4`K#pRbS~tD|bbOqJUaiuK(N*={ zp#feyJVOqH4W!|V*d`q`ls8xqsJ&fZr>!r@D!)+8mA z-Jyb5XTbuP9s~zpRmHullrmgH(XBY#`e+>27llV*T7ynse>mi{nqq?tidos5G`t$C zHZ#^+wY|1c6onD15Oq{A=`ZE5ozhPITjV{^>KE&FzeLG8p;c2e(ykJj_%ma=UU|4V zuMe8kxV_bx)y31Ck{(u+#x|PtvFfgj4=GYYMm_2 z80))Hrqq=5BtiASL-Q0Z@JQ)ya;GVvGk(PwU+KMGb~BpgCahfg15IRyAm3@2EFb6? zA>GGyV9EU0L=$BG04+a4XQm@h0hy_rpsrvKwu^yA6u>;j9R0oeRJ_WHA9|W2gPoAq zz^88Dd6kyH+Wg8%y!Nwg|5p=MU7CykmM$-$5oF!-(72R-kEOj{)eNj+?}eV7-EmwdXJI0NiIX8~2?-z9axH|}MQw7^Z~ZFcvFDxQZV@L9pUJj~0E zXH0Bl$#UFNj1X-ZQycR+R2UW%O?P@TRby~R_@8Fg88m?*l4;}ev{R40?|${wy{<-# zMCHsk((g*w$K>DNCQ=0Kq)ei2eGt&TMpnZOZI7%3M-1@e71hH|RHAlNL07?%`d)Gd z#5r^enV0`ZD)*sL$d-SiuBx_}GoC4k|Se?*VWHzN*vPC)>! zp+(ByFS3vIFufeo)}vjfuX5Jr8m6A~XR;ybfTRF3Elgy3`7hkqJ+ASJe@z2?vJhGv zGvBlC?O3Wvah7D&T5QR{gx)0XlOk6pLX^v@ z#=8T(er7~IK&J(HW`DpIlB!deJlDfi@gXtglnzHy($V?+c>B3W>^siFm?j0~t@(zKP0QD7jMEtbNGJD5BPPrd!BCJ z=qbE^y(CodB*|}6hxo{ecX&56Ul1|9-0hTye4dDRDAaY$qiL=UUaa@XZy##ZL<}P1 zA4yk@0n1yGo(EOS^c(k+P6PIC`&B}KPF8`l+AH)db-wVhFg&mNH?Y&#Y4>G-R!2P| zvIRT4Lkk>5C7TNUy-EoMctE~ns6h7(pck-t`tuTkLJpdm{#4E+GtCTqJ}P;6@Bt#s zGC$3o`xRIiji=?C?RfBi(De3y57qym>HnxKkwDAsdmyr$6V#&T2l3`S)Js%u@b765 zVNMIa!2N{$A1WO}WjdjS50aXKE8H$BDhg_Sj#|LgarTPom-RK>Bhp zIjo|=7+w`v^c}z5t8a=Ai4SOPJp^{D5PNr z9mcdXlwWG1)MehUO}Oird12bu%~gUJ0k?8r+D>{h5`k5XJ4=x5&R9!omR)P|QO8sJ z3i0jCfW_4s?3Ji%$dPq>#Je7jdzpJfPRfYbUtHnaHXa`V)`FA>Y?v6#NLvf{Bnjnz zsOnD(oQU?}47{iB^ug#lJYTRKFe}|*P_n;lJ4XpdUs%bKXP++M?rf(B3{> z(E!nsLUdP~EAKyTiAcQ^)2B}Af8708q^FQE$t)4cbYt;{Z*A^4w`M~&mS)1@-fx|M zKr{dlQ9$2oIqLpo$U7Y-$#+^(TG6M!wB7kqV?+2}>qJ*K$|e7!U1FZ3)4}gckGJB* z02ol?ko?PH(9b$aTjMC=Q@3nTzFCgn1W}vq4C|VTY6(7ynH!hEkX2<6a3R8AuaWT; zT9~lf{Z#WSz}DMNwGGiG$s{-onnLY_aEG~!u@3wwM*XhMNo;k!+;DKDv@`A$!4w(> zb>71f!n`8$gn&UCvNVvaE8bSFs%iv=X2N{)2P}k2uk@P&3`ziuk=ap<1q=rzpi9jL zugdqD1(YT~J?gDFgr9+HAP%96Vws+%(NFgZqv}fJF7P`AU*OlAvRa@ScAgbA?1p^l zQwxy&Ut_T3vS|lf8#=s5tl!2)xi;B9$F2*hvTaRf++0q1E7aerm(i@uN0S5azaN5<; zF(fqf0iw5+VU910Y-Cs*TEv*D~PZjMEAMK*nfPyi@SPZ`xHh5Jb&S;A_ z(5ShQG9^^!gAa+n5pnqOWW9p?!(R&u4lo>^?0DP=?Tksj4*jSiht3{6N;}d{lLO`q zTmKb59JN3zcq|CiXk(ckxY~l(@_0;*;R*ko{YTD!kv>=$oJw+$ulx}lc0L*C+YMIM zE-U`y<9H=lWbaRe?VEYi^=+++=@%XTZa2LDYl$;_j}&lb(E4fpyL^d1ZO(`0q$r&r zHgfh*eiPey#DsEYkQHqwlJYsp?u1fKu;D=x5$5b`(hDlgHNJ=qv~x?k#yg4%ekSqRWut1RQ*N0UN+i0>)RakMn?^`kOK9a$%{(1T zHZmpPIhVsF(A6IJz!MQ*9rsHJ9Wm<%a_UF}*j2c_%){_Z`H1`XXu-%t%dDowV9$4} zv#aziA3bmzXne&rXLb}vk+6@3ocYrYeg`dV+my)cjMr7m?%3xUt8{p`A;8EhZNq9ya;iFWSof!dryWy)HXQdf>vJqDA|jB(0I z%8{1r>znD+8!sNimBL0`9dkn@39Td~88mL;t#dR4Vh#u~=`JQhcAtiu+UqFwaxduV=xLRG z4zK0Z0zO|zWB|1p!Lpd^=a?m%4|S55E_Dn`MW(0K!Xmk;{erIhm`03x?R3G(Flj?ItO@+Eg0jyJGo_l!-I@D-1qwE8kC3&-`=_-vEy4fHMz)Hh*DKuiRs0@G6-EL9||e4acpn2Y*is3LWP~ z1hvVu@xie2>~!&HZ%EuT0g0ba?>C>&+i$h#h@89eFBc)hXaWs#b$4?eF8oDM#|mwR zxGKj-P}-HavPtTA?9%d?1hkaxg9*j^gQN4s4QDKWN1LS{E=QF+3UipkG}j0{4m+B0 zi^%$v*gn4Dn>|m*_xp|_tMiaOQQ6Z;4}i$yb*nvcC0R(B;o*yLgy5$-hy;&`I
    +98nQPtKC_DzO7PyK71GST*8vt@{U$z$KguYZ;>WT?erRjL6)GT4sHY* zzV5!`jGHq?GLR}OquhHQYCE+ho^tj)ZudZEp*{{_%Y|NKh0EQ@g(pxqGfoecK;HWu z4z%_PjIk+kd{9Ea6f?Klxc zov#f&oI1-?Dsu^PO6R`>P4&c~8Ga4_JYO)0D30PE{(S%3xX{r(9S&fax*&M6)Wqr- z*(6oAAGP{qbz>V1meN;#I|VrJQ<{>VE(a$2caF1ijHD(HJoD?E>}&3nNQO7nmG*9W zl=8%uNK%lm`GMX%)BLD=vB`eXB|l`aF5lvlv?*a|nG^E{pWi z;)?kg$f1+GXCC#}sY4C`)zv{=us^RMoCP!K;Kri9N@i=vah<^VH&&q@9db<}KI3UUJ$t@6PWH)%o7vuG zmgtutU4N9Z4@zivT}5Hu`P}s!DtZt=Bn)f^>khr==~O&d88T#zk{8-`n940BB*`1Y z@VR};^9R2RRo&~nA~;X{TIvhm(uKrOAEgR-v-oFeK=I<(oBEx==|9l?m&o4A=F6^oZiMn1$rTqZ zK+xRFDU)l~?2T4fwRYrgkk&n~kxVOp7IH!WUPpFYx#O5R{FZt z$sCOJvF)4mSn(_Bu5Je#HQxhoh17qDFLdkvZiZAnK5lKNZDn&bC|rZLIQysC9nU%> zl?1>T9QQIjtY5t7b75v{l?P9bW~;ap8Ljy~+<5MoPDG5PpIqJ}UH8R+*}aG<8n_g# zQNH(>iPOk?!7?6D`AV03untbl7(OksbDQe+#l});-9eAw)chyB3k_Efw63!2?%>zT z(<7ppFaL7k>UYhHKl{&P97MdHTsuDCbCR1*adF+~*5c~5 zk+7KjIPVf3`VQ7Zx6`xS#}Uo!O3m+3$Ab|waRX~nC*;{mOs?kr zt6}Jdmd$Ogh|nv>QRmwy06@%Xhw2d3f^xQ>)X$CtKEy#VN3*n1I0zI@QhWhw}Rbad3%d31Gh8N~N1k2~c2Y0kG4NcJl)170^C81?{vcoGhy34-?Gl{;1fShOkel+{rIMxkSoy}qEX4VN zI~9Kd{h#7csaM}SozHoDN#fXZV12)w=3Xh=;(tw|ll{`>?lC$Jsvt+cayC*; z*?cWTmO)I<3P_srdwhJDokr^a&%TcOEW3=q=pR;kh7z+!dv7>0mA1cRND?q zq1Dk5cAGPyo~nMJPN8((#m!adHVe>hs#;zC*BZ>3+Y1Y_3O;12e6jY5 zmBOdBY2^}Y=hc*$wYz5n}vT(0d%-QPYiEG%y@Vc z-OGs#ktPCy8lyqyg~S+}ZQ&s^_Jt1#QKXI78zZ8MDU{=Mg3#&=!K&v%^YL2@9}VSP zpH)F(=?SynG>|qZa&)ho)wUo*9%K04A@kXu0h^ zKg|Pl_>)V_iZ_Ia73GCOkOIx06HmA*vJV~r+>cfRGGNMOS7XeUFCGs&Ikx^Blz8yJS;KxJYHj)F<>!=$pRGgE`!Ok!U$|(U z-{v((1jvLmN>O%jcGa~^38Wv#a|iOV51K(LQj@m4uu=G*>(BfqMvXSBGP)Ly*ljjN zk*n6SATUWvYVk<#}!X- zFRuIboLtAp3(CU3O%J0L5#hKR`T1hd7}zbse;4(hoj#G(;=T*mTeqd3p}A`zYEJrr z_xmcU162-YX5Du|;-j4)_Ma?d>7I?Mbo`KOxQt){JANtXPvYkpgj6qm^=|>$7nT-{ z>~Au|QUY)*uktZsstgW~N|~CZjLX`rKDFS)5Xv zXQECT!Cw9*w8K6EU32vHL^#91?uQ~?M((wW=sfK1pn@*XUq8_N`Cye)D|I)>^I=r5 zmNc@!==;q4F$-+Fp?Z!{@9qNb9Icd$7i z^}uTB+i*!AaW?OeAu;Z3Rer=w4iAEEaw{rXC$N(klCSiO{&cclqzN34+ zZn*{a;Y>$q!M5Wz5b*GboecuZTjFY9>@b5M{^evlJv-W>mv@V!Vx5GWG@}V<)1$0+ zV-7qv?dQ198yNgAKY3j#gVf5Ak>~7oF(Du@SQz#?{&Td1xqwO9x#d3#Y9R+}n(N>A zpz9GuRTd3j|7xnoI7+syP1b7gbZ6P&<@fcm1Pr|n=_598G{q;uVUA2ZKGE9Q90-nR z%p$aS45#o-X7M=WqOka_5a2d8!;isXKcuO7O`*>z6Y^;x-^l7s_0MIr%S4+d)gaJU ze&s_x0}vbAkEjRHinFD>1%=>Mb~GRvOz`zgVuj1NTSdY*t?fy}&)IZ$JI?VjE?}@v z^&n;}^!)i>$Lu;v&k@(K_u>3teeY7bZ26zSee>*2zG=j`Nx|<^Xor;H>jm;88c%}; z>k>sV#s3}z*p_w6sLGT*??5}D%j%Hoo?(G1+Ay$Iv8f`6Ev&ChJ9*^%_6!f-{@(Ru zoiOhaVT9a%k!0tItLVv?ZF9o8rrvG;HN7%r)F*&h%Pbvc^V)J~)Aq_d$Qx)P=poGW z8kMqpaN0c(?n-3*;v(WAF8axzr-)3*EV2Qmq5M*F4E5OH_#G6g##MqFcI5{8zYRP| zzGndnoTf#~g@9#DvISy>+5ll%(SwctRGzW{#MA?AEi5RHT<}M!A`N=?br8~Xnmzha zv_Sysi22M9hWBS4c0&OzTg*$MB!)9@6b{!l@DEw)N%|>U`LWUT(!y484{xoQWqp4a z*uG#3r%5zh$exVO$An<3g?RExb5L&`Ci+V?Yy4~c3D~8iK~3STP~H>UDyg&cNo)C2 z4Z~S+d5=<7#oL_CT<>kj4m6X1 z{`F7|Wn~3weFZqs6d1REa}5A^41e8};+ws*lJEN5n!T6_RxZQ(o=ae!{>{M?EOVsV zZo^9sEo02j70GAf7iK3IPmL_2{*KM;Jf!y$`kd(}ekpO^F0^k(A=rRr@xRNrU*1L! zokEDLlb=L1XFRxX+5A-RhbH<+CFgkH0j4P()cm@`P<=|0ZPE)7NFkY1*S5c9FPZPJ zp`BYBj`4UU*0~3Rmgn$}c%og<>M{K9w~IJ}ywOlyoVt9o(i)?JX4R(QY^SQqss`Rr zupO?E$SdzaW6$<%v@=mpOOJMU67Kg1KY~QUTkD7O>$m|1wVY}t9l^jKRLMfq(=c#V zX-__=jtlGH22eERKSpkXU6yfHm^;C`$xYfuF8d+_!2Ds%jTR_Qc7yL(!6Cv7GS)*w z5SY$GC<_p2jM&aENsipPfjNpnxCrFrJuOLsl|$j`Z9D7S-h-nxJ(YVldRmbZ2u*-Ve@<{bYmq% zf4}NVzLZyW^OO?0rxZ2S7?xcc5*e&9j7E(KVf(IU>*7j=$Mn+}MhijE)b~iV-~BKz zBm3q@3bAhOne{y(60a~W9kAEw=jp9zOG_&lKW{YJ<&h`>toN*!&%qb~i?r3}#Wv#6 z=N9Cr&jKBZH4v@a4gMJqBZH(Get-dOjMkZ;7tP@PQVOH{0i}UWmVPdcr?CUYqz)v} z%(2yLF@N^Djq{%bb({}&_dMG~6E+J@DBn|An4if0cN3hcXCcIQy{Y*AG7lrQzV)>A ztHQ9~rapNUgLZ^|WUsb5Tu+HPOoD6vdQ|;I04#2AHfwWKZB~5|pB=uSVtV;Yam}5A zuWZE$`V+(XiJ$h9rrQ}62OISS^#)G|36&J^P(P-PPp#`*7xCll48RVmsn+DQV9R2f z9HK_h3Zr)s{O!$KSS~UY|NiG2ATy@_7;OrT3|0Mfq$cL)lE8Ls#-H`e_p=1OPL9r& z(8A#`VwZ)3^tMZ?ph;dl$dUjzoq$mj4E#q2sa)`}(e`xOzQn|M5Y_=Ly;Q0ZdDKAi zKN(xr%6XJ|{NPiGO$PaS5?IjXD&6Uf=zDwp`c9j|t5X7g5``qsF%SM~$J#H|`0q9$ zMhBMm{iB9YC;`JmwZ_$9ky_INYbs&&rN=FVws%>Y1b!8j9q&s5$L|j7&O_)Ij@lxR zDBCzT=X@?9h{|X6*9JPtM%#$gd)z?55W$o9iVj9+X?b;Rz-WsakV4}Cp93{_0Glz_ z^K_9H42xV((eI8v+**J@%iJWG?KBZ1`DY)vwl5sg8Ko#UM3#E?h&DwN&w7Vk80Bow zaQ8F1dNq~ff2)CXu5_s2kR?V-Ex<%~+A+fGQ>fyb6bz0}50vK1{S@yOn{T6c?1DPp zLnQZ5w(%P6VTt-s={5JYYf%Dnm-F4xjpYH~bGHY#{sw--=6i`s*YXe+D1leLWLhX!VX617e6K}r;(t92MG~5&J)bntTQE3m%K=+$@y17ETuvx zBAL!apeb$+n~Rbmfa!ze)#|@UA=R%hSuZ+XNh%~@x3eL@qgxo5BWiD=6Ls$W_sxFV zg1d$xG-i5`{vO$qFEjwtwvE?6wL#3!zXJDf+bka*a)^&CKPMSU(RNLo-<>f&`q1Pu zdhz&=W?+m}5#T7w^iN3}98<0s)`|w|8GJMvj~hoL z1V6HT5*j<92SQ^iKWpSQ=l77yqAXgq1vdg}1yA;mW|xYHXL{yS);w?{lQn6r^`LO* zTwP@QqujpEhy;f%vR9S`{yV$}L8Wk9^MrjrAmpOW{NN#MFxB^Gz~!|mIt;chUa&&L zfzS$n?=~7X=ukz=TA&PR)@EEhNgWfTrFq`yn&_M_|kvjDRW_GqL)F2*^ z0v7Ht&;nnft~A#{DiqWEsIXux&8Q#;MFaEmm;ZgiK%~^oXG6~vj8D(g3r|g^yv8Z2YTf6)$pxXevJWF`zYReRTBZ`)( zM)bRA{qap25#6V-KO&$;HgL|{@IO-Iu|RFYeR@;rbK-qVhR19JfGOYEq{7#r5n33} z%M2{;_}<;K;OPCjcnnkWYto`j z7u}cHiTKEV@(utpugbhafqY?5Tu|MI#lk22@e}KC0i>>YCVr+55iRvZ#!e3fgW(j-LhtHOQgya6P6>`f{@2?7`-`b8`Tia%~ zNgJIT_rf%O2|A>o?C(=aHh9aM1ASbbb_ptL!76Nc{6uw^7GYZb^yiI!i6r78+D?=d z=vlPm0m-_`^U(zH%LJ*5bZq=PI)Q(0SAlTJe(D4~!ab;?DDztHDHs#$RVP5(pa*zM zQQD45oU&FBjsFGUo>tj$GUqdQ$6#UW{Z4(+uTXKw;xzK&$6GHy8(f`P5QZJ|zJ?*q z;bt_WBWAo4K3PgU^b{9c2!P;6e5-yvpT%iv8g0KTh|~=lMO!XljFSy*f$mf@LGC^&0VvqhS2e7w^( z>>3IS{v!V%4H!KLuOE>iv9I}Mm0|93j-wP#66?-0z&(Wm=SxkaPC6HQ43Sz9$nO{x zXQ3#Mdox$92y={JngIZ8POyq;X6HD~q+;5r@4%X<<;-nM`=8K=mzwJ=>dZiI!!^%| zqP$-dapW&bo**9N6zZ4;ZpVViuHKq(mF}B>XFkC#B7dCf;8SUp=K02B(laR@OZ%@4 zV4t2=FyCeMl;{y^SjxxPM)b_Cc}$UhYm4BY2ffEpd+^%T{0=)9493^yao<*yYd6o@ zYDxJoQtI3$*679A{1WG1>%&=LXYw?zH==&)-76&uoI9SICWuQMm6u674SzwaGAp*f zjVXTsPf*fSoF9;WE;e2Ay*x?iQ0%a_P-d+E{zl+R5>sWpCvyQLwplw1_CyW8+djg; z48TlHP5IAsT$36&+D>*sSc=oflpk3~-{M|EemFT5qv&m&ts?-E(_W{-ueV=0TTuKU z2>+zx_K%18j~IGEd5z-{ZyFI((^s~;QpoW``;PNXSEElafKm#jvUzf*tQR5wv=nqI zqaLS+rzck3_Dj{6(FmXx)-qg6`6lWoB^UYLd$)5u zgqi1idpIz_Qv8iv1z%N|b8UgL%BMuY{VGfk$fYEbj^z_f^m+lzH$JM_YcrG zJjBgQ;^VnLvNH1TX``yd`YPcUv*@!hlc8Z@B3Db%S8r1ZxwX-<>yc;azJ(NjkP`E| zOsmsMX<(;ixl>qk@j($E#IbFLjgC73q=#BrB?E7>IvT-Afnm9q>q3^S)lA)wisuIa zOdkR&tYjs9_3amJ)wH<6-S!jb&A4z1{etPR?$&*j_-8}byZoC)t^o1|o zkmU2IDwsU66)BW&YPgQwVc+CxjC^ECTCR=M^0u6C(L$m(yLxz(y|6;@&_q?}BR2|9U@c!~t?yY<5C*)LjzcNNg&|ppchJ@(Z zL_kOvOl&>jyBsA&D_*vn)QBwyAeVt#Qp@dEO5E7<&xteNI#omeM;8X0)V09mxB+%2 z^Fvm1OYSoZrx}DO^8AdmbXW~@X09;YmS~SZogVY4|7vi#{<-t*Gj+t&p!|o#%uT^m;g9?qDLsQ%NoZ)};e~AMHdqF{Lj*zdrP#OIppVs-L68$B4ETT-)~kQmx5Y-c?bHxY+zt9 zOMQLht2*~tuifDT@r7s2nFsD)i;VPH?=Jo;&H_6Ap`niVmQJ%oBwQ6D$IXPS2^Bk! zG&5ra^n>W8QXY6{HJ7_hNZUz;T_Tyh!tlYPQ%ReVf>rQ7v+z=jsJ>W}G9$NbDK?&e15o)8ho9_$^_^DK@ zqu|bhz|Zf>D-Mp=A5)J0uDB=A`cc{jqq0*A;`^Sw>dU&tpz}GZ+6`ayGT{1YkxHBh zv4WZA-X(2XW{atP)MKI~-J=eWrv|%1*VW#Sm9XFdn7^pn{?)O3Ep=7{Boku>=DXzA z*rbjmI`{@PPxPOf-j+U6leiud$g{MVNJHp4pFV%R_I~L%-gEyS0CEBUU`Z#&DJvNH z?R>qH^46z{VL9SV$$d|zm92ri@&1+s_Is{8?1`><%d=jm0pfm6 zLWRMa{3SveK`UrQ>#IlTMx&5+PV_X%DbWE-3DZz__QJat&EZ1(Mnh?RH{aS}^9{r` z*Ve^7FvgsI#F{ILWwXua|ExEfT+R^JC6i@(tEQqN1@UYPxqDV`V2gVyk?$DsnM;xC z4m4ps6G<1C97(U}6P9y8MhZu=ZQ0Avw5Zb!Ixh>idxtLEo>A^?kW_eO0~GLGhC!Ku zg?r~l?4~I9zxmjEsVKK-<%N; ziEx+aFxY70iJ*F}PrpBlLT~+D%ZJKQ$VB}OcN84O6BV8{!QG5XtR0zdT({@uJKe9^ ze1G+0DdRg0PL~q5P^dbQopuz*Q6O5HL~{Dd_DWVO@`*Y>$>OS&DXmDvaO8DqXV|ao zKfwD1x0v=yn6F&^QKx5WTrQN3z`jacYtp1D zUcgt`t!;jBhy+pIME4QdXWs^5?X>^oq1B6Dl`Fi1Y z_Pzp}Pp3`Jh4v`w3X#8*M~L;p-PkIUQg1MIBGOw``R*_eQ={^@0hd4dq-x|&qxTh< zmxf08aw~(#wu5z`js+#llv(f7bpvw6>rYx-2h&jp;;$v#O(AjLrE;%z#N4JSm4)TP z`NV*5$TcM3W@d9q9ACk@(kWyP7R>r!{ERF8#$Lwi*^KfP*m~M200yPc9xgqHK;a~9J;DXB5V%Qsc=jfH{E3bU{rS$7|8&9 z*UG3CoOjJ2PgBSi4CRwpSBYC2t?_N?m~!RrThXoGBgV9flWFWB_1^-jfum1hLVyM! zxRp03`CL(V;ELMf0`50qhuZC(esTL%iUM9PJa$BS%+y*j?&f_-%kv&ObX5NKH3e1s|unw+SK)_ZW0qJ`J`LU8zPgx z8_D++FFMD6{Bk0=ERwOk{42|qN6l$9%rZaBX@-@k%0IuhArXA`U^QV_q*U6OWivK<(CQRZBE=4Nvty3a zQq2?(b)%4uJbU1dF)qcgvs3X+#M21gZJ5`e!6%2S!GYj<=4rej2*Zo@`ZR9M;z?r zUNvLw#@@UmCP|=QbLuvYXne_e&idWWoy4SDs~gheT=m3SE1`kn+7Z_s#lrpBu8VOb zOjvHl?J2FLXJ`R@VC-}=1UwC$|&5)X+Oz7ga z*2^aD1!*B)K6Adz^7XbBmfD0A6Maj@A{m)hRl~`=K!ICb#@k$}qjd1>Zr`rfG`YOg4-B6DhYr_WVal z^@JB~>&KEdKkIXHkt{d5+&bg46sJnj|<;{YblS5%pr_gw*MkR5CK!^?)k09|iR z4sV2#JLJs&;k`Zn7b-@M^}|7A9dO#cNzw|$djCUwGlHOPTJRkgh~qvF;<-8hJH`$w zjMIXj&VVTIvmmgW0aPHr0KQIx0Plat&VW15f7owE@F)va1pGZWRhW8~DA=YsAvu@a zC$s$~0*M|AFV;RaBXKl8@Dbx7ZV0`tX~C%j=z!0n-@Zupd2Gs>>MVbg5d}+a^9vc9 z#R)aUF-9KEwhB$njtS@F=`RkLU$eqGe8AxYt2e7jBd>5#*AFp))QaXs-!WCH-<6r` zOLtk%C6~{04Rqz=q$_)x+7oMg$O$%Z!6DlH(Jl?LtSk)r$3eW^sXsD@WnL`kiop&9 zOPPuF=umYIqpJEVjVuffRVoi({8UG(mR{_=Z z7w>-~rEO^t5fFp!nqtr(AtJ(1O1c}i0V<+|64Is8F}hQfkd%;SDrMBT3?di#$jr-+_Sr_>++~8-3Z9NjFAHVux3a65|9!+O5r`CBigQ}jbqrCLJT!le;0o}iw{ALKr ziHaPY&;HXw19sJ{rcU&+QMzh}CPm&}-nmB3K5Q;xj>1Yplnuld&&XOabwZjk@i>1e zJYS`G$IfG(_dr!Sm^W*_y6n*vxvYEZUK~>DYyCkgS&qfqv{-DjE2%|;<&PpX53>Gy7_P@4`~~_o7Qah^CaA1bBu~>ogz+%p!z{rr)TZ&HLWXZABU;Gn1iVj%3!qrD6(4L{X+Q+XF%VPD-@bURLlguPKfLv_Rf6e7m#BbLiL+hXx3&gj% zUOUAFKelgk+P!8l1mxPR3Cp?vIvslUn6irDGQ0)MQYO_AAk6&AE1p8 zOj>5jZaZBvovaI~8gFR*`zLNl5a^5FT1=(s8NPQVM{NfLVeX|CP-t_F4IdjG zx{11;OSvRZGqFSt)O`e0@6k1u2O}N9QxA<8 zm8ce~`6swUA*HnWh?9_d_^{o>Acm8&oH(8TF72}{O2okj4m%S(q`pH@;>3)neX)8u zW^1vyChJp@%DSzYX4TAY3w^-c^jlfCXMBElV1v?|{iz#^KKN{Tbl|4#oGvycjLPxT zk6WY<*WRbc8>!$LdIDa`*+}`3WS~^QG2G4Dm%8oNfKStRMkmr2;S{CJp5d$3k^GiY(=s;?69acR5ph)B1 ze6FfthoD3UJ*YtQVNIB%;FBvVt9HG5`XFHADsPc-uGT8*NdN35Osd%C36WoVF^;Q54J#2z_j=2aCst}Xt( zT9LMQDz(k1Y_~Qd$E(exZ27sh7|zMl%3QGac_a@db(N;=&BMnfnC1ay%hc{^7uUMp z%LW2vXW)msktuW+4hn0^i0zy{9jdy-xxwe${qhYBRZ^R{mQ*^Oah1+?@^T0^P4kFj zGOCWRYS7lBHY+LnvoI)>b+G&QHjtdY<}up}R~clVWjVuiR_vSh>yl7Eb4>iZ>mf8? zYy3f1B{VL&;+AuL|~yHJmoAF)PtjE{osW2a?g%0W{1oBzTaGQH`-e49#)>4b;1wwUWI z!lb76bw~ZDZKaqP&~9Bmt7k{gfOIhshKANjVD`5gIj#P za=w_EV1i^9Xc`?fIOFiv4mbYzs$gW1ZOb5KG8jcl8})X}-NU-$7*5 zT4~#w0{%JcLJQ@Z47|N=gGa*0^X4x4U`}23>(17ijqPtL{x!F4cjlukH$VEkP#q+E ze@HFUa%k4QN&iYu?$GL8!T|ZjP{3(zc!m_?BW=%u}pK0u$7L6856=Iq>uH%6~+oz@Vb=|}ETVK<& z@1y`T3;xSugDSk2Q9%ZDhTSn%#}^VP zFA^-?v%xpGQ23cid`P#2Q}w(y9ASQQ^bXbu9l?vtc~S5BcTv!+1OY#_cD>x03_J|7 zIm9y-#fieCZWk&G--YbsBrateUP?3`YoncXO$79&y|Kl6OHN0Uw9Rk`{plo#NKQPh zCZCi!ZV%Cui=vMu%tJc7xb=TdPPp73Iy!hMj$!FiMSS<2$e<&HF;(`z{GEJM_%uvY z09?}SX5Xi}n@yxl_1ii4zxPQV#~!W4M0KHFPFYjapxXnfO?mzkVcy$VJinlws(;`y zu|A;UP-WXC3ip2W4*qm*t|WHvt7Nygo-DvP=Z&e-iyASq1K2B=%FzOR7^q%oqXIkhy>$S(s zGR(cpa32Ct!z*0)z+QW(st$NyN)M)XO3TG#1c}ylpWxN@*A;)>J_oORaO7#`8L4M% zngKk$X9W3gn!+FN+nV*{rbk58s~7MCSOqIZ^i1{_feFgbB7hKZ%~n4pwb7q+ z?!Mz$c7a^>GzahlZBBshy|1sZ6wm69Hxf~fyqF_3Hle=@tjONkX4k#SJ(k)hXQTV=Bli`k&a_uXN0vPJ zN19-WGE_=V}_Po%NvMzJAcLYj}E0P~F!GgU{<_ zc>;pqe)u@*xDx3S?4y|4Xz70(?D6=J+v{-2uHhZ2)G@bQ#T*~gyiGOFYLD&*yb zLtir@$&rm)oI(y#$_y<~twjva2d!QU;aNP7k_xF^icTzd~;%d?;1+X87 z@l89^FwXuM2ejgop>t#lxexf62J@tV*;w5GmVSzck4PzVs-uOm&Ge(>8nGY=rG}Q( z?KZpE&a!FydeTx^Bl=~5=nx*JCV|{D(6%gX#jZ}7$`vR&vcr{24rFLq8A}soTlGo% zje61DI_to2kSZHO#TnXT*mg@}5OALjqY11(x@H&&rf?VAVbInTA|Ip&ID{F66={uo znT(t%VO2i?9#Jw+H_mrG1lnamtWMmC(E7J!P#=KA-oR9J0|1(3E&FaS%&F#2>cEtm zYo@i(1J8TEHMr~sSw-G3G_r7PANkr97d>743<3pPib(V}xB3d^QrcHF_JBbo7cQw#h9a2>IHX?PI6;quN@#<-d!2hZaU} zgCFL63Z*HM4`05zu{ubXt^XaG)dZY}y3Jv-mO~X>y;!B8@2iSpX?FKSj~WCwD#(GN zP@wJmXO?Wy{VHQvYQk3JS0<`kDG$hFb?3^nKbRcy3YZ}Eg?~Um#7>{?g4s^~RR6wg z48_$q0efJ?{Dq}!fYKc6zvClfb?f(2-;5^>g8!)#Yh+}RI1z8qZI-GVOMxb1rHvoG zj5w-h;|Ryt?ZacM_qxg|1rOLs2WP=?SCc~c%xxereQQ-Ez-)WAgqLP!rFdo1n&ImNT~x z9v|f=C+ykJh7)8qJF1>Laa12wcM_B<@;0(PnBT0;YiIp@=&V?zMFshnawMnKq{77s zPo3L59RJoXHuaw8`T1)!9_vh~zY0hLYMiwj1;YsdY(m8eW{6i{gHTwr@Q!Ei1gTHJm+Ok|!G3g;)Boyi^_3*{v z@k=W>Sv;GqBB2j7tLd40)lyve4LSwg9B!l-??mC(uTQL!vB~u9+ec{pIY4P7cT>%T z{a-D04?C13R~L0Rj!y9E*W-NSs|9gKZPm1)T807H{=_eJ zO(o=2D)5he>9SP*Gy4FC>lQ@w)`hsipL%#cpWdVmKB#@JK*kQ3zl>kKGAi6z9EFzA z^IUp3b!9UIvzNf3JM{KT|CZ(Fivw|aPS`Ys+9)+QbpLFYhU?!G_8f)e{`HCkiW{}P z`k+w#_`&X&Luh4DVaCP>vhKCDS^g_P_R2s7g5+ua_23i~ZKxx#Bcmw_4DEN`PLXcb z2^_63FZ96*p@|V%cSn4yE!z<)*1b!Tl@;Z_TYIWuPO))ycQ==XO@JqWV`&uZC*Bl1 z`L+jF#A6}vAeo!nOA-T3?7Ot+o!Z{m^myh}YBK&_b3=MnQq}9k^dbQy_{M>ez(~J) zxSsi6{e{z%38hv)tIaM4v+E<7d9A91iJ{YK*&9jKo(Fh+t(q5<#)9>-K<}3)w`+PD zZvUy4V!S?7lYT-T&-HPYdBu*g+eoFDU40W*jLz<4zBo;kjSXd%M(&{znJO+;LlKfZ z2`Q?H$`t{oH(qIARMaW>EHk`evJ&8|2>rbpjxSU|$x)wRM|*Em52u`%xN@-Ci?)%K z*k5td0hdjQDU2G%XYv|fh8C#72f;Q5T?0j9Zslf2D(TtM9#(uwH@tqCnI*we|9XL z62CHH-hp2M zC~sWj_2feI*wPJ!YkD(3#}QoeLGnSYp{#2fa#?Iz`59yfj%Zam@!@V4Rk933;Lz{q ztmMBm+X}{a-qR73;S?MI(4oM`T)Xi=$6P%M{V4)RlLi`0Pt6Tn}Y)AnGY z^E&NA@W)hmdvr&fe9tb)#F_x_GBRA z^AlOqm?vM2DVXMIHq{Ue6vvroUK-66{cNotMmpWjky*r8Ma4#-tYf&cJ|i~HQ zJ}CLAY4uhSD^_mbF}r!)P&VRy<$Ru^tf^cL$CWm{-$C@qOj6&d(GJh_7~n}-!@5vo z%5dm$_5?3J4e%vW5gtbuy%L3~UE0-H?VeynPWimm%8fI&kfAyD5?y#K-7{HPvg-pU zmxuso!$klf?g<0(u0IR#m1@2z<3Ppf_N)1~60%83BleD$!VNQhj}aHC{=D4go9xtq zB+9e z&iN72d_H!St>!&5V-uUxSL?{>OwwKEO`AXcrrfMq?M?4~{U(r6)<;nCn(t=Y0j0^O zq}Z?M>Z>6D$@sUzS($b_)>xNd&LHO7?+1AK+cwl@+5rpc;lQ+=bW*Tz)t%Po*aLPE z7e^aKFLGYvdm6sB5M&XDMlDr){)oBBry}eI)wg9a+FAfO-lwrQ!z{FuA75Fc5|3bR zhfPleMc|3d1{-WhjuDXR)}zldOam;)Ja7ldzkHSyHvPhvSjy^V@1s-v9&#R9KAZvPbeN0mmkY@%9Hf}+c#F?jJH99=e%Xq%87J835P1H1u4cBbVRIZK2 zDO7mpUPN)jOi{PzWaRUcxMBws8;?#?lS~k4)_H1Kj&qumv#{2DM;e2JQgmemu}WHy zho#{wd0l2&X@}&me}^Soria^d!YD+jIAJy+6yeK53P2O{hV8Pc0dW};WqsQS_M#P0 z1d&F5W+T-&Ahq^aV!e=IXb7^Rspat98|WDWL5P}4^EGggqWR3^d=KM6?=`~ZzxAhd z?wdm4Z{}@)To}g!-EL?R8?vbG*^^ha*`TOXHiWl7gQ(=Ce=)ejuSpl|4a>zZF_7zc z_uKr;Uq;Ut$T(~DK&<}3yQ~J^Vtk|$n#z;gEH%}*c+@Jpo65%8iY6}$52_rOjvkKB zsQwsjkN9+jQ+_Yy1qlvQiz+Q?%OqiCTb0#z6!JHwR`jyv(0T!-V4w%*lgFKgggzFeeEzUloNqRJaFEa zMIZ?@Uo*bPuNF!$BVHrp3xNm&@T2^RmaQo?nk0*W#s9g^$rp z0U=LO5Etjq6}ftyjv^2L8(%-}myOQ3{vID0#?&8)79|69o_6OAe-N-4Pp&{mYyypekFb` zFl9P;uwH-I9J86r%v6e@l;=Dj_wx%yUT$y4W5ph42|$pRfo`kOv7_~RH|QxX%T+c# zI<|BRlx=g+eR1<-zg8IM(>xtSCDelPaputO7~wg#R3QGX6$chFmaisi6>aFl1mw*M zLktYst)PI=^f`7j$dTse^P4{~O$nS;02$J{$@9}IwJIfHPe!oGt0}rAm2h#mbs^C0 zyG+u~FwWWf@p85GvmzSEBLD(Yk{6Rng|mJHhfV_>%M!|Tp4VW`{K&5>u-Y#+-d#y1R|9yF^y)5;sVtbUziw*iUQ zA5?6gWRdhCStskgrTNlnJ+v$y6RcX}ax{;ov<#$u{W3p$_|hRO5=0~OXz;)T1Ajdb z#1RiGu=H~A%&a)CbpY^oYm>|;)OV-?3y(_OzHQ9)*H+nGIN4$npcItqVWillTYpd)e=?^7j;?Gf&@)d-GxkzWuNve5pk%oT9L4 z#G#Z}=cw)ZCr;-2N%qB-2{1@>_+Jk_pP_O z+P!|Ioyvc7uB^m5q3K01vpDTu2CH)!`qw9R`rmnh^Q|88VLoa?R4Y>JmvDaJ+`aZ+ zPt+d-3~X#t&|1cqO>^aXc&ccrfNSWSOKhq@v{m-jxx!e=l+v5OF6(g7MVP;IHj!1@Ekh#QO`8& zoW(!uUrD;z$Jgg-mo6UQKAsdWW^cKF*zMBBae0s_3zWolT4R1If^nL=S|yQ~L3<}7 z@J}Pbs(x+5G|qm?(uC? zYQ|4%V^IRbWM^vj_DKLjr&iQ-mVb~Ic&iHL1giKBH|JPL|85*rEt}=m97#zc{xOY_ z9q8RVe-2T-CU`;UI5YG(?QU{A?E-m2cF$vs?~si)@hBDc(ELDJHF)~51{Vw~8Z9rpp~;Zj}3 zY*z~1pzDF8SQneBrJ-cH8!sH#r{S>LaJN~b5yG_)&lgzC8KZ$S4I7|Zf`GAld;UFB z7NkUhsG8C0wb0Zk4<0HJP13_Bjtle}hPj3&E82^(>>PmA5MT`XadWHv-hH6;Svkb8 zmAUn^eUb1Rhnda^rX8}=h9A<7>pM1sgW*dJVLPwod06SWn^lt|`SCeV&%Fju8*+Ms87|eY}+&po^aHPCi{PyZrD#Hy+IKv-P7BpHi&w> z>A1{6&AeK=2d`Jz4y#hJWW-nqdUQF0;mZhO?qjxL2s_@rM0Bm9YMW9=a-afxlRb5~5A6G4L27csO0Ln!Y zt0|h}4*BbthB!5{IPh^!lrTf+)SQ-em#fT@ndn$W1%vj}lpq0tZ$!t$0zPfD2k(x< zBhvfi_Zx$i;?s3M8I1br`@cVF)%$VPPq@FBL@^x7TYp)3)cuiJP&XE~?Az6#ro6+K zjGXG1ZubgFvH#=26(1^lNiw%NSXk=uX%?6^1qk3w`K~iCOG)9;ZX7WIJ3;dhq_Q$8 zS<$5S`ipCP0h1;J6O3;dx3Se_qQORqh_4_-y61x+Yz)YrMCM+Z$stbGvVcmv;`e7* zpDsMAwFzgqgv^1Q2dFZcTgU7-q!*YD!qNztHBC?k{e{+e55MwT6}1wawueutZ6t&k zpkR9s0J3zQ92uMRZZUHopM>_J+am?u@mIdjY%TV*8t1Mu3Rg-*& z*7P<6Xs1aN0vyf=B1je{iGp()qf{U=EMuU`Ot}4^HcXDaxzDso?@OmC<1Ac=(t><% z@ymH@vWT%@?jkGXKZOnl^wG=DLRP=`OCon`14wXFTbH=sKmC3&P^q zu4Vj0mfGanAkJ5eBm2^Rx;>c?NS_?^xvV%R#f(V=%p*cAH|$JBPpFXIEj*%+58WO! z0?y`iI|a~it|*&u8`aOZdh1vKR$2GrPRMb;Dp70m8b|VXQ}A|WRvmHrqmRTmMBuL| zucK;je>E8>y^MDGxKE|ktqr+UdEaCAWzw^J(aZP#-5-Gk5EQ)tbAF(V(1a@ut0%Ug0A{Y*Ec$AiU2V2j+nv*1x4b@s{w zEvL8|{Mhp8?AnBiCU!Ji6NR@gut)ra`gIr_Hnavd61h`_u23=~&1zC`k{)j@GDlO? z+J~21WKTEJ@*TDWXY5y#Y)6=(>*7ak_TgOiGZI0mbZ3E&T3<5JlnGr(Wd0c$O+w*s zN{Ytat?&rj59-=vX&Qgl3lAaGB_&w+O8f6nKpH8?rXIfyfsRuFnauNjk1(3z!5tyo zmP@7>%j`tCU1q_uFuTd7TT^$y^Fk$K9IyHBv;c9&C|z}#m#%rJTTWe*zW4*gm2IWY zQ3r5x32VAVjXW@sAtfGu^_!GeXgxH)zKI}Xn`2IPU+G`&+`LdJ)v~^I*E(-c0+WuO z-rOr#I0G`a^Vu;=%ic;jL={EY!1G9#UVvj}*$x!A&@izTz_lGL_zAW0L8oKp}tO9*>sY z?+HG(ptZInOVQE!nOR4&@C4v%IlY?9?27O6BB14?)|KaD89v0`MxleJ0H~AK`Rq$Q z*H22rAFs)x!0cmfJ_sG9(BNm>A_sgd%!=nj9!&td;@fTWf&~{L`Nc(XV&{O54?by4 zYmthIr~vI(pSD2gU^^B6(&!&nK5+8mKm$}~l(H8H&Ob1@eU=^o$?3Z`AF11U^&u$N38om-d*s>?ZrR(^>m9@)?10+~Wwp<_ z6U8#t#0^o-;pzOYgX!VI(}hNzOXQ5Y@(AmP1MkPu6fcoWp?%feIN9#ewLtC)c^q@Z zYlTCn!{9%N332z2YY+40M#a_Rsd0TUlpGLVhjQ~u_UbCBa+_`~d6Fwbeqoesm73?3 zefME*O;(e-h<#xxA0`fx?SFR=5s?A36Sx~QY5sM2drC@kt{{Bgxr94YUnnot27o`| zrz+!G-bNI{8&PPQG@oe2r2-(xMmd(2onHJe#6eIk?OQYr@+y)V&aFCnE#%VWQMFQe znh1ORC$eA><|e}q&mXK|r?}RgiaEV}UCl-CUI0MjExYv>X^yI1MG}wi+))%BxZx>& zOX)G1Ec$+>f+QS;N(coBIJ{LyFtg^sK&3E3;rdBignScc)`!LB7S_WFVWkhZG51VO z6&n=e$png;oNLYGTO%nu#O1p@oNX2P>lV=8!Ev3}FtT;``aZ%M^BQmb7Q{hq?Oidj z6b{G+Y*qV`r)TTPyayd}-e8;PCb^buT_19~56rTH<#!bK7Tf4BI_52KCX{>P+#Bs+ z+`TwpfijR6$9V9E?ZNeX>k@f@vwxSV2?a}aKMdezaAF4Rv4p>pBYOUHV}Ls*x0<71sX9jde7%0n%6f)f~2GpjXl z$DPasd6o9Z^KEQXV$Ceh2~+;LO1COS>8twQ65YQTd!Xbblid3D=WLiT={-u&$3G@+;Wv-R?6WuDoBVEF z{@L;2I;#P=XBq^l{uO_bGm;4Y)^stRO+UVouAJ~01E$5pSf#V9QN9;EhvJ1*HfeTM zKpXBp#;U??d_Xa!*r$FeEJ!v2e?McQFR_8Q%a%e3QT9<24BSvPP{bYDP~1X-lnHwt z;<}^TqrpXvl$ykc5hv*$*ytr@HLKd$p(fOZ?mMQ47KTQPpN-Ljf$-(g+Z6=)r1BI! z^o8?_(TfKVF5terr8$8_XVqCxD@|3~zr^Uc>j?9EMn(^}%r zXF%a5cR3C2M(M#@70Xi#T|;Ns>s zxsSQUD3bk)a3e+~;4F6&r<~|`XYg)%_fm5wtxC-uP^^mW&S{><1l;0cXWnP+jfiJv zNj5{n6jQFz{`@)p_Yoyt(<@sr{UW_gRpb&y1;|cUXl>;*Cj``2tR-viY<5e8t<7gc zQ<$6?8|;VKuF%=R;V^mnsK^!yETo;omqfvB-ruTHOQ8kCj~vLm^6W(yh#}{zggz7z zOMVU!Efe=Y+j`LLzEVB7-lldL2u8BM`V~QS&q)nsNsGjoNt#851=^D{o3fH&-bVb*?wTUyg$+ndcGX37ej^7_XqfA>yvh)) zhdZ+tQqy`H;?mfpLw1Y@1uN@p6L{k!yR=O|)yu7>TlGki8W<+0EyLfJV~+w(-4*3l zl#`Sb<3^l#2_X!CI2CS6zoXU3*?I*vI5=des%p~cKs&5@_XahR)*$lRMhgI??xrg@ zyK^zC+`)+I`jkz$*)5Pj%K&6iwQgn{*uT7oPXo4SafJjwX>rK_d>)8zomu9f-V)wHjFICFKf9c2`JoZ>L5(H{1QAyjw3p#OR+ z3!!iAQ75;4C6om?A@(G*0!Sk#SdFm0*g4@Bz{V!PtYg$hkQt$u3{c5+g{OXw`{Nu^ z>ENsgK%dOX}F zMdmUCaDf}jymb|Lmo$xU3Z^Mn0qSWN7}`Goyr5<7&3>BE8D3(TFivX=k|+FnXR2Ex zaZh%#sk-XvE!kDUsB$VW4gtve&M$L1*BBIjC%q>0=*;sr!*+Ub9;0qCN}CzQj}7mN z-84qW>THFierLz|MD-2i7fZdpEv8GWh5jo9XZP}Y+cA?+oTq$k*9m+rmi*&eA+@uQ%$N}O9 z#Btm6NLH00k{Z;y#V-^+{SUA%K;Z;I>i=Np926lC zq|Qa*0Y5HMc>jCM_uu2|pqx1;_{mMV27=rFU)r1xgtA}y55RsFeEucy`)uG>S1A9a zQTzP)<_4Y>_;_fzg*J!~x>Hs*-MwzC(CrPv;{?te5dA1W)mqPJ z+@S467a9ZNZbejV`$KicDnOCtrr8a!!n`2P@zE7(g`CYcW72v6p$@FoeZ|bnJWqP` zhfzE@`mUS*7+;D?$5AyE9{#4nO$2$)sk&-%gSJ3dlljrVFb{e>%XLf8C%pS~sXL#P z3E@!3DCKs>2Uo#G3Pc5vV8dehk@|%RThFPE{55n6dP8O+jG|aUiM7qh-6OkK1)>^T zVjAm1B5$k;Ovt@s5l6V~+%Fa*OyD0KO=WE?%(foTGMk9ax|5bM+IbkmCQRUy_C;0>sKxa z#7`tKw}nK_%6bqCAfXUKl@Q76M$gxWdFk!)Qkc4eReeh~U(nYb`ybhVE9 zSqhhO>4Wjpy`w{)U(!QaYqEcTB8^`hiYd`2bRW?HnEqxBnAy+vkGJeYrXwtl=EgXI z$t_%sCEQO%I#L{<$|*&VifSF=0i++uziN;BX7$(%#mxW!^nbXtKomRFJ(7#4QTGP4 z++gs5w2O*|4K@%dlHkPmERwyR^3JUGfmNHoD@_PM=R+( z-iD5RL7UpKS4(&ZAA@zlhh(`Aa`rdT%xPxyP6wA#7MG+(EUf@fn{_)bVSOHq_x zt1H%I7mTy@!IsS22lT3f(f+jP`wszK#)F=JUzXX!rc{V~2jY|TgQ}erhd~aJQn|k) z#IGmboj*6H++PhGlPvLra+p(Jv>AJLywq*JsJ1T$alwR>{bCmJkKv8kRs}E>weRfd zlAj>XGt+3wo-F^8_G^9y8q>41&38#(TU}I4sep?!Ip<FqYK*+C)pZJAd zgHJ_D&OS9UV$EuiBNFm*BX&GIz=(hLs)?G<{cwqK#Z<+%#B|@;;BODurkAO0-P*^Y zoF~=8=m6iYS&p%7I)$3x64nqEwf>p5px9+*q6!>3w`Per{eFV4IWm^XtEqIZXWMl)mM5%z*ptjN3h(B`7YAg+Zq z0ggep0zOM-Jh=#Ur|IPXchyu(43G#lsYaYG_G*hJ6W$6Z&q^m|&OV?XN)(Y2`~)*u zx{zmBv;DPmXe?sqYeS+UDMaAbH?o`L=-q4Dm(4g4pQRA+B5S@bm>jeHA6c)q6a3Y3 zviIAHq2q?602W%PbMsB~%MpgduHZ@6NxNYi!xbN4J5D%CxxV2QkH)x!G-q|!fN)f- zmP2V`vDY;tMIa!Pg1-qMGkMD<#(_C^fcvMKAi~6P0zFFwghTqYndC>Z&ecNRoos$F z7W^#>ON-MBA?`c^`0Hm^6au37$Ghl)0WOb&F)g}>APC@|>+o%E5F@?4&98nz=504_f13U%MYCuS~B$L1%}W5hvLn)d)z1B8_{5xdpa*WabX&s z`g|2qcV%V@N9y>kC3k3xd!*-a@b~AD&}_-BcipMe)@+P#7G+lhHnFCv)>QE~*Ibxj zQK@=|pMubT@AQshiHPp*-S)tyDwkx@zh{|~4mmPFpya0VGe)8?9VGAmLd}a|jk+3e&5MsT6^@GFPN@E$kq>G*hcG| zPb{)$Uxs9~(LbVsu=BpZFLT}J&`o#_;9o_!kk4cSW4Z2^_#t7p&xW2d8t^_KfBL4? zQsKr0czzvc0zD(=f2?2Ipu4B|BusE%S*m5k9GQ2uvPYqeF^I?SO|A5iNU?QHD>)_ zuS&@%g;&;KE8THn!jmXIMCaHxAhtZa=da`?$q{jR%l>e=rsFNw+()UrUR~T03iEq1 zs}Te&AWN})%w7x5!XepGcq0bd1%!jQrGM!84Z_;dOt&%6IjUm!V=^al_HgD}DCG6W zQt$`?0B;29tqc<&&VVeZ@PYYQdxz|ig+BGu*Rnr{4#b{5mbme6k&aiTN;%l)q0%QK z*qrm?R}a=Qjh{~3;=$b6y{GVdKB1ToGR8_(aE)C6{FMtc7A*xU)3OsL4=MaprEcga zeOG?a&#V1b=4j?qe{sX1iDrQ`Do|kp0QWU&FRdqve!D&yPhWVTAF=WwXdn;=&D*ry zzZ>yL*wQ`u^_TIC-u(O8H^wM7sd(nv!=G#hfJn3+>%8ZXfn+r+6J>eRIP?Rx_iO|~ zZ+P<5P9;YuP>DME$R@K5gO?g(Cvx3UVgX)VfKeJRC696_FRafNftb3hp{lCLW?Yr@ z4HHOdl2>*0NuOM)axk5hCaXFL1ae=23z;w&u&}-pyUqYum1w|F*J=GHCEpdi{w#{o7hGFSwdgL12G81oKy@@sBFJKXU&kzRo_5gNWPBZpc$>HXr zioBa^q5I}W5E@{59TeYC%|&2>;&I)iar$s!io@uB-e6C){@~XJGVW{r3ZiE<{zH;&v!@BhTH7x!Sj1Bx5@L0OPxks~FE2wWwa&-xA;RGkku?I5o^@Hit8;VP$(1#Jae}8^ z76jJ80>>i69zg(2nm>n>Rl>dai@;0{_gl!K(bsGZ$CDvcz!|{50O9*cmrRdj;sV8b z>HR(uqsvPkSe>y7{~ZGPCq_1ToY9)zF?!+ZUFm?YtJmy>6t6YcJm&A1tvK_iLs#tj z&@Enf)SyHAQ$ZP!#RLE@Zi^n@>TrqlfksAuHdB^^-J13bBwOb-6P&>-kN>?v^gL0z zC0{^PL;xE0n^Ao;e0&xc^Ig#&uOe}X)$D5-+n4bi3<%uw;FFUc((_sFVYo!(C2`wuRhPvK0M49km``2aK&B{nk89x z2kmN+kUrFQ`l1<(L=jyc+2r8;ftS1m>&z(zDbIS7d_a-btPR~F!5a`+2#VYq-GM~X z_P$)_WQIKLe{Wo!Xcz~h`j}Ni5dM`ZX$wW(hGA{X@cOUn8&y?P3P5~8@{?lt)76L8n0?if@GQ@!tF0@j)_7pXWm<;ZSjCn; z5IeY0pOQBq6c0;vPMynIX=ZK%Dpe?-t8}fYlj-0U@_pXZu?TXqtaE2qB&rFREYG(a z=-<#&DhvXKv1_QXHaS(1k1n8EJ^0Gw&X6six8{NK1IdvORR-+T974skiZMojE6|w> zzOjp1wJvjK3S@^XM4>cf(2?(i=hxkRS-OGcmgY(mle(0lX^Zs483>Uvu%KEFAt`#J4}+Cz(5ENy?=qYOM{nD(@9Epk`I8cV=L0 zvCF}#_Voh|_*;Y~iC~5Bip~A*G%0wx1cKy0hcWdA{oTCfh+qyi>n3wFP#=>2ByiI^ z`>w+Ak>r*OAR{Rj%gXQeiluU(iZ@@cG9q>e(XrqtuW0b=d265=TTseGmOXZxS*KqG z-TI!5R=St4dAP}Kt6DO&-N6f986%6yO`Rk}iw8CE@C?|$#nQicMV)hs+OjZuZWF}p?j_Qa? z$mPk*hcuMgnFkLR4I!Q!eSbTO8KL#;oM-=G0JC36Ciu?I2vo3@nICY~H&A3xkJh|k{fgV)(N~Om z2O1v{7UoIbJJptUJ-<@cZ!xs|7TzZ^hp268+di&w(t=St8?q?-l%Y7gCFuTmv~495uWvG!KI;9a@J^2 z4FB6>$|_aZ<-hC`5sXV%pCV8?x;<1tN&3^4S3Z#Z@fTAO@>HApH4BWfozso{riuF( zP_0Jm&0q!=2qr-0E!7!6jD$G1JO2Hn0DZMLdLG)ZO|m}NPV9uNR8>7PYh#2ey)3?x zO($4W2%sb7!DC02 z9V9k>Yf)MW4Dsn1dpCg0fiRTlhQ?o27ChES`FYklRv;Wk(}fkCPA7QB_!f4$(r_JP^bXEEoq1kqX4u4RXhPssN}59xc1E(n5RW zl`Kg44b8?ff|>0P-Tx2EJ1FkmylC(1spA)Ke7IUVGI9Tp*# zuAErQ6dw}u8s;LX`TF~Iy>D77etF~w`)-vcip687VkPgoh!@KKWR+?DeghY_oHP!^ zmDZ*neOex&GU&p3kgO|`#en5Ml7_RKh=;43C+939l$P+78 zi5^n7Ag=BR#th|ZuA3Cu~`mN%n>r-AmKB6Wqs$HFw`p9<~X-~l9?RfLaD$;+!z zsdZIlz>2=Iy@wT<&`WYjk`zaP5U{{2EhBQ_++({n9=~hfQ~|9D9ZNbkMP9$u1^*te ztZTsOv4hhoU2pf5)AKwY`Q}Y~oIv+pN8o=H>VVFNGc}3RLRl3Fx<-{VHA;5B=5Pb; zMrX=+Chd&A*xJu!2G&Akz^AfX=K*ul^Kz(PNq(p0gm8b{49S_RIg%74;lj0w%fY@* z`Sr82aV>JjFk^K`ZH_ZQ3Aj55g=yfMw@SVi+}+v<4m;aAn6UE;mFqq}8^=ffvtLHI zWdEKn=L$S6(3{uTZEnodps9;`Am^k)Q2${EBE%y%X1lnoq1kbg`ufiUzoc!!NTA1A)G_Mr=^l`$U!c z_^`00lbr)^H0)W@$)9mIt{URaC6EtRo1u9+*OQSkBcd|e_c%8$070dOCK+Bv<_e;} zTE_Rzt=VtihY>XFFoc4nYNcMDZxrF7=zmXg#THx(${ofc@l)ueX|=RGmv7yG0Ev`Y z4&;~Obh&PIMhK9Lnirz%`>FCM5pSaO^8|wwQ=oN;sPn{X3XYe4>wRSQ05DZtYqAU_ z^F;n3H#W~RnPtr*8%iVt0m(qhH+tI(L>W!7>@V)>5RR4_uX9f>&#ETwE_BG9M+B#| zOP%373%Q1lDKvcdB*Ah?Kv#F?UaHGaw`r%=)cw$TDN+mE-C}M@x5LB#+X4hCtk`37 z^Zb^kL)e5xlaKqxi;YG6^$%)!YujV*ONTRWfRZA5gV&FUzi(p0mQM^TrQ=p?m0|}qb>YF z)N|trI@y9!bh%xW_A5?%E*Yy7C-uVw&n0?52(Zs%1Z}f|1*1=ZT5>ft_iB%g8_SXQ@9cbt3jiNfOto0D(45+noSq)3BToub&#V#D8PF zJ&ZTuy52utRL`icO|dR7AD~?Tbz|!tNeq2?Q@;pCr9oCu#g43x) zAB)#SS>@%pjEEiSgHe!LIv6L3l>C^fpVyEkDtWc_oE@*o0g8K`{_7(pG62$x@>^>U zgYJ|MDpD%>GWl-~wYl!W>>2x(b~nxOQ4088YuuDU@0B;^Jkd>j}LcQNTX2eiiiM zZSI?|^8n!6vqd3gB07pZhz_|>r#h!UGYbgjB2M=GN2f*GUScE9d*a{ZS3$uN7(OS& zoYcJ(#XeQ=lI@6eeX{aDe|tXyjdAh3f+&SWS-;|oqkdG1|!%E6N@&;JeE4KxURcBcR zKAI83PfuRXXOjaQ-#xT*o(!fWwAt$e5@h*icTMMuzOe`ku1x!si$*0~=F0ccW&*39 zI}k2FfZmM}T8b#wgDe2y-}s1RGLlaA5@0sg03{q%#N2bIc#dPW!}a%Gmz}-STGzM{ zo9L_>$K$d1t%`~1YAe+lmGQr3AoX_1uxL4g(STZ>#X!&uy!i5A_{OcHNesa|Lqk>% zEx6@)l!Q%Y;t@*6=yw)-4J@(SmG6BYczRriIlV)-_Q)sT;PO-*lm_~dGsN_iG@`ZN z#M?S$4nSG1zF3Nvc$lsn3EMKltnt_X4jgdNvRB;ZWgOU0SK=rFHUA`@&rd3@`QdTT zoi=hMZD7TUpx{?v*kFsG(>Z6&$AxAQ113 zG+Q}R+T1ctc_kkC@#D?a8$f{&RyvTZ1UU!@U^}BKQ7n}LFYyy(pyrD>6jlKMV)hY$ z2G3lRMHzL?KW*CZ&V;$6-2cSSto9=){ArbDm|tpF>w7*)y;br4)?J~t|BY%?RGmY7 zldD?lJ^Jdm-lLv7g`@*Y#3LoUz z16NMU$a3AW1*vndOkG4QmvVK(vHy&|&sQ*eGb83bvM`zs$rM?781m-JuP#9Gdq+JiNl#AzW$E%=+Z+(kGWE6QXam&5+pgxD zWS&o%<&ypP6#`rWz(w4jkaVWtsRaUK(!Wd?o67Gi?Ih1CIFEl?1M33C2aZlMf!LeX zV)H2}m+xHEHJ7iVZPz_=u~}t=+0L=%)3Qq3yn|ZjVRoj%(b`Xo8yaSrXRtzZx0zCN zPJoVrpxhl_Ss#Fy?QLOu&qCTwT7|;TLk14Q_BL7)-597d1O4_))!=@MGa1EU1DvsM z;kT<;@>=55bp40&KN++CnD=R0)-#ho*4o|H#CMtuQs$udXjB3D^_&0=aB)r7sXIRO zF~rCP0BVB?cKq1$f8M=UX@E1<#;cz;k|9vdnG?YV0I#RZXL2&wUhCOORmDFSIJXr? zCIjfdNw5}pWNwsJrb&Esh2oh#gmVELe9<<5&j}FeHBKZR^$K9Q*gq*|w<+M0TLn4j2UK(^(R0ZBzh>wCA}E+m6rfu{Qz623=!zDSB_C?%--)->O&Cgd!C zoZQ0-5e5D zRph)+=%utXamC1=d&3V}qRRiH+)OL1tr>P4S#Z4L>u|A>Vwnz1GCS?Wc+<>=n;^>k zo_U1?!!>uL2YY9YN&hO%R5i|+J&t}q7COhxjcM3DneU$Ib}d#o-puPZb$lk9C+IT1 zyh4^l0*u6y))Af!_~5*4FbsOV-DYPv*r)^FOJo_#|*TJ$SIQ#iL3Xw zZdKN6{|K{0IIDIy7^Tg_No1IDy8!a`Ts2%dcvbeR%r0+SW@7`V4?L50HwnZacj?Im zCVd4`&)~WMO`Z%?fVQ{qg#$&qeH8=zY5G`%GdcJ)r|hN0aGg8$cP+bRXsQ%eOAP|5 zAP3OWT2sn>fR8}~ul^&Y%hgDgeUY2Vhv z{LOc-T2o`4yLW6Jr<(j6SL(vZwd}&YBl?pz)8dVpZf|W2q+$u>j)b_XN3J||W4$;T z^%y54>S(3yzPGxZH)GH8@3>n)YvEN|Q&=|-OJ*Y^v@Pj#adG8C%5UVAoi`^1T zkg1^o9&L(Yo9pS!d_mQyTmz)u=h}yV?eyY(LjFZF(Jv_2@@V|hUJdkZ-q|O{YWod7 zrOYf=X(v5xLuvh^TC36CD858}yZ)Ta6I3;R2fe6*p?e+?`MddM1BrM44lH^9ISIP0 zb5}K-M#N+KKzA=DYHA`~SbxbvpKB|M8ftE)#P$4G&h!!hEf~w%!>f${%4A&5ZF<_V zu{>;jNoG9hVa{FzRz-F!-F*+&bA{MUXyG31SW{l+ZJn8dI`U8#b`J!yFF#Z z)?&7X@El7LIm6*+J$;xzJ?b9^GuFPYVS7GNN#!%}w!2K2Rb@oa8!Jb8+oY))tMKZL zEtDJe_1{=(q;GmOZVSQ=M7449@Wf@5l{ZqV=uNm+!^@ z^F$k46_+p1{2G78W1okM$2ys+RIJDvyJpqDbh2sX1q?p=&1dlW9Sj1^OzmwQJbqGY2-v5PjrLJ6Rh-zt%C41}yk_7aPzbP`aR;c*jNXBK zY>LFcE0QUIvH)T7z>YbED60VZ_viGHx^-kwcppm$Er=u6z^VQvYgI22dWWbEJ;C_W z$tzi&%sqd9LRY2TEBkC@pGKRa?HU-6N6n{}>0Bk3*E|ZER3`k--y8ardLgo$E~iot zrlD}%FNbGC=9|>Nf5b-RZSF`1VbWYRp4q<}j1Vha%DO(-{iGq!A&+a7e_y2hVGDu1!dHi$Vuh6Si%?$m+nw06~Aj?8h3K+_Y=LFHeV%W{Ky0=RcwN1YP3meL+MF672Y1A3dX=#-fA z6tiTr`?77?DHKS7(JvZ8oD!%z(c&z+rqbras&sTLc@Il2NC#B;hMx38xkKC&-H-)a z05DfEr_7Z^C4#6bQKc$X!}E*)_6c~~-djCUF>F{a(F7S&9pF~WX>9}WKchv0P;1~@ znkGU>oxk|+Jid+Yq*=Lq_iuax!Ao+p6*e0fI^0shbi>Uuj3-f0wMN*{!WifhKU~2k zEp~@Z9Ea@qc??s=5%R897!)F_(Rd{}557|iMdkSMjEL;k0n@J1^*FuINzltv;B#6T znIg28hd*FuphL0WIs>=n6js=&$v2>O8@CH}DROOvt-!ex;@0*IU0pdQ3^+i}$#`cD z<9#b9(8|FHHARmLMYm`mfuhTqdmk(9-0zz|S}@Wiy6at=eU(ZD?ZE&yF7b$Znz!Aq z73%(uluckk@>AB4*-C2Qv;(O=E#7=$u-C8^v1*zh%H2-Nz2}>@U*lLEA9I0gaaN4UYE7r@ zNkO}&(Whz;Tje!idsO!8qbK%+Pd^%6L}3GsJO2EunbOG& zWjYon2ecBYgXNG_jHhhZkp7{J!=|rq0&q!j(k{Q7ubc9_ z+|SnE;tG3o1+mCUeK?A9wQGFM68`Erv-#fNKO74%*}5jR-zo+^E^#mUc=&rXwy$KA zV({OKW&pk1jaH@1Un9iX$ABwmPa&~|befDkZ=@QL9jS_K8w}UXGu;mbF~s>nQ!`1# zEz4q$qtGw}2#y*%TA{Z`W_*_TWw`!IH=GJc9!5;QO9N;GD7Z#FzKKVfy^W&OVgQVv z2%Th?>BY5L%emYFSPp9MvrWk(;g86mhoz5*D4pw2$+d&}ZGaxUg|{SXDT>v0ULfrc zmc26~Ui_o2MyNTn;3CID30E$6z&+p4k8)JQU1O2{C+cKBdUZeaTpSr>fx;2xp!b!q zg_67~O=U~snKf79oZ|FhIJ!?d=6yiqu+r>W#6wo&RJ(z5ppYCee)PzTr8Nl#q06UB z!c=w;+(K32%(G`-Nm6vSn)7^ecA5nF-$!A@lb2!T%|&jRMH_=L^Cq{T zpGTTkD!bJi!6d6m&ibTC2JfP-IdE^w-PF649Qg2ZQlyKqsJpNIM|7_=(-W{Ny6uQt zGGpN)nF~>BMy|C-=?Q8|qRhxF!;Bz*f=+KD7pJU%UltpHfG-Fc2j;)C&b19uL!B_Y zuY9gh0w9a)mqb6A3lZr2Cuh4b)?nunl+uV7Oena zpFc1w?Zfw4zObG`gD1Do!ac;-Q|v6b(+2ft&%T`sour!hil^|>ofF@xIX2b}FG-cw zoaotmz5wL~6!A9ujWxUK2LU*zkp#C!be+cTG0Yar{f5`(iG@*2t&mZ|sA+h4(-L0u zE4?Vw0%h?4Pf`$?+cCm`x7@x1CGU+K zl5pukj$kLka@MI{W})E>rJ|>x+n4Q%yk+RKxs)$U-M&w<(Cr?pk&;El{;(RJfT!y47I zj_P&i25nMxz?%!kBXoQ62QK=l=0^8?d_j5PsGH5iw_^>IrWOmF-`!NE;JgC;&oJT8nN3+_N>>4ZgzQlJYqP-ZE;YnXKf&_H=TrH%#0+QW1xeI@vQ;f&NH4I_l;nhDhD*G_Let9kdq)K*yV_Qg64s9rvnVRX}3gBNK%J6ePQg2(BqPbP(=M&!&tT0E! zO|)!g)W#ytdC$pIeiYFYj{nH;+VCZnS6LEDMQOy!!@zXR;5n= z7*D#fv>S}F1o43aTfV}+z#+;o=LH4|AfbnCaqB8@US#}h;fc}3=u34UAAK|{LzR5W zd2PqCUsE3-q7byA|Nnf0-xtFpFeso@{>5xf=h1Y$ zCCC!s0vQCHBnnXaUJ(2%3^ES5LAiTIkbQ6sWExzs4H*9?i_Z!23D`kK0U!7oCQufi z4rCTw0tND~f}h|5f9Ha6z)ZRfe)lSgaZVz4He|}5>Y{Ow-sN)!y!Rtk*O)ab+8XtT z2L}gIs-J%?i*oDyiM+>hv5`Vre*bI=+m~QpOITy>xT~8M(`8+;hNtp?w-Cbi3e$F` z4c|TwX|_sjyymwGujN6nT76$jwT&9 zxB1>Z&ZDJp@mMfv-*ElW%KI&jTB?W1%;|2*<025^Rq&_I&MTn3vL* zM(lhXJz905@x$a;Y);Xo5wmyLw|~?X7=?Uxe)jp5DRJ@M)8Dqhl;)LMDEm-`6sds{ zVl{i674;%&D$$2$XtWhI@^P*QDDy0!8dogG>8Rlu2r_LIf7VNV{u}+k-jk)7)h5-G zoN8O8lyM@ic!}{dnIP2ujPE+bBM;Ch&5U4SE)D-i@dm75=z$>sPk%Qig;tlN#3+iv zTat{A2i1_o!t{bW!`3fl@M|Rp19dwoR4w05G=J!%@Kq4cb?zOLqQ$!W$D$XZNzo&{ zZYf0tklY2smX6mZihC*NauSa@ zFvdB|3@-dQGESsNkT7Ry9q=GDb7gVIs6J|I8+&U#Ful?Xd{^b{!Uw1U`|sEQkv4`v zcBuKE%VFt*A$3)aYAUQ~hV=lJWNmc3g`0!Lbb6)Q zGD13=Pv`)l9A4h@LR8A4lsJ$r-IfOS&DIb;$+=XEbfy$X=K1|lvB6P2mYFw^gYEle z-`i*nt$+QIa}gfc#NmoqIdECg6D%UV`1>=sy;6cG+Qm2V{Ob>WaiNm=o6G)ZeLMAS`Z62LvZ`VyINS5cyCS@u7MxNm979DlP{J zNY5ntIJlL3;K)w$?|WcNkA(Hf(C;Ukl&r;8-F(`S1}5r>%kR8wz!m1jzUBHe$^|Mg zEQ*!2>YS`T`x-d%ORPM3X>_qplAk^QD_UeBZ@8EY13_CyUj1c-t63pg6|{L?d*LB& ztyr3yH4(Psj|=n07Vm1-nf{lGvT=%l&BrwsPm$IeLR?ylxk~ygC2O}ma>(DaDH%ht zea9rY)s|eXL7x8SNVpyA&}@eK48kk61>$V5n;zYCRVTFe#uY`w!<@>cXv~munNU{C zIqZdA6ZkIAyO1m3L)ZaU#oN=C=Ct6L=6Kq1ZGMIbX|uG~Un2G)LBfRkWNAtnX`hA7 zxa<>3jFQn&fyPVVG90rB(`ZEL@cE^97yeE zNTgF3?PvKIA8l-WzSnF@VK$Q77vCcg3kCpL-u3RZSu_2{#_s0SMhUqfIa0i&p{wn? zc;4+9EfCGf^^{?oZzibzkTFhdDa!7KlH)A1)Q8V=T>;ZEr;)3xTk~1!#hi%jfH|I% zZ=PM#vKhyuCch)uy=`oVpWNCwzT__3Lrg73+PZ5z}$1$ex;)MfnBOU)_{ocpeeL6seKEO8C|DD=$1hYm&V@!+-Gz zUb%VMGHAHF;oD0psOpbbm#F~zp*tl-olqFD+@zu|G9Ek+)WFne9tS|=w*8~3z#AX7 zG{G)u+piAFkQnG%o@N9i4e!0yceIx>{Fmc}0k^rwALmZT{#3TkSYr`fF|j#?CH$FzWJBeZQa={dZ&IkNs@nK4@xe*Qw`1!2 zo`2nmm?aTL0*B%R$`2|7G?;G%ERNSs&bl8^DOnDq?;;RY3gC`%U zvC70f%!=c9Ck{quA@&3d|9d`gCUNDVZH~M_Y6SJ}->|+myQ4j)<-wN2Q)9bAkKE&S zBlNdUd zvK$gy_l`C~8RHA?^eQ7r5v>luXVkyw<})&I2uU$&|_8# z8Wxnt*%xI)Hi7$X_p(ex4^H`4{MgcdLOQwygFn&(LHr;!Ff*hq`=LMgmFAG2s?9}V z1>IFY8(heOuhB_w!tTDll=Y)^RNePl5nBux7HF+!&i3#1@Po;%+?DfNx^@b9;YyWY z{YGqHPyh~k)y_>rRg4cb=n2K&X}CTUaa*Iwb7h6peLlH&a^4WomCfW~4<^=@C*jRvU+jEnO3LW@ zflT9lhrUyAJXHk(dRo&h!b6P#&%{-OLR#)*0W*d9^~@Jn^Zxlw6wQXHFJUCLnw-xiR&0_p;2pFYk{(9Bm- z>i}mRehXA27f4PReM2C{wr7ND<_M%4@`lslf-m@=-ERZylA#`(Y2MV`hJts&Jwv~{ z{}i?M72d)aOV@1pZTtt*tZ-U??H;`+y=`YJomAISIy`mO<)eOgKwR~d7?$DDI0!?E zW%f2D#a%%?8sXh3)<42_gZ>0_-Cee!w@IyBav5a6>p7xVbCMAufGqH-X$Ix&)2;I0 zxrgb{qLQ;1pINnL(c>f^epT}LpJSw@18Jn=zb%c3Bz3&KCq(jTOy0RQl_0nM74Gsq zqF9;0W&Ed1QKv(`1}ezJ#s=p0D_0{_dQabE4wJa6 z(%sG1*wfWPpb=Jkm4RiZYk!(C=`0n;x_}JQz+CJ4wA|qH)c9FKsIgq!}7h(gJnHms1jajR>CJGq)6R^Q+d((vA8XuSKsh9i)ynQgoJh|Ut+30LL zi(+zPE(0~zr$hgY%)i(B=d&)*e*g9lu1oBj7kgv^Xv*7%o=x%#GEg~fOn(0;2pqD5 ztQttcHM@Lq(Hjo{NtUrCmdZt=s8v^&%Afv091QXhB5rn8&g+Jbq6x`vU()-D$Q{3z z<|2I0Yu+n04XiY_ExvP{Opx8Vd%3Ogf18$IH+ogMQOx<&kI@w^Cey_ER}18KLSs$q zt&dRU*ow{hZ3EbCf<2ecym#5S{2JC8Jy0>$_wp%OkK=&PBnG#laD)kqQ319y==OHE zIfZ{~Q>EfYG-ldP7mv(sc!3^tUXuOYX`jqNAXWt}R1g)Ba!rZLONug|=jarJ!Iqy9 zh0*9K#piN4DoQ_97Wbx+q4o>`WMAOtEnl~M*vTX%?g18WrQ>LXA;}egX^=u@;-z?V zN>P6{*4x2XkfEr@WU;J2%mL3ndQ!nn7cns?6_P0C-F*e@Pq)>#w9j|*Cl)X`n+xX%oWe1-7=MkA~f!tDQd>+{*n-j+3(H9NDob(+2zLiE1ZqYla@m1Bds<(H? z`?r-6VJ3eqT%_J_{P=IT($;UUAwwxrwc>`Aa=->F-^}^Dw}u*9RrR5(yYlO*GizHg zHK_#sze}Ia0%1>rQX_S4$55>Mq{h^y$^kN@Mh*VrMCGoQq}EdRdAs=KRwL8nj4>Qo zl1$2c3{obc6^dpCjwCO0H_<0{%%XR`dK}aNz&*!2NOvCk@|z(v|!po$HM*dUcPZF zHW3>sOz`H>bUEQb@!M*XybED`YbNMOHa;L=Kw)zKzSzmvUJSndc72R8N5~l?&P>Ua z0d3oz^H!GQb0p{&@-UZ!{6^)9BWVn-p=gi@(;O`xm|Tg68BDloOTOQ2j682jII#C+ zPyz8yYneW3r(~WT?K;uBG)**nt&B*>dmR&tf_=}L*gLg@bIT$KfUa%cWl)$6%2Or0 z@2m%q*OQ|okr2-#2ypKb@S!2)!yzj$1B&meA8S9RA5x2&-J_^mX}wCnqtlgn)J!=e zY%lhFGNE-dQoz%};05x3IFT2AaD$EBk-UAOB>O)DkJG4)ggouO!4C_? zLBYHmaXoz9swEBAEp?NqaaeaRamP(N6aT8-6Ah!rlgH;+O%laq;jzmDY?FmLxw(>1 z!1poN`mG>1Z@|n>#@=-D;N1gVK;E zn?@i0AY0xIVr&qkr?)@DIhiJk!c^!ei}vq?8P+irh8Vb!10Us z%n$F+nLZ?wSXLeE$+_mA)F%Hu}?zH;E z=Z#ASQ)*p2IOj%6o6J_D=woN~9thV%jWyRZHKOy&e`vx^ zq^ZNiVdvEY`&yepNfXR*hUZYZu&{LgwT=J`tZt+Tt(dDPXH}&B`>I-^*qlat5Zs4@v(Ohc zX-&Imj3;e-&}I6zjjXi!AkOQa$mw>_3MI8nJYfz^o?lyy$4S6sRN1Jv*U5sw^r(rRj&{Zl}%85q(t6)n*5g(VSj$Z}G3b+IWkMt<;?%ePiLXYDA~b zWP6?MT9oatQf_86SJS}m(Z>h2JaPYIc}IM2k4@ z``-a%VpY=ae%_?%{Uj0cvTG-W2+z;VQFnA6KoQns7)uC{p5k7%=>e)cO>0h50Sjz* zM4^B@AQM`Nt+ZpMxeH?Kku1#nHD&~Tw{J#QToJkN!{#4=#Sg04X5~8UMtU@3*RHp= zpET|%lf2BT3*s%Fx;?ajuIV=C$#}VP*DT{UU3fYtaM+DzW?c(EKOYKt+hkogch=i!_Pt3p zXHLnuC{@*{uQMuuuO5|02G}RcjpdQuio$zDG$=>&xELYN^hw$C-?ToS8-A$77V!zN z2@L3cesiOARsRtnVE5`7vgZrKvv4uZhoSs>C;#IJM zZxyD^2-Yh<8LFU2O3~0u8PkNx*wx6U!oU6oIDx4X1^P0WO26_GR#WA_z>`P( zdaeC|;Zm*7>u`#zXE+V-#NTZq88Z-o>y3{;Rld^6xb&&LVcN8^$X}@B>+v?FG@6>! zhcsV;j758aGw!E3Jd=kWA!!WrCdOt^XVp6&3LszHY?GcR3uXYZkN5~S&O?-7 zKk&t4o$^m4*L8g3Ol;oo8g?D;>+w0=CjY+PeU0Mwnjfi(6Hcc9Wj6qx>hO=oh1FSi zaq_~y=L>mF*E91(aM|j|m)hykbBP4rJ8>1?+X?Wn<^=xg_e;bRNQ6gO`H7i$r+Vx0 z{oHvQ8!T+s?xu0e-#XA*M|yhn2p=6TecA9Za?Ztah^w&!uY%65R-SNo$we?>8lUgp z7M)z>n!GU>kk}wAGGtva1Ct?HhW8%r^o65yVXLqj<&%##pip;QWif$4`LjWctdc;= zQ(e+c+Bk(MGS_kLey{7a`papebg1K-rf-3^=c?cN?~320Kqh2UE-MHivm5~=)iD0iQM1!oc*_c*gQR7^LGjTotZ$X?H=DuP_f6QU6*YB&$*f}%U4(S z+NF49Feb@~VRb3!S3wVJUscB8FqBWqjMz5*^-aHZsed|WDrR$1(YnFT)!)pQ8{`g` zUvlsbai1w_T!U{B^j{hblJ6$bFLl&yxi*Q{)O)~vamR%#4H*9TuwX=C{`d>$8YMWA8^pWDoSPtHXr~8|jHxe-SIL38dn}aE98fYy z*Xdg-!6FEeA^BWlu#~^d5?I)aEqfJ90sKb}PGO2FLz(GD07s7>ls+}5v@aMRM^ste zZ~c|;TbeA)&cmGW$l1@=@$qJ@#2tYPB*YAMcSzFI<-hC}IAI4vin$JJQXpwdZRu;C z+xvAdHD~Ci`{WTBCh5N>lGi5D&hx2RqaLCcm2SRl?;vrM-QW#%uS#3SMjg zG}fC^GTLO-gMVVth{*902JQ|(jxo1$D~(G{km@bLj0OfG2S&0(=qzb71eU<$ItoQ{%uRS*R4d5;b z_kNaJ@5BI6YVBv&ouq=x;Jmq)bcAk1zcY^>c+5Tckj-3Sly!NtHzc^3hV#3@5+QsC zotBf{#Ko-t#5g=f4LY5Fa?Hr4CG~byalHnMiOT`3c-6vs|-NkgtgW*M=2@1dP zZBQ+3xLzaMupUHjb&DW;b3)$6riwBr@H=P>&&i%HGFaiansJxZ`S%=_UY3B!R;@Lp z<}wZIWCz+tiJ0K7z%JPK&vfA3d5TC$(i_dsl&SGDzAqrXmp4vJAyjXe3YT~RVG02C z=GOAYga8|xI8nc}in8s}_k7|bJD}|pqOsjN>M})Z+~Vbn@z;)gsQI3j{Oisoe*rDX zmazYX*Hcr9L&+z_cUP0|8DC5_+lSlz-`Q>qUX`qQh@QSZ>SO78)Sh-9Q8eY=TS^d} z(bDkDvarYq_bP1-1JM}CCwm3+?~9*zRGUeCT}#tmyCUNUA_)(wQ@U82$uCVByr4<;z*hnc1V) z#$J)uZ|R1Z_<;SiYb5Y-1X~~213+*qt-y)b$NA~=Kb~#QG)Ne@>_v7}f%0?YG%<>+2`NDIr=P5;HQ$|M%1ykWQi<*|HykAarP3Np8q*`O(JgYOZ2r=wi3Vc2XuYwrVjH? zHYKHS*Z#vV2eKWxuLZ8=9I+o8!1@2|N$2vMJyJal%QzpB7s6_M-lWhGEkW!PLN6Cx zJE1E16hYNBeoipDJ8sPIK`Y}(jWj+`%UZF7Cg46$wVp86;$`N-*Qjp0$Z8s_9w>I1 z1%hgGKUGH)eyvHub?SoEKW{|$OPiQgF<0XmOCeq zg%SYJ{z<(FaJv9jJrOtCnULiN@MEb*C0$1KsMu>+*W@KTO4sCm>Bh2C*RG52s7W`(|#w3z(ii9 z`8R54VKrn;z|Y;gB-;sXw0+UhoH`x=^Kn-V{=G5@JLQJ^qxYs~v`J0_cDTnsWMAbM z_;ufHI@v;MiPipT{G59fx*GVtKd&fh>kk(I9k9csM9}xLT6q8(Pq4`=Ul7(Xb=82x z1Gnt}tKt*N@JyKV`r^WTkv};X@LX_j5CBI0{>}U3x?l$)^f(Ocd#s5OI+Qd7+aKbe zruftLzlf7X?v2mOYlAMv7ah!jokK6#f?|R99f&Cv7%7v%0->u@J~WI-emK55il$AE z$9AUmKTxUix?3)jdSv7DwY?3G7s}o;r+hEo;$G}=3UiC}m2s^({exQa_0rV_Cm+n<WeE?Z%yTvxD$5Fq(Tnd!p?Ei~nhP)~a{ z^!~Cgq%;pO0}eV65Jjs`cJ5CYq1TKyBq(!{_m4Ryp8yjS4ABXkE0b*?h^^9E;I7>S zczc8Lw*=J+>g(K&MPAz+5c9D16u#eq^ zUnTS!w{KbScRXmWp;q6DsQobFFtWIBYxd~goxld1WUy-JQ0n}W>(CfG=+!5S!7s(z zNL2>342R3r`(*296u@kFmT-uH1|P|$@^k21fX+JNiqgli^y!AZLD;#6V|I0cN#cNm zz?Ghi02_(-3Kp;E@~4`&9c!?^f3)Gp*CkFX$rup6a5sLuBKlU$3Psv=^ANYKhttjQX#}R!IhCDw{ zTRuatv|HYru~&*Hz-@`um36wciGdjHgi zOS5}e^=*^Z%r=BgQ!&l)SNOU+4#HonZ$nxeB}6LGuasB4GLL@W@wXf+A`nix6ME;i z*I=tYj;RN}{7Ln|Em$mwU?yaA~&AlALn;1 zLjJfH^@oz`6>jDH88^meICaxB|aZ{Z}82?%dkey4^-947&uC*zo9b+-#3*2mDZ2$_=GoofB-Xz zYkNva2SsoT?Tv^GFHK2)9-k~wecyo{|B!g~KEc53-;MWpgU zSJfZrR$+dfPpY$cL-MKhqfJ%!I72~Maa6I3l~VFl?LS$3=c4-DQVmDC(SF3$;M9P2OAwfC~!4gvQ^b zQ;P^wf+s5XA6fKuu!{p*cy# z;cQZ009ltl#lk@q$;Q=rZ3x7+j)X$Rzd?&@(0e*PHUEgALza{+SC+jn`(gg0MAA0a zr1|EnA}A};?6~R%xd0P5YFHY7uqg}pLO1}9PjE^8?>=#mb2{4@^*-$>e~QlJSJjugbX%~w``q@~NP@8WMK_pV2~TT&J1^CWr*Agl9*W&rDyL=!{i>6E zJ$={DunWw}C)Jg!m2jg7{>DQA*k0w}9%j}#?}VVZckYoH(7SbHHy&QJc9QeN2v_~5 z8+$NbUA)7Qq!h7AA$|P2sOlg7#Ry|t|B2%*OL9i?+TYLcA@R5BU=x6Vs{rlvpoo>A zsoF}03I`~Cb+j~CIr2@;$}JCMt>;>c;TrcO>GDOb*h!q^WM!-14WcuzvgG3>NN#^= zxg58{_k;>CE(0}m1fJL;q^GZd^1G*5_7g!`=rSUa3%Hj&>p%XHD!S6vh=S`q8K-b} zMgIuk1%;q^P+vmE{ER`w;_`l{`W1Dk9ODw=)MUUv_=!2>^R#g1#bs3_*s1_|bV=6u zxA~3u$MacR!-63;;p**%q+L9Unt2y*J(*pvI$mb+R4p}gr1|}pPhLx5*h1cP@%X`X z+k~!NVMxy!=`f9ob+_U!&x`_9`n-{D&ewwZhykCIe@lL6Y6Ghedui_#Knf#W_O7@$ z-g!i>w>aPsQ9UoX*F2lCSS3RpOR>;6myv&mT;RQuOr@qxq})!81EH4^*AErs@1NOQ z>O(;c_SoH+K8jxkSj-1&{}CtQ56#Kwet|g)X(q47x7i3N>Wq0Lb60`5@su^t;Qaqs zy6U*5-tT|6(Nao>gdkD^0@5)R6$xn(X=zF6W*A_Aph&k$clU-OC9r{X4U`z&J+}R> zpYM z67;wfLXk7DDSWyeLA+*Oq*_00O^|ET3%Vn)z_#>Qk)tc!lXBCm_?AcBw%9hssJ{L3 zu;2dZ{_S)v&oUr3RmI4Pn`H&A25k>RpHF1Dd3~7Pir{dX|EL(N1*(|bId8^wlRcIL z7L_BC7q25!=!2vX?Ky&fDYaMew;2z&uc$p>wppsWOM8)X`07j09mQ`kT31ULB1CIh zgx!*el%D}BcVsN@>M;C(>`e%M*Bo+04usH?2@V?)& zbykg>SLrf?e_kvqZnQr<$jRR~`Ds<&qkZQGQY95L?Tb5kdm7xuK46A94*As)9;>^K z3n0wu-nmoPK<*Zt)ee`qIe_jLt7yq4jFX<^%b>R1BeqcF(g3sYoceWVAm_NFWBhdx z>3R0ZWebKV7tMv;{pZs@Gus{9c(_NZ)kK7nK`>!?uI{Fk58>(h0yjU{}g&PU8F?z+gXE~4fYzti?IWkpVW*$h7p5Vht+S8PLf|6+H?w7 z$OT`01V=~vkF}OXMM42*&>WBVQq~q!b5#l75g_pyY5pA z;n!BnySrg=Vx-ei_JTR$E&={SZvcDCKOsTzurku^g&%7ui3IEFCCto(Hn~5qpZ+Uf(di)a-fm6eUhFzfrxosT4hx_~dbna7#G6@@U-0be zG&D$wl)gEb&or-w(65MCHT@vVYO`?zZedJJ3Fepp?sWyH!?lWwo01H33Gcu?&w&7D zR1!GkAIu54INe2M7RtE8APDR^K!7)Uv}7R7U3lp>g?#)>S58`M)uBK?SqGB_Zcq5+ z#~TMvZ8HL-?F!3(Yw=~dZdSZ_vcK&~uU-s6!%Wox?(l$ICF4ut3!A))YHqVNgsFY* zAYaYeJ^Aa^@_xsb>EQ?1z?K=_`*!EG)|qSDHAllJbvd2=nA3&4@iVIseIHBvxklS& zCHyLF3akN|0m37A(s=Us%-wS?A<83KPdvenlGQ)Mya`w%Ks4>o94WAUre2v6$Sg3L zdY!|9Q!*=nq}XE7%yH(%2+XZ~HK2 zYMbzZvi;Pv)AnieLJq^n0Jl z2&+17UtT|VT|7LCYrwlJbSTDooSHi|fyDS{r_0@r&^xNc(FpdV86ONrj*~_v*h2Rr zVX|E+5w1;$AhrVsafDM|n2u0|$7kxvo3q0Gp2tPRQ2TgVIjM6&_37e5D0~Mp$$y*m z(;F z@CxC)SBJVC&>o3sxe?ix@%XEFl}Q1liR_~G&O6W?fc!L*dwyMWX}XY@$Jp{>#6@zJ z$CiIVR@qHr^1)ef;=+K1c`ao`u=y`#if2EA_wCKET8Fkw;N(c0W9vhO<2$Vllb=UI zR`Z6(SD&V38r&MYzmn?K6?Aa^re={>58?pvdFJnt-Wn+_3j~dq;Eg=>X|5+fLjN7N z*v?dpq3hZxXu9WVt;D`lR4#hdnmxV#_x^T4(s5a)s)?3F-e>wsnd%Dwd?i=uOW|!6 zHgbSgQ!meXRL)Qw#%>$NuMHrmV|}}`J-DA-0v`Xyd};^rDgag0C~FQ>PQT}TngT9I zL(R-l&_fB718{;^&hQI{EO+e5rl~M21l#T#v;aa)A&rq-XA{a}j@;b0B#oZ>iub-| z1V841PKt(H^y_qq(gk9;H<`VQ;-t&jq2;}!y~D(Mt4_?E85!R6TpmO4)Smjy^rozH zrrDwEtM%+aL-ckQ?{bior^n%QyQx<8fx)wbcRr`V9-S$n+vwqMS#)U8I+P-qe%c`pFqu<9L{LZ@-|G9)aCi5G^8wesi zE3Qjh^dPHPexrk1c zM(y#0xXp2W|5?6i1R`-E-uNHTso13k7e7Jtw8K*`m6%Z!mS*HW+Hu}b+%@(Y>LbJEh3nEC=XnPj?x>44&E|KKU zHtoyPeeSCkoDDcV%bD9jQHEjrTKg2jsT_`W#6wtnkSu2z9PId{{Z6TKX*b@}&#?4`(B8&-(Wze{ zk6{2MBgG-ua3lr1gM9m*%cf-#x@%Ir*VzN*NU1Al7zeyA0ROL85cV%W^uG>*e{~&~ zlB)@(V4naDi2LUUk8EHE0P}wx0Wh#b07{}DT>yV02hX#C8U?iARZy@$fQm#*1W&P% zD8bW|;2-{7OaZnST=?%&06a30F7UV`8v7p^YK+{Qll5>nSe2ptGn`@rw9~x8R#fARDQXby^*o12si0@+#xTkmQnzlRxXqK{cv#syzp)V}j z&kdUMMxZFqGTeZpw=IxQiuOmz`5C7!YN5Q-gq*ZRH_EHOJUw(vx$*{`7wMZi8m8xU zKklA7d%>17!iKCfX@HzZtkxs8aba66U8%4_-YavYMAk^ph|?-?Ye=$x1!&&v9Dl~i za|NL?B7bPKL|aJ_Z##ZvJLWgyH^@UNPIa-=0NMf#{QOz_B`Y{6u_s3f&wdF`!fQ^` zu(_1%9*mf;*rLi9Lr!;41YR`v$79emHWwEJ_?14dPdm3(5S%4uI<%sLdi;j1znav( z2^d*($9s>j9Ucv4UO!tA8ecY(H@Yaf&s;Dt`Fbgn-)h_G@pE)m9HmT!Vv|VVTi*4> z@gS+=MpgPZEu8akSa31F&y!y;s{ ztPAntU}Xg@0y>bQJpmxz>SK)84>E&86wcBV(%U8Yt#iHb{?q0)B zz3KWq7aiUx7&6s5@h|!mR@THgh*zx^YW@)g|5;8?#(LMY@i;nL6{$I0R9rSeFYwtY zW~1e51;y~0_hdrw)M`Qic0SK=!7sLHJ8Yj=#pa}<7FJ(`Z{fPtX?i>>xiv|?@yM|9 zT1oj&_rsyD{dzbx543eQGJ7+!CbfHi)8#4pJ00PV5e_R9++F6kGI{4NbMMX^EPWue zO+IjDcN>FK?G6sxtQmm9fiOH46d`)Vz0?Nz^jj7HC>A`of~un*^Y!vGi02d&%f12g zD6J_P(cHB^Uy+torN(zxLIt4hKkia|)YZ<9vYbG`ZOAcQQWUgv0HukL302oWYm8$w z*dyh(QgtMQ@^}X}w0abLxGs*wDL1`}p{o6LOfklvkl8Zy;l`%9p&`}3tc3K^d2Oh= z1ozX`4<6}w81G@dKT2L-Cid+#I<91Qg$vWBx3n~WtTcd`Bo?Y0CnEOx63LYb1PFMKeT;c48-HRH@jyy{nW5} zAE>YG4{?WDh4%m9cG{?NslE3#aeh<;SG{7byf4SyEsJg)9d>%&-ndQp%;lJQg9pbA z7@VxDo(P1zj(rW|f-Le!n5aU5iYh3`lYjeuvv#72T0D*5J4svEo3Bkfd1uz5MK7Rt z`9d;Uv=Z-sJeq!FR$iJ*qr>{iSq0$HL7sw|1vMS1MjettH+WrC0r?%xuCs>)E%RQR zm&Iz9YKOHhJ-z+Uo7;pqWVYCUXcI_3lFN^=OG{_i8gGiQIK5s`A!M6lxtv$RR>YMw zDxj3|dklla3*hQ_=IU=0$H~bJM(mqFejNlYtD&}PuyI^Re(y3ugB^AKi7r{@0?JOo zy_>wc0eUd0AdrDoC!y+wAgZaq(xdZ!kl``2La@i|>U^F>!L438XJ_7U>+yBlpM>eB zba2WP+^Ss;?O=*`0cl`HTe#rpGMGT=G$oy7n#KU`7ntE9EY@EPAg%f0_f5l1sSS(6 zBv=FhL?Fnh7#7IN@CZPB;DxdS7x1|D>QO*!(`{nNvf;{L8fLOi##MQWE zfif=552=G;_)WRWADZ3r&ugTxwgJ;h>^_lDt$WW0KOd;tK2#L3fc_gk1F`Bj_Bov< zI>~#-Rj8(;(#>a|JEX;?{8z`<%B_8_Z7=L!>dBDLoc~UVFnq#642F8!R#f}!2kL!4 zk;W7rmJ$NFapLy$BX7Pg5NagQ>@_@cGNGwP;QH`obl+guUXn# z2oj?M;EQMelH3id9t$3SzkXAD?5ahoI{cLDYzZK~{6?L+3?HYi6T0u`--fI>NGu73 z6Ulw$+rJNw*4vsoC?Aa^@>lSeN+HKKp$Jp z)pM!(_;BRjZ3vCN4HgQTq^`o*RH&2gJc;czF{KLuQ(@Xs`qNAm^4WjPA>J$q!DeGvC^H%#?JB2Y8WM*%I~0h{3+o3CnG~ar?gvrPl*PW1jmR)ep4ztQ^qqmvu-sIX%`2RpS1A`;tMV(yAC4K0q-jr~DHg|v{bz(N@BushdSxAg`- zf>PjgKSqYP&0!;`%U=N*YJVZBNlyf+#Ch}31UE@L+s_I}C0jFab<8xlufQc2ItNf& z*KQRB5!~k~GP)`Cj=LeW9)#VyUg`8lpxl@eDa$k#{)1}eSy*|rWSOYO&p8PJWPAN1 zvwF32of%HPj`-=eduajWxvw$-GUUvAsvI~>YW1IY{fVjds1;XyXOpZ_y?w-uW8|Js zka;l!4eH+*&qut!-EsV(B_sSSWvoc+-=-5lsM5wZ`tTXJH@}y&igRU~U5>c@=2$Pr z)N{^Sa<0CqucUr>zk2T^X!okxc-3YwC%Rz9d>K7ygixEsH5EqBWPgwmN_u@q+7ssu zCtx^m6AT^P$H|my&THfVeT0D=7Xqp;OgO8y#uWOXlEWy0N!qN8l@7UI@u@wEGRHdG zd2UGaGWjOG;R+H;-jlw5QzyKx1VDt=o-FT&DlEHiD276FzHpLSEeP)gxElE?$zRQk zMd(fylkqu5a;Wb8E&49)_&7l2{G3mUH>%A@Z%LTEM`G}oKm22;Ze8&|qkO{{b3ivK z{JRrQ`ZvA{g38b{)YXUI;1-XcjbBn$WxY_~W>R(gpEZ`8y)m3us1J9w7BHO!-KTv6 zEia1-GU$%?3J(Tq5(c`hR@g$6$NlLflT^K#7Y;k0A5Hh`h(uVz1(HkG+)NrH!lJ$^ z*ya=s^!4&u2>Z-!Gqp{VZ)9kd#^=KfbnT0|+mztH9vvm!rCW`!)e;z)g(dr_3}!SiEGI zC>liglC9tVD+DP1P-Cf_Ex3rL8PvJ4Pxj{6e?Alet~mUa5Gxhr=WGpmxdBL2cLDy5 ze1S%%k+;;t*!mAw16qre8MZ_mYrRH!{G3Bh@6KHpn0BB+5;^^ZMc zIqoZ_Omlw<4Oxs7d)u-1k%0woioT01M~}HI7R+%7!&mRbom!UvdWyy;#&>$;I*o^( z%4R#HFeo?0qgpdJ-90LCa~0}H9jW$K0^v?`ED@yD$SHkkA|W5GbJANmy_-N-uv1enmK`VpEKyUh6(B{ppA1PvQNi=#R#C z@k>4?B4j|9N|OH*11g9nu|Sx1n6Gvn>e}GQz7O&FRDEc(BdKZYS1)*RE zS<355HC++;OEV$f`TBCZX77r~oS$LiIE|uaMk1xd?&4!w#lq#|7JxN`^$UJSGENeNdcLzZ<)?5 zulJXbLK(J`o|crl-zWKMo1=<8d_e{^2efoEUA=2Y+Hw_+Rq^-c+O|gb%AyHe@d^bl z$ocM$Y;~NM4=!WR0Y%qwL!pHyorPSA9aj;p+P$uShfwEWqro;1-jXyDq77y4@kK+N zQ&Cd>j?eI$VN-HaiVerALh#&RW5srG>DcW~Anrs32#`g--ZYT7en5bb0mr>Mx6V!D zP+|u6pdt|u*X*DwJFR?rGLH2lIz0K*fOL|lJ=t@1n=j-t<`*$75>BmxEO8Bbtt~dFP%j zWG2YDVFK)Nq{{09Nl#`DPP5=oLU-x=a{Df6#iq!o;(X7q(G3W-yXElUnNtg7?g(w8 z&eUm-Wg=31j_OjEv$Rt=;UOIAdI`D^INGzEi1fTacaovFT{{svxcdfob5M0463Y1+ z=g$tk+7Mvw3*-z>;!Gz2>QNFthHj(5MHs<%<1D|>Uho2BCdmN+z7X&7C4MTdSa&`n zC0+t0pt>)`cfy2QWN1JwJ^iN=^Ev}@$@~}hc2c!;S(ko1;IB-{odBo8R=%5F*_Tk(|Nmlw*UG( zJUlrlPp<9J#k+R}9F_=&2LmpyR?XWa9S4ht^!sKrr#fTy(y8monZyXK?My442wto1 z(xy2tp4j1^7d^_I@h4xUzn#er=ugQ<$4WZ^O76QiN)KrdGFHQ8vb(o18AEe80~GsL zhWhgCvW9`?B?^1z>oC>h3{?1&l{TNeVs-$bxX_V;xRG&oHi=+DXVCx?UB3%lf3m|Z zD1fUKDjI1!<|cww0OOclFPAfv17FtVC` zm8-meGuIyW72|vXve4NFeD!r_TdFAfTz;PoT}93U`GUGnT2~ODIS^hAYzh3WksE%o zFBR*mW{<+B>ZB2MA&MUqI1)d)pEgZJ*M`L!oFpuOPUSp=6iJt_2?iJxQ<*PC}HA0zsJqk zh)zX6ggDz@8O>(9poKY(Gk^i*pzaytu=2B+Oa>e!wBsyO;DAxebAtE`*n4CNr!!A6le$U9?;~mYSnQ0@Kbl|QS72ChncF@ z!RdjL-cx0RuWDN#B5E|lZ-wK2#)W0DShrqXw^GHF7j>v4Q?573a{5h&SS#cErEfH` zsIAvyk*&fULh?%D7TLdkYCEkQxSYp^?kQYA{L=Ik8VD6g7H3EBtk_GR1+wFo-H!gE z^;W_}349TcH(>E+`MOkqu#x5E4-kZ!n5WiIZ)#R93%FJ!WnBs5ioX*HG5^3QhuEpR zs9};ATzY260IC?BTzg%AQ+L^?GH+X+a@J+u`9Y&G1VQ=~JZ?xG<{8X-Y+P@9C0S!1C{3=200J;FRcPHvDD4D0t2Gkhsu4P*el99BjbhMs_*1UJV`?l8gSc`w{s&8Rjw1 zvYLp>lTc9wppc3*pmb~+er`sA+?#$k%>jdlTi!?ofHjnA-i$}Zf*bf1 zr$0U>PR-2-Qwv_6dNCxLWZep;sq_y`4ZdiJ7VjNb#5H^?euc6?y`rdWOEmi<(ywry z`3@pV>U?_B!;*5f^`#bNt@1Om85?R`K-Ak{M*!t(hJMxN_ z8*6%9-J?W}epPt3fWLnEg8}64$ZR z({j`mdr3tpIpk%*uV;02(~d7XL%q~^(0Rllk1gr^RTmEg`lgRP0Y97DRfRjXGOelM zk34H@{ya_V3uvjL6na2igxflUYEz{zXL-h$^0?+RJOJlXpS9t1hqbm0Yr58hP7tpAmo`%=^D?8FPx`Mw!XMe4dfIa*O=j z5Bz`6;dhTPKI)7a2Sa*MQ^NEav0lB6P*X99p0S%81zcA8IdR7n z9>gj9AB!~9WWKvJfiRAV%(!7QhYTW|Uj}ph4=)!#+Cf`eF8^(G_fuplXm&Xo&R8z{ zla=NHiNEWo!wT4|SWj4=r@knS@R)sP6Gg%e$71U{c5N;D|i*&0DWr* za|`1-gjmS&Y`D(7_;%7j)Q6h$k;l7wBFuCCO_dhYR&G^~2aMv#Zx!n-A{C*N*Yw z3}7v4twECymIzj>G9Q?;Y)7~K>Ge|41 z?X400qF1z~b3y`b0*EpIhS-M(DNzugWnu zoqkfdwA`%8RmOjmPnvA0LdlIj^=>Se9!2CcZ4Y1xzi%sCjQC^~y zx||g;y3dUVX$v0|b?@%KNe!Kghza^J%siD(2v!|-4r_VB?wb()*DfD-%33Hh-XMyu zh^CnDCN5vZ(bpXPt(Yd?SL=>z+V)9^?r29GJUniVbzEWQY#%{*AjQYlbvy`%y%zw5 zmfrAnl1w3uH!%z-_?KmalgSg0vyvaclET8X44+U{h7s=)G3FFN&Kg0R<}N|=?k7Rg zxv_2}?QP1=coENpUG4#Q8_O(+c%|TFj*ZLEF zwo9UYcV)%~PEYbDqeT@K%rR1Zg%G3VzprTYY2c~9{^sq&x3V8DcVuzAi=?zEJRS-; z8Z+OHC%-HWm)wjFy$CKY#wt*N=6&x9$lofXZY^GW9Sp@+&jZ@;t%*bc?Fr_2f~UKR zqzld;)GMz^y45f2t-@q)$OEA@Zc<>7HdrCZ0Rvec#xNtYa=oz=%Hqi8xIiDy$=J_M zQYRHZtp9|!wbp)h|2&!EPxty_0`M|X?Nzviq;0SMrx zE!UbE)XwH63w5C4Xt%LF@2R1q47o7t3p|kiWMy{(;iCtM)scCojvNx)Ufb^&&d{Px z;-9%rQm8cENIwsKeD}3q(zR=2U$d&FGK$FnD$o>s`Nhm0UBnk_CkiSzTX}zc95BVs zj-A_HJFHus{asNOGmjtJ4lx_<2(>Kd!^GY^?D`qUoD=6d?YpHo`zs@I#(S+lBDcDLoAH>V$)5}?k^o9Q|m>!+7iF4oBhj}zhwT&9lJ9eh^4 zmG}25(h-OXAi=^Pp-V8ptKZWBTU_%VoE^QNeiY~>CleC+f=bEy`FCeDL}(6bm+8#p z>Z=G*iMguG6K%le&^0(&4pfGRFaB~{G7825WZ_ji#cC82{>pt&Rsh$F-Bns8`5h`j zgPb2%ja4=cr8(`qsk5{`{qNqL*)wJiX9{zl-0RvKLEnu-rPCz{PXF>y_fj{DUw*lo zi`XzWl?ZYl0-wE_3fgRTp~_PxpoZ|Cw8pG5ZV)GLs=(|l=-qQhOPk@BrMnQ0Qnri3 zY+nsg=K_2S9>U1)>{dG6+4CP`^Qt0L3@7{SW2 z(Nr4fDqtH_%6z1S2H+acScv!vej$xqu%wpL1K6xzvu~dWHT0dJD3Va0u72%f1jq12fXj#$rYQJrv5R*g%2Q$3v z;N@EvIjv%@oF)tjwhZ7qJKZrGc40^q)@B7-OgYE=-8@@la0QN1RtB5Rku&wvz6T?K zWcGeWTNam8_E}p5#x_Qk0|cR0vSgr8AkRM(aD&ay$DQ3TW)^9jt{W1S_s^@(V;1&2}9)I@gxLqJi1t@k|Esm(Sla) zoUGxK=%5Jh!VUHYJiE{-QP)00c>zlwPOig{0I9h za0T@A%N$^@2|F}55r;RHyGEEyhiI=yowt%zBE!9B8c`3u;Xh@Voq&u z1h0uG{W&Az&8*v0VO!#dD>GLeZvr-g zc@OoSJ?1Ah+%9X+|IOP`b$@$Tn^#-G_!k5Abgk|ENr}KSr6f-1fvnE{hC&VS}9P6 zxUQ;>n(CXiK>Mz|m>XG7z*+N*nTHhPxp;grW^}ngqX!UY2?Uc?M10$+X|Q}e_YOt0 z=W^y4-Q^EgS}}r8MJ_;-uZ(;U&=ZjBGQ1=94RpL9@87rYwKog8zdNrKT*F_}wl2Wy zC4}Nd*Sssjp860q`WesbY@gXHF1!5Oz+ z@{Vh0G>_KbU{R#dC%iRPMCOW7PwIWEi~YVDkl5~1yjKQ(DO3r;7M`NZz0KBByp~D5 zR(ms#;FrmtxPOEXgQl+zPi8y7e27XMC=a{EUAb+vVhBi)^(?f5Xbp;uRo|+Iz~&`w zO+`&bfROau8%g5p(yU(08`!0ID2*2ZHf#Xai)AnhCl%2D&&FF{R&J&Q(RN#qSP19+~IGN zvjbwSAMhF(RX(Kc3VhZ*(p5WHy%BNIxC+7-P+Pd?5vs)v%sUm2HO=hfLX0&jY}+j# zPY|mihzS$U@JF3nKKE^$JqR!Q+c_2)+nMANxI~9LrdUUNa|SeC9ntyE}b-JnZGsnS+A_4 zWt_(5Tm!|uCqsFHTrl+y3 zO`*K%b6OEtN~dy5u6K*}PTN7NI(6%x?ao~1j@ac>HtyOY?uP+%!KmVX5~vZN<(E+WeiRitngq}S z)J12;)-OKYzGZ6R%)~a;LnqUa0C{=2|uPY~$2H+01!ynpB+=|3?j{ zBIyt+y5-om_}sF;_a%J;o+veoyTPNL$?G)y#&}CkDINW)-+tw^GOO9kZ6%8UiD56w ziip|Fkl`HWd$O8&esnmg@i6gl7~_MbAK8@qkbto+P=9A3Tvqw-c|jTw0Q^7mSg0A?Aw$5&gAy(6_r&nQLaFH zT4XnX?Q8oMr%&;4Vm<(DVimhiTQV>ACxms+D*A8lWFFhSf+Oc=?C<`=v)jx!eYf8{ zG9GqEfoiTXZV{Y)kF5N81q$-Q&en&mkmizvK$fo!VnUwTVMQg?N^HjtmO^-cqEbxD zbPp-y%^z*@(Sz9?!M~Xl_4~h{e!xU^f`lGjrqN_>kIHbgqh`_7Io|qaxmgx ze{j%PR(N3e++X&(L)h==u&*z_;Jdqfo;Su(-n^NyCW0`qMsBy%uCfAjn)X+?PJ}6w zuJ%@{*p!0B-tt_JLof4VlVDJC48~tp`Gjp^(Lxn}ls?~bN z`)l0;G~XIb#jIT&({Y362&7N*S~jB#Wj|SL;J&1iW2;s${#q6O!#HXkPh5}odB$@z zb0LLeI88FN*PX|^>-j_atdprNd&BkfzNyI!NX}=K!?B<6tyDU3KR;x@TjPP zs(hLZ^6Ke*M~ZqwC=Vb@4uGQ~(Y%oCJC_T>v!x}x;K^@ck9iL7Q-u|i$NtvRGIDjI zw`~3`o{Z-9g7v01X^xXyst%m@$8R_AlJqR5B?$?hL4^Ic;a*bS5upktM^6V#jq;e5 zeU%GiF#CLEL)(jV8b7PV&lqE1tM{6Qj4U@fDVFn1!+g zsAXTP`RxDntSk!iu67;Jeb~WlaSf6`1@pb3c1Jf{Z|05qFiaLJRiqcDlq-_sN^;HL zTV#eq;Cxb&AAS_v%h9*#`Rq>nT)g>dQR`>X>`*=RF0WRGPj2r4LK>5^0Zbao_>O$A z;X5#Q>$kv#iKkc!L~-R;WJw$TelR)fRl1kJM#~ZpF>w@D^3PKRUGv+Xzd%wz}7K15sTrobHHl_^o?1Z?tcG3@# z{S!9YvD5}8CL~m75dMnE6_a;cL}k`yd-HqV-`rtiiG>Ac$6u`k@>3wr@T5TSVImq7 zIXGk30r(5^dp7`0l@v|=`NB%N@O01+GcUbGu zlz{a;-^a(r1i9fHTLKc~P4taT_r~Jkx~auN(P&4cF1k0EB)9KhW9^D*>OM`}5{1zo z75eNIS08apGsz+ZxB5&U+%78q7VeBR|C9|UU+Io?KdQnIUx_n+Hs`f62R<{=YPvXc z6oof#Jq-}exDHf)1@tfQyl-1EpJ8&QyKS20exi8DQu{8KobN>K6#2pi&GYdZ{OuBy z{O6H)Qk%3Mzu`x2IJm*vjd9Z2nZ$Qg zDziP%t=leAS1M!Rbz614;-p#}9K3a-Tm6{3A#AUi@NB#*9@*s8RjtOU`3-} zw}IF85vXjJbT2z`0XKSU2|T2+f$)U1_1;gTe8YKyaWo~-ive8c%UVW% zEuO)9@0ocwo4nPBfPvKN$Z$umVMA*ua^YyYwdPIm&ViL*r;)Enp~{!)8rlZlFtC$# z%PQh<*;F}6^q@|1nc)GSu1^4g%I=7)=`8d;=BW>;m8IOB0 zULkBv>0~{XA#8MjKv%=G@bUBIpc%COojku2MKMnFU`Uh%S{4(5;-Q?Bs5pO3A8SPe zgbHwdYGW8Uam+5Y)B=4t#W&C8pOV*K3Yy(MQSN!aV`eBU-hsL(3egEY80?FWXQdng zss~rg9pj*U^VIB>658s0XO3nd$rQ-Q8=1^c4X`+~{10kQhPXlj&Vm0y&1t|Rn~Tn~ zJ46sW4g(?M5F!X9rzVjffv|B1_@V|uAZ4$6UqBuAtLHEz_ zgkA6iISGPRb8TzB|A@gNB}(dur3HUqf!aIZ<@ONUxzx#Vj(D3rbvnQF8?qPw5}{AI zxXc|2)w7=Q`TtRp$at7{?Kgc}X}NA{Ft2fcCeA^98z=KjAi`~yp`!0-bC(}$rFVH9 zpE}&6YDu3Trsw?#-521)TYIh-1{*%DE4pQi_HkuGZNsF}bn4N6Sikl?$-v__l4G;@x5O#uW+@ ziCCGpeRc|ErK6B~uliHD>>4a86&}2`ZqUf_S^3?~;LhA{WG|ShL9O@38H?1qp>n;- zZwvZMs%`8O9=5f?VVax1b%~POCDuYIvLU&`?uL-C|EbuvIHpDr^d=oR65vu<~~yo1BmnE*I~w=#S7mW9UvSpT>X)S4$344sHPhZj=Kk6SB8pK~bT^1KX(X zE}v4oy+z+uJR;wDlwvPKeuf*RfjHxXtDLPK#aL+lk98U_^Ts&+_xOHm> zHzwni4kmL*u`;*r>dFZ&2Guxohq+}o6%GnDl3F-Zv@Qvw8qRDrE@RZCu}8;2F{gYV ze$mxf6VlCnz8;Q27y;yw-`wUO4ibKV!2OM$FoJuKL-DLIut`fTwNobn&@4tqeb-C+ zcvbt;Dg!9*ai3zh<@1C%G2ETZX3`)>vAuE+FauV8ixSIAOU5PIqX9C$USPujgv;`1 zw>5`ouzY3GDazY2d6ryJ+j7gh^ts*$r)Sd0hAJ)0XaId^seQ*=;2ey`GQ|~J!E>w)Ie8$cF-qk6|}~LJUL*d z>(~}Xp6nm#1b-2h9n!jqSIb7bXJ_L!qi3YarE8PMIV?RbL2e$OT*&(|$YVWfy+2jJ zJhy$P#-oZs@(2EcKzzGA`|d|TCg%Y>g@Vt-6}AJGdY-mOES^8f!e~+JC(*!pLurso zs{Jxk@1fKgdIc;I_FK9FnnE)e-Rf@q!^lJZ=lQsv`j4@d)kY6A08KJ7fVOqQx#DDj zC_T&Z(Bi}@jr5-G*sKvPb0fgx_(O1mShfkH#kJ(CpN`9f#Kx4qPb%*3(#S+$qi$f3 znCac!t9CPSK0?-EIg_$2Jw<2vSH$G!N6wXF!rF`W)uz0A$@vn@HnQc@gBv57-`nzk zITSG5Jy?u_o{i5A7~ek>V_4Z6Zld#7BQ|sSBs6!i`FIGrE=2&9IV=pe%isr(}set+N`;;uPD0=GHgx)^3uT6jT z85~DQZ%a+L$+WFi&%8fcjNePL@$wDB5cf;Ro~mT@AyMr6K~ksZ4k_pvu)@247P=ql zlhLE|@R&VKHlxDziP=@xBbTKFa-d*2FOE{ktudq=Oi+w)2Tjwz5`X(VxH~mNQT}U} z+RqtJ-p$fK%LSf7C6}UJQBb5*FXsoyje`DJQ(ArWih%X|*RRF(C8SqRluaN=k2?KO z;)I9O9f ze?^j)*^`2$F~kG8kXNOblZn=I>o&=d!R(RI1}=xbA3qFp{)+9;@K3Ot#X-14AACAb zmqh$n>a}$X)YpUWLmgV#BhAgMW$ym!nTZ>R1lUUDfs0I8$;hl43Vadt)6H>q3K>zw zIQ;#hs!W+~se9Hn?<4amvFc3I>_Sc%E8fY?j(VIzD(af&r$mx!H%I!7XN$I<6xC;0 zwGCb+Yfvp~QRK833TZ{U~oPZyL%0@oy~l2z`0=)#BJ??M5}C zCf|H@<)t}rwAcq8-VZB<d~UyQC=AHb&3pFBSMm!1SkAVy`-K$zI(9sl>$C#S zbow3JKSKA9Q|6a@b)_GUB-}2EfQ^WJf2I#S2O;iTE$>f%MqB!{HOy(ZxZY%9Ekt!U zzdf}796lbt>m6uv_pXVF>8oma$HV-&y7hydx38|7)<`c{1-o>P%Z?gNIUmzrCL)JT z#JfjpR7uTaS?KP?5;fXI#ckBv=Z>@;*{#`;Nf+R@rdD@y^*<|nVOEU~b^k`+b9kUT z-M1N*R@k&TE%FOEN97|fThUQcL@8!&j|1?{FPsAwJ_Qm8rlHSUuYMUVUDwxm1iwc$ zs10boqJ{3jz&NXNZ5#?`k^=59OgZCdKo&;;&H_O`P=mm*oT`7tYANXKALX?KMvd{g z)=Te8vU%n9V4OpYtBpM>-{`*rg~iIcmbjd$TDs!Qo-*0+pJ9{&_tGonW%nTxgRT@<|t-X75I1;7J0$f`4K z;TI7eTy|Vf2Z0{w1~tH#1L?Sko$)_q$EYDT|Zc*)Q7fYw;XEvdd?h zZ)!;~O5n4XbXNz|2Jf!K+#j+76@Ll1BAm#AqX%|wYrP7f9~IC@M~lTBV7;GZm`iYr zBhQ;K6BAMqVNH`hQ*IfB0XH9DnFnPEg;F|B?*ePb;`N>Wucfo_YpQ+!_}O5T3{dG- z5hy64HZ>P(o7aZbo-*48G_2y?*B}IIsJ4 z?)y6T^|?OpkB7srpe-K=%z@{|qLQegzsN4eo}!nRL$7_xK_12pl9>M0xQ>_80X> zF1)joBFev%OT_)nq!)+?#U&r1*Vj(QA|qd1dMWea1*L}Cf7xODMN>~$SS_tK#aM@t zz4fWuE?z!$#^RfQ>+bZNNKbW*#@{s(*C<-0^V(X-uM60oE6z(FiMiO2Q#c6rtDQMI zKlH|_K)e4?5nozf)SEo)A@Rz!%iQmf=Et08xB<#KBdTLo^6>0JNM&WzCQ8{Vh|^9j zx5zP>VQIEG@qt!4inKNd>i?N{@p)1Z40hvBKzgt}1hAkiqtP==NibAXQ#Inff8BA< zr>s1R=8umvm%}d2&b|rMnSvc8wd74RJx=C;yntI@64Gy)0B|D8^U~v&Hez+4R-y+I z$3LH`M+-x~N2|V4F%T|Kyb|I4k376ChaK^s&*y1`iuk=}j~R2vg0Z8$*&{d6@c22w zx(!@P;s_g?Pf*L(06Cwib5ErIpNI?B;oFX$uv3|&p8=+4Z$P@)E{3wvjJPY0^x<#MXJq~7i&_748@JsK7z zN#I@Z=9|Iu+U>>zKvEVtX$yIr`7pBpschT}ROd2NrWn#TuUVaN6f0iX;&AgsBd-3W=N zf-5Ll08!oEO7sINAX25N`Sf41&DiR1#|})Cm_vdgsS}l82)${3|2=I4h9b-qV+?A< zOy~>3h|xDXelUFr0H8?46RBB>%P)Y}kS2A=D1cJ{%Rs|T)&l0Q{2RIn>BP$!p)9o5 zW;RF6@!Cy&FuIj*(bJFpn6ACmR1?tvFdhkKN^}0UuE_bw)2nFnz3CDoa&%n5pTw~! zDsP~n+sB0g0?veCH^kx#QaZcW6Hxmq=Pp)GXWqlPKcdCusvT=1FMYju zD2o+c(ONdof&UB}nBX$iWcO=$Gq4&W`{RX5FrP|`2iWoKD5yCC1U z3|p~|=h5w~{Jg&344ws)+#lkJ*a86|Kzdg52Hgfb1_J2QJF7y{MH<&E{8=;wYjf50 zP>hYLce`tQANU#FyR_tBn?+NbC-Xsqj=wgX8JHbyAbI;(1QVn$lx2=UlKnsWE&gu? z4i}4zaKnx)DrZErcX7L4ps(`p&FYI}{|WA|(OHD5i=;^{yl;iN@Cjc{pNC~pjgWts z63UZL_tS#$`;xX&xeR${>YyhKH1_u{Ud@*EA#QI6=(Q)^6R|oj@Utb~bFp|x?>mv9 z-qY{N=iA)Wi>@5WTDvedl>h(~J{Vo^)-<@jBCs)XJ`~dz(Boo*0Ia_&83)koCo1IS4KUd#$Jq!ngL^CqX6<7Yv@+r-Uz)wx4e~+5R5@&~3 zYJ{T=^TpApn=gYrkF5mT`!AklY*`L}q03)v7nJH2Z<{=7%|LJa*?p^Ke7-&BBy`{` z#SCF~KxQPm*{7yfM7QQS`AAY=ws(Zt>4b!EdaMU?f%WXJMl0Ck{Y`_AedG%%s+yV_ zKE25L)4gY)-75&gsQEiCJLC%Ul@rjIHArt5Vhrv6Bp`PS^Ab>@n_ANlq03>SUr-4G z>Bbee zV5Ql|U~HTIWaA@XeD{QUIqL#=XE+0JS(NKnQRq->y5!Z;n>7?;eM|56HpjTydt(-JETy=(`aSfARIwDbOF zDu)S3Of)kh%+$|xlZ{{4E9NzTIdz?2_m=XAgwQy_v@pVxbpNe^$qIuejhOR)d!e5$ zrpdRj!E?5hb4b=z+e+((>~Bv6FMhFiV8NV~JVH4@t(+bw8|4tIjPLrci0o!0nYIX{SH}v&;haQ}}i z0Gx6Mz~n0pR&IF&waNv1I zV6%~RWUnGgmrvL!Qswr*XaFBLdTXmVX+>EX?U{PZu2#*p3hjx#O&5gwb}q-q9;cbnQ?YW%pKP=ouGq+k;y7{cV1pG%hZL}q`y9V z7hCr-j^qcpU9H;2j@%iJM&re^_53E4*~ZT%L75@1?eLt}o!idRS40O0oz&Jismeb& zrG3ADIFRa1(Ghvbhl`)2`=KuXND^Ka@kOtdN^?6x18}YY-%aZfJ>_eT6_9{3-MdjB ze#jz(VajcHLtGIDso61krE~~g=o!0yPP)j&lMo#${*~d$JjT;i2uP4{3i@W>a4Txg zG^g7#V>Ma+Qp8*mlLDJiVEk0|lU0`z15L^s4A($_L||!Sz(R8uI-v0bO5vz+FXrD? zBaZuvBN@6D^~EO`#?x8HLn-{$j41WQ%P|k}V!n3U3uH=3-IPMfK4FGCemLtH`HM!% z+fHur?{oLcHXok%$4c{zr)EWM$hS{#%=x}Lc2U3UvCiD4Q}umk@H_t7Efq=YTbetqN(BC9m9o*F-`p}NPFh@|S-H6YjKou4R?ne?(tz{A{LRlCB>W#m+!(O?7&kd+KXQtf{UxVUA z%#;bt-}<#Kx*k0l+V9pc57KSPTUneuu0geybEIEbjxVTG2XQB@LLf3d$8~rsm~?=K z(Ew~?mmKuk$9*dfXwpCUgnbNwrwuZ}QJ)F{-1OP$TYw^VbkNN6N2V4~M*Hs66=7Ol z21cX|UHS%u!)Y*3%u8Ae;$K$&etp1Fo18S0C7Q(GYf_MSqGhGJ&a8JyY0>+!1j}%9 zPsO6xaC^KoZQDC2&3^*jcj>OLOXUWCh}L*z-=^bjy;Nk8Pp{qX-;2?R9F}U15K%NjKOFF!Qo@HxV_>iwU7RpHUOrO1`5~w zaN=+kN22BF1Yb=C-w8tv@b${eOi(e9?7t))$z!gq zAe^WdzOt?B*wp)4003gUvIkYsPY?7%BA~!Hov7Pj5R4y)(d%Ek4@4zcU1fPm(Q%7D zCq7Xt?6xzPjwmu32<5RC%wp7sK>%Jw@=C_XZvwH^Iw5bjz5I*#lDmW}eRIub6|q0H zkljLg#*b|1RsXY`@?BnVM9OhNUHJU4H3IlV=){Gu&V*xXny)z=iP)ZDz|rFO$9c>&xS7avh83!4OG$ z{@$ewf{KmaRjYmS;)ct#_g0N`2|@ z=7(RtCIHTCWnSQYUc6iyJ26XAqQBxfQ*H+Cd9Rj5vjSO<;=ge>vllzp*W2(4om4LB zGdOamB}}8w{?lB~1Y_{CwOD7V1B#&4tBZrJBR%CL&=r-tk1s}&JPV?+#FImEUQCU}xq0O~QF zWu2wrp!vg#&ZObOLYj}@m8Hx5??bG?Q3pBtaHI`uv^sP>97~MZ(PoR+^b=@Qd%1m7 zZ;=?mT71Hu)887AbRx4bns-jRWZ&>S!!F5{rRk!N&^mVyCDpct=R~GYMy_tl9s=&C z`AFov&hx^cE9I;(-vd>~qfg-?^~Rahbnz<3_(gcy{*3>9;4W}5Vo*1R3CRt20m*TL ziLd-87Sq^N!BPXHw_p)-rdJ*L5Bm(&Xc1J)o&fIO@G}xnW)E3>jv2iw8#AWht6?KF zdg9z#O#1+^Tst$hlAJ>PE#7sN6+h(l2j1NJg_Eyax&19e=(AZXzlgva&?3vOZHiqv z=e$4@-`9BsP6sGRjT;+2A?=b%H`yDcN51o5m7=Nk<6q386jz+Mxvv&}&H6HE&ULa* z-@k7G-={}qOtd8~1$8elpn|p@ZBsrsp-44(nx-ZVq&c3hr4KolP<&(az>Bw)$~-NG~d#4o7DX1P2qTeSsu9GQ8<7-1&RRJ7@50v3{_)Q_v#^)1=+Z zTZO>DzcZ~S_0jXK9V}d)j{L;Px+r*{sh4B)>gLr^XZ{eurUjl*F1D*O)x=yZI>CepzDXnUX$$882Zqz9aJIG;qfwO#;`bmG!p^24*@>jPl|tqBh&{ z6SelAq~Y(XvUH%V_|WbH2h>Wi^PjhA zo7$3aFo}bRU)49?y;4yuh2GN5deje9t)hz&wP=Psi>tdRPAY-O4)l=;JCL*9<&&_wM%6V6uz`Uq|V6zm(s+ z9`(4JfV{wIfeN=QfOtke7Psfje<0|D`$DEnmOts(Y z9A0X}5M$Est~=L$cV2mrhzm3f>tp9*=jJbioOhn3W@J%o#==Irmr9gcv|`{Es{D;? zDI0$$92n_xJy_HW=>Y3h{)!I6XK}YbL*z(@C8>HN$9iX$d*)B8C`Xi!tQpsvf85b? z%4VD?IbG42m?De=m*}PYNFT$mkV&S~!MQ^J&BD&-`n*&mV zEJ*x)8L$r7$dCBc;xMTCbD9K`6Dp&?RQSUZDQV~-P4zcyfuHUa_sd-V=t0UfA*#7+ zA7^L18DLD7$$^y`*|6;4*x@U+>Vo3S$-`Heio<=yE(tjv!EgF=!4EARoqKsV6 z(Gb;jL371^4OtarbV1L5iR-PyEzB7EK(qD_xC4Nbp*+!GUr(>qI`U!7@3(Ru8}VHm zBw3QubBaa@3fRd)UW&5LL=tk)02^{^^YpBts_J9Kd(v#Lt+o}(k;*`#7 z+>YjIQ@E**C-52mgtETNaMlw$erRSE0dACBLxXj$SQaMMl=TTbZ1kVA4bOxa-y&JB6n$Rf_0bRz}nB|=lcSm@ZdxPdN zWb#~5>cV+YAM@bdfrA6lV7UQ!Pg(5D8pTn)n zAzLRMtxQK2?q<``Z2qHb%=fUe;x-T7PA#^-N@m9}ezKltr{t$Fqq%4P)sKt&g(?Me zaMIbA>Z>4|-G(8~2c@^!xuK)}K7WzFmYVBXlV5SG&5Da*{(+$pNRxS2NZEcjHe{YB z-0bU|Yv7*r`VcL4Y`pgU{i(y``CESH5;ce#FXIoN5^;Uve?RhU`+}_)f~y6-aq4(= zUs8`@zo9>7XEK@uVUN3yU!B5M@XPd(H*Dr-`P|+<2Y<;&;PeQJO~zXkNTts|bZ-V_ zC%oz?7thF8j$R!q*YKm5+|<6IsU|AZrmfmcWxL3vZq?`lbgJ8yy>U}`Q_1tZ*{O8S zc{x|jMzka@COGJo0WVVPDrf55((RS)<7o5H(AgN@g9tRkk;3U{)lt>3-n=>`LWD^wHqHnG5s|W7re8TQ55Y;$(e;t4E z)vsY)$E;lHQ+`oL9E!ViHS2UuBku{a`}NVLq+3SD_{e+0c`}mWc*1kNRsq5-VW@jy sSmDlWuUNQRAKnl*{v7Lc%$A2vbJZYk7f$=A8LJupHK~#(-h}}F17|rj7ytkO literal 0 HcmV?d00001 From e38c10edaea92c4a3051cf8d2f3827c5a7afa84e Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 19:47:32 +0200 Subject: [PATCH 21/67] fix icons style, store in action --- .../Vampire/VampireSystem.Abilities.cs | 15 ++------ .../Vampire/VampireSystem.Transform.cs | 2 +- Content.Server/Vampire/VampireSystem.cs | 22 ++--------- Content.Shared/Vampire/VampireComponent.cs | 2 +- Content.Shared/Vampire/VampireEvents.cs | 2 +- Resources/Prototypes/Actions/vampire.yml | 38 +++++++++++-------- 6 files changed, 33 insertions(+), 48 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 9335885dae8..15c2fc04b1f 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -49,7 +49,7 @@ private void InitializePowers() _passiveCache = BuildPassiveCache(); //Abilities - SubscribeLocalEvent(OnVampireSummonHeirloom); + SubscribeLocalEvent(OnVampireOpenMutationsMenu); SubscribeLocalEvent(OnVampireToggleFangs); SubscribeLocalEvent(OnVampireGlare); SubscribeLocalEvent(OnVampireScreech); @@ -73,19 +73,12 @@ private void InitializePowers() } #region Ability Entry Points - private void OnVampireSummonHeirloom(EntityUid entity, VampireComponent component, VampireSummonHeirloomEvent ev) + private void OnVampireOpenMutationsMenu(EntityUid uid, VampireComponent component, VampireOpenMutationsMenu ev) { - if (!TryGetPowerDefinition(ev.DefinitionName, out var def)) - return; - - var vampire = new Entity(entity, component); - - if (!IsAbilityUsable(vampire, def)) + if (!TryComp(uid, out var store)) return; - SummonHeirloom(vampire); - - ev.Handled = true; + _store.ToggleUi(uid, uid, store); } private void OnVampireToggleFangs(EntityUid entity, VampireComponent component, VampireToggleFangsEvent ev) { diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index 5b67b9bf6de..1532c826c35 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -91,7 +91,7 @@ private void MakeVulnerableToHoly(Entity vampire) private void AddStartingAbilities(Entity vampire) { - var action = _action.AddAction(vampire, VampireComponent.SummonActionPrototype); + var action = _action.AddAction(vampire, VampireComponent.MutationsActionPrototype); if (!action.HasValue) return; diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 77a019af41c..64c7e1bdc36 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -84,7 +84,6 @@ public override void Initialize() SubscribeLocalEvent(OnExamined); //SubscribeLocalEvent(OnStorePurchasePassive); - SubscribeLocalEvent(OnUseHeirloom); SubscribeLocalEvent(OnStorePurchase); InitializePowers(); @@ -174,21 +173,6 @@ private void OnComponentStartup(EntityUid uid, VampireComponent component, Compo MakeVampire(uid); } - private void OnUseHeirloom(EntityUid uid, VampireHeirloomComponent component, UseInHandEvent args) - { - //Ensure the user is a vampire - if (!HasComp(args.User)) - return; - - //Only allow the heirloom owner to use this - prevent stealing others blood essence - //TODO: Popup, deprecation - if (component.VampireOwner != args.User) - return; - - //And open the UI - _store.ToggleUi(args.User, uid); - } - private void OnStorePurchase(EntityUid uid, VampireHeirloomComponent component, ref StorePurchasedListingEvent ev) { if (!TryComp(ev.Purchaser, out var vampireComponent)) @@ -281,12 +265,12 @@ private void UpdateBloodDisplay(Entity vampire) return; var chargeDisplay = (int) Math.Round((decimal) balance); - var summonAction = GetPowerEntity(vampire, VampireComponent.SummonActionPrototype); + var mutationsAction = GetPowerEntity(vampire, VampireComponent.MutationsActionPrototype); - if (summonAction == null) + if (mutationsAction == null) return; - _action.SetCharges(summonAction, chargeDisplay); + _action.SetCharges(mutationsAction, chargeDisplay); } private FixedPoint2 GetBloodEssence(Entity vampire) { diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index 1c1201ac768..ecf4dd2d898 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -48,7 +48,7 @@ public sealed partial class VampireComponent : Component }; [ValidatePrototypeId] - public static readonly string SummonActionPrototype = "ActionVampireSummonHeirloom"; + public static readonly string MutationsActionPrototype = "ActionVampireOpenMutationsMenu"; [ValidatePrototypeId] public static readonly string DrinkBloodPrototype = "DrinkBlood"; diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index 8870e6b9e91..4b21600f4b1 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Vampire; //Use power events public sealed partial class VampireToggleFangsEvent : VampireSelfPowerEvent { } -public sealed partial class VampireSummonHeirloomEvent : VampireSelfPowerEvent { } +public sealed partial class VampireOpenMutationsMenu : InstantActionEvent { } public sealed partial class VampireScreechEvent : VampireSelfPowerEvent { } public sealed partial class VampirePolymorphEvent : VampireSelfPowerEvent { } public sealed partial class VampireBloodStealEvent : VampireSelfPowerEvent { } diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index 94d65e6c59a..c893d190004 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -1,27 +1,28 @@ - type: entity - id: ActionVampireSummonHeirloom - name: vampire-power-summonheirloom - description: vampire-power-summonheirloom-description - noSpawn: true + id: ActionVampireOpenMutationsMenu + name: vampire-power-mutationsmenu + description: vampire-power-mutationsmenu-description + categories: [ HideSpawnMenu ] components: - type: InstantAction + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: summonheirloom event: - !type:VampireSummonHeirloomEvent - definitionName: SummonHeirloom - useDelay: 60 + !type:VampireOpenMutationsMenu + useDelay: 5 - type: entity id: ActionVampireToggleFangs name: vampire-power-togglefangs description: vampire-power-togglefangs-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: InstantAction checkCanInteract: false priority: 1 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: fangs_retracted @@ -36,13 +37,14 @@ id: ActionVampireGlare name: vampire-power-glare description: vampire-power-glare-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: EntityTargetAction whitelist: components: - Body priority: 2 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: glare @@ -57,7 +59,7 @@ id: ActionVampireHypnotise name: vampire-power-hypnotise description: vampire-power-hypnotise-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: EntityTargetAction whitelist: @@ -66,6 +68,7 @@ canTargetSelf: false interactOnMiss: false priority: 2 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: hypnotise @@ -78,11 +81,12 @@ id: ActionVampireScreech name: vampire-power-screech description: vampire-power-screech-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: InstantAction checkCanInteract: false priority: 2 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: screech @@ -97,11 +101,12 @@ id: ActionVampireBloodSteal name: vampire-power-bloodsteal description: vampire-power-bloodsteal-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: InstantAction checkCanInteract: false priority: 2 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: bloodsteal @@ -116,11 +121,12 @@ id: ActionVampireBatform name: vampire-power-batform description: vampire-power-batform-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: InstantAction checkCanInteract: false priority: 2 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: batform @@ -135,11 +141,12 @@ id: ActionVampireMouseform name: vampire-power-mouseform description: vampire-power-mouseform-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: InstantAction checkCanInteract: false priority: 2 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: mouseform @@ -154,11 +161,12 @@ id: ActionVampireCloakOfDarkness name: vampire-power-cloakofdarkness description: vampire-power-cloakofdarkness-description - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: InstantAction checkCanInteract: false priority: 2 + itemIconStyle: NoItem icon: sprite: Interface/Actions/actions_vampire.rsi state: cloakofdarkness From 0154ebea7d574f2cb1f2830607f4b44a1ad3c9a8 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 20:43:42 +0200 Subject: [PATCH 22/67] clear store things, add gamerule, mid round game rule --- .../Systems/AdminVerbSystem.Antags.cs | 14 ++ .../Rules/Components/VampireRuleComponent.cs | 20 ++ .../GameTicking/Rules/VampireRuleSystem.cs | 124 +++++++++++ .../Store/Systems/StoreSystem.Ui.cs | 10 +- .../ru-RU/_strings/administration/antag.ftl | 4 +- .../Prototypes/Catalog/vampire_catalog.yml | 196 ------------------ Resources/Prototypes/GameRules/midround.yml | 15 ++ Resources/Prototypes/Store/categories.yml | 9 - 8 files changed, 178 insertions(+), 214 deletions(-) create mode 100644 Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs create mode 100644 Content.Server/GameTicking/Rules/VampireRuleSystem.cs delete mode 100644 Resources/Prototypes/Catalog/vampire_catalog.yml diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 0cf044e71be..6dabcdfd192 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -165,5 +165,19 @@ private void AddAntagVerbs(GetVerbsEvent args) Message = Loc.GetString("admin-verb-make-changeling"), }; args.Verbs.Add(ling); + + Verb vampire = new() + { + Text = Loc.GetString("admin-verb-text-make-vampire"), + Category = VerbCategory.Antag, + Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_vampire.rsi"), "unholystrength"), + Act = () => + { + _antag.ForceMakeAntag(targetPlayer, "Vampire"); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-vampire"), + }; + args.Verbs.Add(vampire); } } diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs new file mode 100644 index 00000000000..7712693f08b --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.NPC.Prototypes; +using Content.Shared.Roles; +using Content.Shared.Store; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Rules.Components; + +[RegisterComponent, Access(typeof(VampireRuleSystem))] +public sealed partial class VampireRuleComponent : Component +{ + public readonly List VampireMinds = new(); +/* + public readonly List> Objectives = new() + { + "ChangelingSurviveObjective", + "ChangelingStealDNAObjective", + "EscapeIdentityObjective" + }; */ +} \ No newline at end of file diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs new file mode 100644 index 00000000000..18d3f87de29 --- /dev/null +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -0,0 +1,124 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Mind; +using Content.Server.Objectives; +using Content.Server.Roles; +using Content.Server.Vampire; +using Content.Shared.Vampire.Components; +using Content.Shared.NPC.Prototypes; +using Content.Shared.NPC.Systems; +using Content.Shared.Roles; +using Content.Shared.Store; +using Content.Shared.Store.Components; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using System.Text; + +namespace Content.Server.GameTicking.Rules; + +public sealed partial class VampireRuleSystem : GameRuleSystem +{ + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; + [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly ObjectivesSystem _objective = default!; + [Dependency] private readonly VampireSystem _vampire = default!; + + public readonly SoundSpecifier BriefingSound = new SoundPathSpecifier("/Audio/Ambience/Antag/vampire_start.ogg"); + + public readonly ProtoId VampirePrototypeId = "Vampire"; + +// public readonly ProtoId ChangelingFactionId = "Changeling"; + +// public readonly ProtoId NanotrasenFactionId = "NanoTrasen"; + + public readonly ProtoId Currency = "BloodEssence"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSelectAntag); + //SubscribeLocalEvent(OnTextPrepend); + } + + private void OnSelectAntag(EntityUid uid, VampireRuleComponent comp, ref AfterAntagEntitySelectedEvent args) + { + MakeVampire(args.EntityUid, comp); + } + public bool MakeVampire(EntityUid target, VampireRuleComponent rule) + { + if (!_mind.TryGetMind(target, out var mindId, out var mind)) + return false; + + // briefing + if (TryComp(target, out var metaData)) + { + var briefing = Loc.GetString("vampire-role-greeting", ("name", metaData?.EntityName ?? "Unknown")); + var briefingShort = Loc.GetString("vampire-role-greeting-short", ("name", metaData?.EntityName ?? "Unknown")); + + _antag.SendBriefing(target, briefing, Color.Yellow, BriefingSound); + _role.MindHasRole(mindId, out var vampireRole); + if (vampireRole is not null) + { + AddComp(vampireRole.Value.Owner); + Comp(vampireRole.Value.Owner).Briefing = briefing; + } + } + // vampire stuff +// _npcFaction.RemoveFaction(target, NanotrasenFactionId, false); +// _npcFaction.AddFaction(target, ChangelingFactionId); + + // make sure it's initial chems are set to max + var vampireComponent = EnsureComp(target); + + vampireComponent.Balance = new() { { VampireComponent.CurrencyProto, 0 } }; + + rule.VampireMinds.Add(mindId); + + if (HasComp(target)) + { + _vampire.AddStartingAbilities(target); + } + + //foreach (var objective in rule.Objectives) + // _mind.TryAddObjective(mindId, mind, objective); + + return true; + } + +/* private void OnTextPrepend(EntityUid uid, VampireRuleComponent comp, ref ObjectivesTextPrependEvent args) + { + var mostAbsorbedName = string.Empty; + var mostStolenName = string.Empty; + var mostAbsorbed = 0f; + var mostStolen = 0f; + + foreach (var ling in EntityQuery()) + { + if (!_mind.TryGetMind(ling.Owner, out var mindId, out var mind)) + continue; + + if (!TryComp(ling.Owner, out var metaData)) + continue; + + if (ling.TotalAbsorbedEntities > mostAbsorbed) + { + mostAbsorbed = ling.TotalAbsorbedEntities; + mostAbsorbedName = _objective.GetTitle((mindId, mind), metaData.EntityName); + } + if (ling.TotalStolenDNA > mostStolen) + { + mostStolen = ling.TotalStolenDNA; + mostStolenName = _objective.GetTitle((mindId, mind), metaData.EntityName); + } + } + + var sb = new StringBuilder(); + sb.AppendLine(Loc.GetString($"roundend-prepend-changeling-absorbed{(!string.IsNullOrWhiteSpace(mostAbsorbedName) ? "-named" : "")}", ("name", mostAbsorbedName), ("number", mostAbsorbed))); + sb.AppendLine(Loc.GetString($"roundend-prepend-changeling-stolen{(!string.IsNullOrWhiteSpace(mostStolenName) ? "-named" : "")}", ("name", mostStolenName), ("number", mostStolen))); + + args.Text = sb.ToString(); + }*/ +} diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index ba1472a287a..928dcde79bb 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -10,7 +10,6 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Mind; using Content.Shared.Store; -using Content.Shared.Store.Events; using Content.Shared.Store.Components; using Content.Shared.UserInterface; using Robust.Server.GameObjects; @@ -139,8 +138,6 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi } var buyer = msg.Actor; - - //var listingev = new StorePurchasedListingEvent() { Purchaser = buyer, Listing = listing }; //verify that we can actually buy this listing and it wasn't added if (!ListingHasCategory(listing, component.Categories)) @@ -164,6 +161,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi { return; } + } if (!IsOnStartingMap(uid, component)) component.RefundAllowed = false; @@ -174,7 +172,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi component.Balance[currency] -= amount; component.BalanceSpent.TryAdd(currency, FixedPoint2.Zero); - + component.BalanceSpent[currency] += amount; // Sunrise-Start @@ -195,7 +193,6 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi // Sunrise-End _hands.PickupOrDrop(buyer, product); - //listingev.Item = product; HandleRefundComp(uid, component, product); @@ -262,7 +259,6 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi if (upgradeActionId != null) HandleRefundComp(uid, component, upgradeActionId.Value); - //listingev.Action = actionUid; } if (listing.ProductEvent != null) @@ -273,8 +269,6 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi RaiseLocalEvent(buyer, listing.ProductEvent); } - //RaiseLocalEvent(uid, listingev); - //RaiseLocalEvent(buyer, listingev); //log dat shit. _admin.Add(LogType.StorePurchase, diff --git a/Resources/Locale/ru-RU/_strings/administration/antag.ftl b/Resources/Locale/ru-RU/_strings/administration/antag.ftl index 9e898ce238a..9939236391d 100644 --- a/Resources/Locale/ru-RU/_strings/administration/antag.ftl +++ b/Resources/Locale/ru-RU/_strings/administration/antag.ftl @@ -6,7 +6,8 @@ admin-verb-make-nuclear-operative = Сделать цель одиноким Я admin-verb-make-pirate = Сделать цель пиратом\капером. Учтите, что это не меняет игровой режим. admin-verb-make-head-rev = Сделать цель главой революции. admin-verb-make-thief = Сделать цель вором. -admin-verb-make-changeling = Сделать цель генокрадом +admin-verb-make-changeling = Сделать цель генокрадом. +admin-verb-make-vampire = Сделать цель вампиром. admin-verb-text-make-traitor = Сделать предателем admin-verb-text-make-initial-infected = Сделать нулевым пациентом admin-verb-text-make-zombie = Сделать зомби @@ -15,3 +16,4 @@ admin-verb-text-make-pirate = Сделать пиратом admin-verb-text-make-head-rev = Сделать Главой революции admin-verb-text-make-thief = Сделать вором admin-verb-text-make-changeling = Сделать генокрадом +admin-verb-text-make-vampire = Сделать вампиром \ No newline at end of file diff --git a/Resources/Prototypes/Catalog/vampire_catalog.yml b/Resources/Prototypes/Catalog/vampire_catalog.yml deleted file mode 100644 index 8cbe3a95096..00000000000 --- a/Resources/Prototypes/Catalog/vampire_catalog.yml +++ /dev/null @@ -1,196 +0,0 @@ -- type: listing - id: VampireToggleFangs - name: vampire-ability-blessing - description: vampire-ability-blessing-description - cost: - BloodEssence: 0 - productAction: ActionVampireToggleFangs - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - -- type: listing - id: VampireGlare - name: vampire-ability-glare - description: vampire-ability-glare-description - cost: - BloodEssence: 20 - productAction: ActionVampireGlare - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - -- type: listing - id: VampireHypnotise - name: vampire-ability-hypnotise - description: vampire-ability-hypnotise-description - cost: - BloodEssence: 60 - productAction: ActionVampireHypnotise - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - -- type: listing - id: VampireScreech - name: vampire-ability-screech - description: vampire-ability-screech-description - cost: - BloodEssence: 120 - productAction: ActionVampireScreech - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - -- type: listing - id: VampireBloodSteal - name: vampire-ability-bloodsteal - description: vampire-ability-bloodsteal-description - cost: - BloodEssence: 120 - productAction: ActionVampireBloodSteal - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - -- type: listing - id: VampireBatForm - name: vampire-ability-batform - description: vampire-ability-batform-description - cost: - BloodEssence: 200 - productAction: ActionVampireBatform - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - blacklist: - - VampireMouseForm - -- type: listing - id: VampireMouseForm - name: vampire-ability-mouseform - description: vampire-ability-mouseform-description - cost: - BloodEssence: 200 - productAction: ActionVampireMouseform - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - blacklist: - - VampireBatForm - -- type: listing - id: VampireCloakOfDarkness - name: vampire-ability-cloakofdarkness - description: vampire-ability-cloakofdarkness-description - cost: - BloodEssence: 400 - productAction: ActionVampireCloakOfDarkness - categories: - - VampirePowers - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - -#Passives -- type: listing - id: VampireUnholyStrength - name: vampire-passive-unholystrength - description: vampire-passive-unholystrength-description - icon: - sprite: Interface/Actions/actions_vampire.rsi - state: unholystrength - cost: - BloodEssence: 20 - categories: - - VampirePassives - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - -- type: listing - id: VampireSupernaturalStrength - name: vampire-passive-supernaturalstrength - description: vampire-passive-supernaturalstrength-description - icon: - sprite: Interface/Actions/actions_vampire.rsi - state: supernaturalstrength - cost: - BloodEssence: 400 - categories: - - VampirePassives - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireUnholyStrength - -- type: listing - id: VampireDeathsEmbrace - name: vampire-passive-deathsembrace - description: vampire-passive-deathsembrace-description - icon: - sprite: Interface/Actions/actions_vampire.rsi - state: deathsembrace - cost: - BloodEssence: 160 - categories: - - VampirePassives - conditions: - - !type:ListingLimitedStockCondition - stock: 1 - - !type:BuyBeforeCondition - whitelist: - - VampireToggleFangs - -#- type: listing -# id: VampireSupernaturalStrength -# name: Supernatural Strength -# description: Infuse your muscles with unholy strength, allowing you to pry open any barricade mortals may hide behind. -# productAction: SupernaturalStrength -# cost: -# BloodEssence: 400 -# categories: -# - VampirePowers -# conditions: -# - !type:ListingLimitedStockCondition -# stock: 1 \ No newline at end of file diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 73cbd6d4806..603ca28ccf1 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -45,6 +45,21 @@ lateJoinAdditional: true mindRoles: - MindRoleChangeling + +- type: entity + parent: BaseGameRule + id: Vampire + components: + - type: VampireRule + - type: AntagSelection + agentName: vampire-roundend-name + definitions: + - prefRoles: [ Vampire ] + max: 4 + playerRatio: 30 + lateJoinAdditional: true + mindRoles: + - MindRoleVampire - type: entity categories: [ HideSpawnMenu ] diff --git a/Resources/Prototypes/Store/categories.yml b/Resources/Prototypes/Store/categories.yml index cd4f3af8368..94be8cbab45 100644 --- a/Resources/Prototypes/Store/categories.yml +++ b/Resources/Prototypes/Store/categories.yml @@ -120,12 +120,3 @@ id: ChangelingAbilityUtility name: store-ling-category-utility priority: 2 - -#vampire -- type: storeCategory - id: VampirePowers - name: store-category-vampirepowers - -- type: storeCategory - id: VampirePassives - name: store-category-vampirepassives From f97e23b421c72c73e4e26042122527b5935e5a49 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 20:44:09 +0200 Subject: [PATCH 23/67] some rework --- Content.Server/Vampire/VampireSystem.Transform.cs | 7 +++++-- Content.Server/Vampire/VampireSystem.cs | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index 1532c826c35..dfe7490a602 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -89,8 +89,11 @@ private void MakeVulnerableToHoly(Entity vampire) } } - private void AddStartingAbilities(Entity vampire) + public void AddStartingAbilities(EntityUid vampire) { + if (!TryComp(vampire, out var comp)) + return; + var action = _action.AddAction(vampire, VampireComponent.MutationsActionPrototype); if (!action.HasValue) return; @@ -103,7 +106,7 @@ private void AddStartingAbilities(Entity vampire) if (actionEvent == null) return; - vampire.Comp.UnlockedPowers.Add(actionEvent.DefinitionName, action); + comp.UnlockedPowers.Add(actionEvent.DefinitionName, action); UpdateBloodDisplay(vampire); } diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 64c7e1bdc36..970875793d9 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -170,7 +170,7 @@ public override void Update(float frameTime) private void OnComponentStartup(EntityUid uid, VampireComponent component, ComponentStartup args) { - MakeVampire(uid); + //MakeVampire(uid); } private void OnStorePurchase(EntityUid uid, VampireHeirloomComponent component, ref StorePurchasedListingEvent ev) @@ -258,14 +258,17 @@ private bool SubtractBloodEssence(Entity vampire, FixedPoint2 /// Use the charges display on SummonHeirloom to show the remaining blood essence ///
/// - private void UpdateBloodDisplay(Entity vampire) + public void UpdateBloodDisplay(EntityUid vampire) { + if (!TryComp(vampire, out var comp)) + return; + //Sanity check, you never know who is going to touch this code - if (!vampire.Comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var balance)) + if (!comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var balance)) return; var chargeDisplay = (int) Math.Round((decimal) balance); - var mutationsAction = GetPowerEntity(vampire, VampireComponent.MutationsActionPrototype); + var mutationsAction = GetPowerEntity(comp, VampireComponent.MutationsActionPrototype); if (mutationsAction == null) return; From e0354cbad05dea8712a86a76c6b4d9fd8af69863 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 20:47:16 +0200 Subject: [PATCH 24/67] clear any store systems --- .../Rules/Components/VampireRuleComponent.cs | 1 - .../GameTicking/Rules/VampireRuleSystem.cs | 2 - Content.Server/Vampire/VampireSystem.cs | 84 ------------------- 3 files changed, 87 deletions(-) diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs index 7712693f08b..48197ca452a 100644 --- a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -1,6 +1,5 @@ using Content.Shared.NPC.Prototypes; using Content.Shared.Roles; -using Content.Shared.Store; using Robust.Shared.Audio; using Robust.Shared.Prototypes; diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index 18d3f87de29..dc669c781d8 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -8,8 +8,6 @@ using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.Roles; -using Content.Shared.Store; -using Content.Shared.Store.Components; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using System.Text; diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 970875793d9..721be69be5e 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -7,9 +7,6 @@ using Content.Server.Nutrition.EntitySystems; using Content.Server.Polymorph.Systems; using Content.Server.Storage.EntitySystems; -using Content.Server.Store.Components; -using Content.Shared.Store.Events; -using Content.Server.Store.Systems; using Content.Shared.Actions; using Content.Shared.Body.Systems; using Content.Shared.Buckle; @@ -38,7 +35,6 @@ using Robust.Shared.Map; using Robust.Shared.Prototypes; using System.Linq; -using Content.Shared.Store; namespace Content.Server.Vampire; @@ -71,7 +67,6 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly StoreSystem _store = default!; [Dependency] private readonly MetabolizerSystem _metabolism = default!; public override void Initialize() @@ -82,39 +77,10 @@ public override void Initialize() //SubscribeLocalEvent(OnUseSelfPower); //SubscribeLocalEvent(OnUseTargetedPower); SubscribeLocalEvent(OnExamined); - //SubscribeLocalEvent(OnStorePurchasePassive); - - SubscribeLocalEvent(OnStorePurchase); InitializePowers(); } - /*private void OnStorePurchasePassive(EntityUid uid, VampireComponent component, StoreProductEvent args) - { - if (args.Ev is not VampirePowerDetails) - return; - - var def = args.Ev as VampirePowerDetails; - if (def == null) - return; - - var vampire = new Entity(uid, component); - - switch (def.Type) - { - case VampirePowerKey.UnnaturalStrength: - { - UnnaturalStrength(vampire, def); - break; - } - case VampirePowerKey.SupernaturalStrength: - { - SupernaturalStrength(vampire, def); - break; - } - } - }*/ - /// /// Handles healing and damaging in space /// @@ -173,56 +139,6 @@ private void OnComponentStartup(EntityUid uid, VampireComponent component, Compo //MakeVampire(uid); } - private void OnStorePurchase(EntityUid uid, VampireHeirloomComponent component, ref StorePurchasedListingEvent ev) - { - if (!TryComp(ev.Purchaser, out var vampireComponent)) - return; - - var vampire = new Entity(ev.Purchaser, vampireComponent); - - if (ev.Action != null) - { - OnStorePurchaseActive(vampire, ev.Action.Value); - } - else - { - //Its a passive - OnStorePurchasePassive(vampire, ev.Listing); - } - } - private void OnStorePurchaseActive(Entity purchaser, EntityUid purchasedAction) - { - if (TryComp(purchasedAction, out var instantAction) && instantAction.Event != null && instantAction.Event is VampireSelfPowerEvent) - { - var vampirePower = instantAction.Event as VampireSelfPowerEvent; - if (vampirePower == null) return; - purchaser.Comp.UnlockedPowers[vampirePower.DefinitionName] = purchasedAction; - return; - } - if (TryComp(purchasedAction, out var targetAction) && targetAction.Event != null && targetAction.Event is VampireTargetedPowerEvent) - { - var vampirePower = targetAction.Event as VampireTargetedPowerEvent; - if (vampirePower == null) return; - purchaser.Comp.UnlockedPowers[vampirePower.DefinitionName] = purchasedAction; - return; - } - - UpdateBloodDisplay(purchaser); - } - - private void OnStorePurchasePassive(Entity purchaser, ListingData purchasedPassive) - { - if (!_passiveCache.TryGetValue(purchasedPassive.ID, out var passiveDef)) - return; - - //I am so going to hell for this - foreach (var compToRemove in passiveDef.CompsToRemove.Values) - RemComp(purchaser, compToRemove.Component.GetType()); - - foreach (var compToAdd in passiveDef.CompsToAdd.Values) - AddComp(purchaser, compToAdd.Component, true); - } - private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) { if (HasComp(uid) && args.IsInDetailsRange && !_food.IsMouthBlocked(uid)) From 41e178f0a02eb0358aa710a1706d7f2d43db6dd3 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 20:52:08 +0200 Subject: [PATCH 25/67] clear x2 --- .../GameTicking/Rules/VampireRuleSystem.cs | 2 ++ .../Vampire/VampireSystem.Abilities.cs | 28 ------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index dc669c781d8..18d3f87de29 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -8,6 +8,8 @@ using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.Roles; +using Content.Shared.Store; +using Content.Shared.Store.Components; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using System.Text; diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 15c2fc04b1f..987d0938f43 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -75,10 +75,6 @@ private void InitializePowers() #region Ability Entry Points private void OnVampireOpenMutationsMenu(EntityUid uid, VampireComponent component, VampireOpenMutationsMenu ev) { - if (!TryComp(uid, out var store)) - return; - - _store.ToggleUi(uid, uid, store); } private void OnVampireToggleFangs(EntityUid entity, VampireComponent component, VampireToggleFangsEvent ev) { @@ -277,30 +273,6 @@ private void SupernaturalStrength(Entity vampire, VampirePower #endregion #region Other Powers - /// - /// Spawn and bind the pendant if one does not already exist, otherwise just summon to the vampires hand - /// - private void SummonHeirloom(Entity vampire) - { - if (!vampire.Comp.Heirloom.HasValue - || LifeStage(vampire.Comp.Heirloom.Value) >= EntityLifeStage.Terminating) - { - //If the pendant does not exist, or has been deleted - spawn one - vampire.Comp.Heirloom = Spawn(VampireComponent.HeirloomProto); - - if (TryComp(vampire.Comp.Heirloom, out var heirloomComponent)) - heirloomComponent.VampireOwner = vampire; - - //Init the store balance, or init the vampire's balance if this is the first summon - if (TryComp(vampire.Comp.Heirloom, out var storeComponent)) - if (vampire.Comp.Balance == null) - vampire.Comp.Balance = storeComponent.Balance; - else - storeComponent.Balance = vampire.Comp.Balance; - } - //Move to players hands - _hands.PickupOrDrop(vampire, vampire.Comp.Heirloom.Value); - } private void Screech(Entity vampire, TimeSpan? duration, DamageSpecifier? damage = null) { foreach (var entity in _entityLookup.GetEntitiesInRange(vampire, 3, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries)) From c8c0a9e8323aedbc555bd62eab46dbc9d66a49b2 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 21:24:40 +0200 Subject: [PATCH 26/67] if you have 150 Blood Essence, you get mutation --- Content.Server/Vampire/VampireSystem.cs | 14 ++++++++++++++ Content.Shared/Vampire/VampireEvents.cs | 3 +++ 2 files changed, 17 insertions(+) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 721be69be5e..b01068fe232 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -77,6 +77,7 @@ public override void Initialize() //SubscribeLocalEvent(OnUseSelfPower); //SubscribeLocalEvent(OnUseTargetedPower); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnVampireBloodChangedEvent); InitializePowers(); } @@ -153,6 +154,9 @@ private bool AddBloodEssence(Entity vampire, FixedPoint2 quant vampire.Comp.Balance[VampireComponent.CurrencyProto] += quantity; UpdateBloodDisplay(vampire); + + var ev = new VampireBloodChangedEvent(); + RaiseLocalEvent(vampire, ev); return true; } @@ -167,6 +171,9 @@ private bool SubtractBloodEssence(Entity vampire, FixedPoint2 vampire.Comp.Balance[VampireComponent.CurrencyProto] -= quantity; UpdateBloodDisplay(vampire); + + var ev = new VampireBloodChangedEvent(); + RaiseLocalEvent(vampire, ev); return true; } @@ -191,6 +198,13 @@ public void UpdateBloodDisplay(EntityUid vampire) _action.SetCharges(mutationsAction, chargeDisplay); } + + private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent component, VampireBloodChangedEvent args) + { + if (comp.Balance == 150) + _action.AddAction(vampire, VampireComponent.MutationsActionPrototype); + } + private FixedPoint2 GetBloodEssence(Entity vampire) { if (!vampire.Comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var val)) diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs index 4b21600f4b1..149a7d19ec6 100644 --- a/Content.Shared/Vampire/VampireEvents.cs +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -38,6 +38,9 @@ public sealed partial class VampirePassiveActionEvent : BaseActionEvent [Serializable, NetSerializable] public sealed partial class VampirePurchaseUnnaturalStrength : EntityEventArgs { } +[Serializable, NetSerializable] +public sealed partial class VampireBloodChangedEvent : EntityEventArgs { } + //Doafter events [Serializable, NetSerializable] public sealed partial class VampireDrinkBloodDoAfterEvent : DoAfterEvent From b8f01f3a8ea8761ab1f4580e56019e6e7fa09bd4 Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 21:24:59 +0200 Subject: [PATCH 27/67] upd --- Content.Server/Vampire/VampireSystem.Transform.cs | 2 +- Content.Shared/Vampire/VampireComponent.cs | 2 ++ Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl | 4 ++++ Resources/Prototypes/Actions/vampire.yml | 8 ++++---- 4 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index dfe7490a602..cbc159e4ff5 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -94,7 +94,7 @@ public void AddStartingAbilities(EntityUid vampire) if (!TryComp(vampire, out var comp)) return; - var action = _action.AddAction(vampire, VampireComponent.MutationsActionPrototype); + var action = _action.AddAction(vampire, VampireComponent.ToggleFangsActionPrototype); if (!action.HasValue) return; diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index ecf4dd2d898..fb42c3f7dc0 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -49,6 +49,8 @@ public sealed partial class VampireComponent : Component [ValidatePrototypeId] public static readonly string MutationsActionPrototype = "ActionVampireOpenMutationsMenu"; + [ValidatePrototypeId] + public static readonly string ToggleFangsActionPrototype = "ActionVampireToggleFangs"; [ValidatePrototypeId] public static readonly string DrinkBloodPrototype = "DrinkBlood"; diff --git a/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl b/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl new file mode 100644 index 00000000000..5ec91979a4a --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl @@ -0,0 +1,4 @@ +ent-ActionVampireOpenMutationsMenu = Меню мутаций + .desc = Открывает меню с мутациями вампира. +ent-ActionVampireToggleFangs = Высунуть клыки + .desc = Вампир выдвигает клыки, чтобы выпить кровь из своей жертвы. \ No newline at end of file diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index c893d190004..23482d9a6bd 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -1,7 +1,7 @@ - type: entity id: ActionVampireOpenMutationsMenu - name: vampire-power-mutationsmenu - description: vampire-power-mutationsmenu-description + name: Mutations menu + description: Opens menu with vampires mutations. categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -15,8 +15,8 @@ - type: entity id: ActionVampireToggleFangs - name: vampire-power-togglefangs - description: vampire-power-togglefangs-description + name: Toggle Fangs + description: The vampire extends their fangs so they may drink from their victim. categories: [ HideSpawnMenu ] components: - type: InstantAction From 7a030fe6badc7d39cf2338c876aef99e9b49833f Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 22:33:13 +0200 Subject: [PATCH 28/67] some changes --- Content.Server/Vampire/VampireSystem.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index b01068fe232..22696d69160 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -58,6 +58,7 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _action = default!; + [Dependency] private readonly ActionContainerSystem _actionContainer = default!; [Dependency] private readonly SharedBodySystem _body = default!; [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; [Dependency] private readonly SharedStunSystem _stun = default!; @@ -201,13 +202,20 @@ public void UpdateBloodDisplay(EntityUid vampire) private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent component, VampireBloodChangedEvent args) { - if (comp.Balance == 150) - _action.AddAction(vampire, VampireComponent.MutationsActionPrototype); + Log.Warning($"Blood amount: {GetBloodEssence(uid)}"); + if (GetBloodEssence(uid) >= FixedPoint2.New(150) && !_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) + { + Log.Warning($"earned enought blood for mutation"); + _action.AddAction(uid, VampireComponent.MutationsActionPrototype); + } } - private FixedPoint2 GetBloodEssence(Entity vampire) + private FixedPoint2 GetBloodEssence(EntityUid vampire) { - if (!vampire.Comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var val)) + if (!TryComp(vampire, out var comp)) + return 0; + + if (!comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var val)) return 0; return val; From f4a9487949e82a96445b26952a9838ea17face1d Mon Sep 17 00:00:00 2001 From: Rinary Date: Tue, 29 Oct 2024 23:50:08 +0200 Subject: [PATCH 29/67] simple mutation menu --- .../Vampire/VampireMutationMenu.xaml | 11 +++ .../Vampire/VampireMutationMenu.xaml.cs | 91 +++++++++++++++++++ Content.Shared/Vampire/VampireComponent.cs | 10 ++ 3 files changed, 112 insertions(+) create mode 100644 Content.Client/Vampire/VampireMutationMenu.xaml create mode 100644 Content.Client/Vampire/VampireMutationMenu.xaml.cs diff --git a/Content.Client/Vampire/VampireMutationMenu.xaml b/Content.Client/Vampire/VampireMutationMenu.xaml new file mode 100644 index 00000000000..5dca219d2ee --- /dev/null +++ b/Content.Client/Vampire/VampireMutationMenu.xaml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Content.Client/Vampire/VampireMutationMenu.xaml.cs b/Content.Client/Vampire/VampireMutationMenu.xaml.cs new file mode 100644 index 00000000000..87c29565256 --- /dev/null +++ b/Content.Client/Vampire/VampireMutationMenu.xaml.cs @@ -0,0 +1,91 @@ +using System.Numerics; +using Content.Client.Stylesheets; +using Content.Shared.Vampire; +using Content.Shared.Vampire.Components; +using Content.Client.Resources; +using Robust.Client.ResourceManagement; +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.Vampire; + +[GenerateTypedNameReferences] +public sealed partial class VampireMutationMenu : DefaultWindow +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + private IResourceCache _resourceCache; + private readonly SpriteSystem _sprite; + public event Action? OnIdSelected; + + private HashSet _possibleMutations = new(); + private VampireMutationsType _selectedId; + + public VampireMutationMenu() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _sprite = _entityManager.System(); + _resourceCache = IoCManager.Resolve(); + } + + public void UpdateState(HashSet mutationList, VampireMutationsType selectedMutation) + { + _possibleMutations = mutationList; + _selectedId = selectedMutation; + UpdateGrid(); + } + + private void UpdateGrid() + { + ClearGrid(); + + var group = new ButtonGroup(); + + foreach (var Mutation in _possibleMutations) + { + //if (!_prototypeManager.TryIndex("NormalBlobTile", out EntityPrototype? proto)) + // continue; + + string texturePath = Mutation switch + { + VampireMutationsType.Hemomancer => "/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png", + VampireMutationsType.Umbrae => "/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png", + VampireMutationsType.Gargantua => "/Textures/Interface/Actions/actions_vampire.rsi/gargantua.png", + VampireMutationsType.Dantalion => "/Textures/Interface/Actions/actions_vampire.rsi/dantalion.png", + VampireMutationsType.Bestia => "/Textures/Interface/Actions/actions_vampire.rsi/bestia.png", + _ => "/Textures/Interface/Actions/actions_vampire.rsi/deathsembrace.png" + }; + + var button = new Button + { + MinSize = new Vector2(64, 64), + HorizontalExpand = true, + Group = group, + StyleClasses = {StyleBase.ButtonSquare}, + ToggleMode = true, + Pressed = _selectedId == Mutation, + ToolTip = Loc.GetString($"vampire-mutation-{Mutation.ToString().ToLower()}-info"), + TooltipDelay = 0.01f, + }; + button.OnPressed += _ => OnIdSelected?.Invoke(Mutation); + Grid.AddChild(button); + + var texture = _resourceCache.GetTexture(texturePath); + button.AddChild(new TextureRect + { + Stretch = TextureRect.StretchMode.KeepAspectCentered, + Texture = texture, + }); + } + } + + private void ClearGrid() + { + Grid.RemoveAllChildren(); + } +} \ No newline at end of file diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index fb42c3f7dc0..d199dd3678f 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -191,6 +191,16 @@ public sealed partial class VampireSealthComponent : Component public float Upkeep = 0; } +[Serializable, NetSerializable] +public enum VampireMutationsType : byte +{ + Hemomancer, + Umbrae, + Gargantua, + Dantalion, + Bestia +} + /*[Serializable, NetSerializable] public enum VampirePowerKey : byte { From 92bbdbd1af112837fd60485a31248f8b4d420f0b Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 00:05:18 +0200 Subject: [PATCH 30/67] =?UTF-8?q?=D1=8F=20=D0=B2=D0=BE=D1=82=20=D1=81?= =?UTF-8?q?=D0=B8=D0=B6=D1=83,=20=D0=B2=2012=20=D0=BD=D0=BE=D1=87=D0=B8,?= =?UTF-8?q?=20=D0=BF=D0=B8=D1=88=D1=83=20=D0=BA=D0=B0=D0=BA=D1=83=D1=8E-?= =?UTF-8?q?=D1=82=D0=BE=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5=D0=B4=D0=BD=D1=83?= =?UTF-8?q?=D1=8E=20=D1=85=D1=83=D0=B9=D0=BD=D1=8E,=20=D0=B2=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=20=D0=BF=D1=80=D0=B8=D1=8F=D1=82=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=81=D0=BD=D0=B0...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VampireMutationBoundUserInterface.cs | 42 +++++++++++++++++++ Content.Shared/Vampire/VampireComponent.cs | 41 ++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 Content.Client/Vampire/VampireMutationBoundUserInterface.cs diff --git a/Content.Client/Vampire/VampireMutationBoundUserInterface.cs b/Content.Client/Vampire/VampireMutationBoundUserInterface.cs new file mode 100644 index 00000000000..8b69f483c55 --- /dev/null +++ b/Content.Client/Vampire/VampireMutationBoundUserInterface.cs @@ -0,0 +1,42 @@ +using Content.Shared.Vampire; +using Content.Shared.Vampire.Components; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +namespace Content.Client.Vampire; +[UsedImplicitly] +public sealed class VampireMutationBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private VampireMutationMenu? _menu; + public VampireMutationBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + protected override void Open() + { + base.Open(); + _menu = new VampireMutationMenu(); + _menu.OnClose += Close; + _menu.OnIdSelected += OnIdSelected; + _menu.OpenCentered(); + } + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is not VampireMutationBoundUserInterfaceState st) + return; + _menu?.UpdateState(st.MutationList, st.SelectedMutation); + } + private void OnIdSelected(VampireMutationsType selectedId) + { + SendMessage(new VampireMutationPrototypeSelectedMessage(selectedId)); + } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + _menu?.Close(); + _menu = null; + } + } +} \ No newline at end of file diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index d199dd3678f..aa91c2214ab 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -24,6 +24,11 @@ public sealed partial class VampireComponent : Component public static readonly string HeirloomProto = "HeirloomVampire"; [ValidatePrototypeId] public static readonly string CurrencyProto = "BloodEssence"; + + [ViewVariables(VVAccess.ReadOnly), DataField("defaultMutation")] + public VampireMutationsType DefaultMutation = VampireMutationsType.Hemomancer; + [ViewVariables(VVAccess.ReadOnly), DataField("currentMutation")] + public VampireMutationsType CurrentMutation = VampireMutationsType.Hemomancer; public static readonly EntityWhitelist AcceptableFoods = new() { @@ -201,6 +206,42 @@ public enum VampireMutationsType : byte Bestia } +[Serializable, NetSerializable] +public sealed class VampireMutationComponentState : ComponentState +{ + public VampireMutationsType SelectedMutation; +} + +[Serializable, NetSerializable] +public sealed class VampireMutationBoundUserInterfaceState : BoundUserInterfaceState +{ + public readonly HashSet MutationList; + public readonly VampireMutationsType SelectedMutation; + + public VampireMutationBoundUserInterfaceState(HashSet mutationList, VampireMutationsType selectedId) + { + MutationList = mutationList; + SelectedMutation = selectedId; + } +} + +[Serializable, NetSerializable] +public sealed class VampireMutationPrototypeSelectedMessage : BoundUserInterfaceMessage +{ + public readonly VampireMutationsType SelectedId; + + public VampireMutationPrototypeSelectedMessage(VampireMutationsType selectedId) + { + SelectedId = selectedId; + } +} + +[Serializable, NetSerializable] +public enum VampireMutationUiKey : byte +{ + Key +} + /*[Serializable, NetSerializable] public enum VampirePowerKey : byte { From 0b8f92932b80afe6e24045481a3d0b61ac5f124f Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 09:33:57 +0200 Subject: [PATCH 31/67] upd --- .../GameTicking/Rules/VampireRuleSystem.cs | 8 ++++++-- Content.Server/Vampire/VampireSystem.Abilities.cs | 12 ++++++++++++ Content.Server/Vampire/VampireSystem.cs | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index 18d3f87de29..9253e50de81 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Roles; using Content.Shared.Store; using Content.Shared.Store.Components; +using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using System.Text; @@ -24,6 +25,7 @@ public sealed partial class VampireRuleSystem : GameRuleSystem(target); + var interfaceComponent = EnsureComp(target); + + if (HasComp(target)) + _uiSystem.SetUiState(target, VampireMutationUiKey.Key, new VampireMutationBoundUserInterfaceState()); vampireComponent.Balance = new() { { VampireComponent.CurrencyProto, 0 } }; rule.VampireMinds.Add(mindId); if (HasComp(target)) - { _vampire.AddStartingAbilities(target); - } //foreach (var objective in rule.Objectives) // _mind.TryAddObjective(mindId, mind, objective); diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 987d0938f43..c3c1919da5b 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -31,6 +31,7 @@ using Content.Shared.Weapons.Melee; using FastAccessors; using Robust.Shared.Audio; +using Robust.Shared.Player; using Robust.Shared.Containers; using Robust.Shared.Utility; using System.Collections.Frozen; @@ -75,6 +76,8 @@ private void InitializePowers() #region Ability Entry Points private void OnVampireOpenMutationsMenu(EntityUid uid, VampireComponent component, VampireOpenMutationsMenu ev) { + TryOpenUi(uid, ev.Performer, component); + ev.Handled = true; } private void OnVampireToggleFangs(EntityUid entity, VampireComponent component, VampireToggleFangsEvent ev) { @@ -751,4 +754,13 @@ private FrozenDictionary BuildPassiveCache() var protos = _prototypeManager.EnumeratePrototypes(); return protos.ToFrozenDictionary(x => x.CatalogEntry); } + + private void TryOpenUi(EntityUid uid, EntityUid user, VampireComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + if (!TryComp(user, out ActorComponent? actor)) + return; + _uiSystem.TryToggleUi(uid, VampireMutationUiKey.Key, actor.PlayerSession); + } } diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 22696d69160..79e98df94ef 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -30,6 +30,7 @@ using Content.Shared.Stunnable; using Content.Shared.Vampire; using Content.Shared.Vampire.Components; +using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -69,6 +70,7 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly MetabolizerSystem _metabolism = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; public override void Initialize() { From 3078ab09bfc9bc7d1b36d82e9df2aeca0ccc25b1 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 12:53:42 +0200 Subject: [PATCH 32/67] normal mutations menu --- .../Rules/Components/VampireRuleComponent.cs | 2 + .../GameTicking/Rules/VampireRuleSystem.cs | 2 +- .../Vampire/VampireSystem.Abilities.cs | 10 ---- Content.Server/Vampire/VampireSystem.cs | 49 ++++++++++++++++++- Content.Shared/Vampire/VampireComponent.cs | 14 ++++++ .../Prototypes/Entities/Mobs/Species/base.yml | 2 + 6 files changed, 67 insertions(+), 12 deletions(-) diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs index 48197ca452a..22fc5928fe8 100644 --- a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.NPC.Prototypes; using Content.Shared.Roles; +using Content.Shared.Vampire.Components; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -9,6 +10,7 @@ namespace Content.Server.GameTicking.Rules.Components; public sealed partial class VampireRuleComponent : Component { public readonly List VampireMinds = new(); + /* public readonly List> Objectives = new() { diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index 9253e50de81..c13aeb98d17 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -77,7 +77,7 @@ public bool MakeVampire(EntityUid target, VampireRuleComponent rule) var interfaceComponent = EnsureComp(target); if (HasComp(target)) - _uiSystem.SetUiState(target, VampireMutationUiKey.Key, new VampireMutationBoundUserInterfaceState()); + _uiSystem.SetUiState(target, VampireMutationUiKey.Key, new VampireMutationBoundUserInterfaceState(vampireComponent.VampireMutations, vampireComponent.CurrentMutation)); vampireComponent.Balance = new() { { VampireComponent.CurrencyProto, 0 } }; diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index c3c1919da5b..3ce315af347 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -31,7 +31,6 @@ using Content.Shared.Weapons.Melee; using FastAccessors; using Robust.Shared.Audio; -using Robust.Shared.Player; using Robust.Shared.Containers; using Robust.Shared.Utility; using System.Collections.Frozen; @@ -754,13 +753,4 @@ private FrozenDictionary BuildPassiveCache() var protos = _prototypeManager.EnumeratePrototypes(); return protos.ToFrozenDictionary(x => x.CatalogEntry); } - - private void TryOpenUi(EntityUid uid, EntityUid user, VampireComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - if (!TryComp(user, out ActorComponent? actor)) - return; - _uiSystem.TryToggleUi(uid, VampireMutationUiKey.Key, actor.PlayerSession); - } } diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 79e98df94ef..62def41a0ea 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -31,6 +31,8 @@ using Content.Shared.Vampire; using Content.Shared.Vampire.Components; using Robust.Server.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.GameStates; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -81,6 +83,9 @@ public override void Initialize() //SubscribeLocalEvent(OnUseTargetedPower); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnVampireBloodChangedEvent); + + SubscribeLocalEvent(GetState); + SubscribeLocalEvent(OnMutationSelected); InitializePowers(); } @@ -208,7 +213,7 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (GetBloodEssence(uid) >= FixedPoint2.New(150) && !_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) { Log.Warning($"earned enought blood for mutation"); - _action.AddAction(uid, VampireComponent.MutationsActionPrototype); + _action.AddAction(uid, ref component.MutationsAction, VampireComponent.MutationsActionPrototype); } } @@ -255,4 +260,46 @@ private bool IsNearPrayable(EntityUid vampireUid) return false; } + + private void OnMutationSelected(EntityUid uid, VampireComponent component, VampireMutationPrototypeSelectedMessage args) + { + if (component.CurrentMutation == args.SelectedId) + return; + ChangeMutation(uid, args.SelectedId, component); + } + private void ChangeMutation(EntityUid uid, VampireMutationsType newMutation, VampireComponent component) + { + if (component.MutationsAction != null) + { + _action.RemoveAction(uid, component.MutationsAction); + _uiSystem.TryToggleUi(uid, VampireMutationUiKey.Key, actor.PlayerSession); + } + component.CurrentMutation = newMutation; + UpdateUi(uid, component); + } + + private void GetState(EntityUid uid, VampireComponent component, ref ComponentGetState args) + { + args.State = new VampireMutationComponentState + { + SelectedMutation = component.CurrentMutation + }; + } + + private void TryOpenUi(EntityUid uid, EntityUid user, VampireComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + if (!TryComp(user, out ActorComponent? actor)) + return; + _uiSystem.TryToggleUi(uid, VampireMutationUiKey.Key, actor.PlayerSession); + } + + public void UpdateUi(EntityUid uid, VampireComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + var state = new VampireMutationBoundUserInterfaceState(component.VampireMutations, component.CurrentMutation); + _uiSystem.SetUiState(uid, VampireMutationUiKey.Key, state); + } } diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index aa91c2214ab..d1f51025418 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -29,6 +29,15 @@ public sealed partial class VampireComponent : Component public VampireMutationsType DefaultMutation = VampireMutationsType.Hemomancer; [ViewVariables(VVAccess.ReadOnly), DataField("currentMutation")] public VampireMutationsType CurrentMutation = VampireMutationsType.Hemomancer; + + public readonly HashSet VampireMutations = new() + { + VampireMutationsType.Hemomancer, + VampireMutationsType.Umbrae, + VampireMutationsType.Gargantua, + VampireMutationsType.Dantalion, + VampireMutationsType.Bestia + }; public static readonly EntityWhitelist AcceptableFoods = new() { @@ -54,8 +63,13 @@ public sealed partial class VampireComponent : Component [ValidatePrototypeId] public static readonly string MutationsActionPrototype = "ActionVampireOpenMutationsMenu"; + + [ViewVariables(VVAccess.ReadWrite)] + public EntityUid? MutationsAction; + [ValidatePrototypeId] public static readonly string ToggleFangsActionPrototype = "ActionVampireToggleFangs"; + [ValidatePrototypeId] public static readonly string DrinkBloodPrototype = "DrinkBlood"; diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 417808b9bf2..97019626814 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -195,6 +195,8 @@ type: StrippableBoundUserInterface enum.StoreUiKey.Key: type: StoreBoundUserInterface + enum.VampireMutationUiKey.Key: + type: VampireMutationBoundUserInterface - type: Puller - type: Speech speechSounds: Alto From 25bdcc058255a1eed7c2ec035312b5240aacb9da Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 16:34:50 +0200 Subject: [PATCH 33/67] actions translate --- .../{ => _strings}/Vampires/vampires.ftl | 0 .../ru-RU/_prototypes/actions/vampire.ftl | 16 ++++++- .../ru-RU/_strings/Vampires/vampires.ftl | 46 +++++++++++++++++++ Resources/Prototypes/Actions/vampire.yml | 32 ++++++------- 4 files changed, 77 insertions(+), 17 deletions(-) rename Resources/Locale/en-US/{ => _strings}/Vampires/vampires.ftl (100%) create mode 100644 Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl diff --git a/Resources/Locale/en-US/Vampires/vampires.ftl b/Resources/Locale/en-US/_strings/Vampires/vampires.ftl similarity index 100% rename from Resources/Locale/en-US/Vampires/vampires.ftl rename to Resources/Locale/en-US/_strings/Vampires/vampires.ftl diff --git a/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl b/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl index 5ec91979a4a..7b1ba86a452 100644 --- a/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl +++ b/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl @@ -1,4 +1,18 @@ ent-ActionVampireOpenMutationsMenu = Меню мутаций .desc = Открывает меню с мутациями вампира. ent-ActionVampireToggleFangs = Высунуть клыки - .desc = Вампир выдвигает клыки, чтобы выпить кровь из своей жертвы. \ No newline at end of file + .desc = Выдвигайте или втягивайте клыки. Если вы будете ходить с выпущенными клыками, это может раскрыть вашу истинную сущность. +ent-ActionVampireGlare = Блик + .desc = Выпустите из глаз ослепительную вспышку, оглушающую незащищенного смертного на 10 секунд. Стоимость активации: 20 крови. Время перезарядки: 60 секунд +ent-ActionVampireHypnotise = Гипноз + .desc = Глубоко загляните в глаза смертного, заставляя его уснуть на 60 секунд. Стоимость активации: 20 крови. Задержка активации: 5 секунд. Время перезарядки: 5 минут +ent-ActionVampireScreech = Визг + .desc = Издайте пронзительный крик, оглушая незащищенных смертных и разбивая вдребезги хрупкие предметы поблизости. Стоимость активации: 20 крови. Задержка активации: 5 секунд. Время перезарядки: 5 минут +ent-ActionVampireBloodSteal = Кража крови + .desc = Выжимает кровь из всех тел поблизости - живых или мертвых. Стоимость активации: 20 крови. Время перезарядки: 60 секунд +ent-ActionVampireBatform = Форма летучей мыши + .desc = Принимает форму летучей мыши. Быстрая, трудноуязвимая, любит фрукты. Стоимость активации: 20 крови. Время перезарядки: 30 секунд +ent-ActionVampireMouseform = Мышиная форма + .desc = Примите облик мыши. Быстрая, маленькая, невосприимчива к дверям. Стоимость активации: 20 крови. Время перезарядки: 30 секунд +ent-ActionVampireCloakOfDarkness = Плащ тьмы + .desc = Замаскируйте себя от глаз смертных, делая вас невидимым в неподвижном состоянии. Стоимость активации: 30 крови. Трата: 1 кровь/секунда Время перезарядки: 10 секунд \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl new file mode 100644 index 00000000000..8da7987ea8f --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl @@ -0,0 +1,46 @@ +vampires-title = Vampires + +vampire-fangs-extended-examine = Вы видите блеск [color=white]острых клыков[/color]. +vampire-fangs-extended = Вы вытягиваете свои клыки +vampire-fangs-retracted = Вы втягиваете свои клыки + +vampire-blooddrink-empty = Это тело лишено крови +vampire-blooddrink-rotted = Их тела гниют, а кровь запятнана +vampire-blooddrink-zombie = Их кровь запятнана смертью + +vampire-startlight-burning = Вы чувствуете, как ваша кожа горит в свете тысячи солнц + +vampire-not-enough-blood = У вас недостаточно крови +vampire-cuffed = Вам нужны свободные руки! +vampire-stunned = Вы не сможете сосредоточиться! +vampire-muffled = Ваш рот закрыт намордником +vampire-full-stomach = Вас раздуло от крови + +vampire-deathsembrace-bind = Чувствуешь себя как дома + +vampire-ingest-holyblood = Ваш рот горит! + +vampire-cloak-enable = Вы окутываете тенью свою форму +vampire-cloak-disable = Вы ослабляете хватку теней. + +vampire-bloodsteal-other = Вы чувствуете, как кровь вырывается из вашего тела! +vampire-hypnotise-other = {CAPITALIZE(THE($user))} пристально вглядывается в {MAKEPLURAL(THE($target))} глаза! + +store-currency-display-blood-essence = Кровавая эссенция +store-category-vampirepowers = Силы +store-category-vampirepassives = Пассивные + +#Powers + +vampire-power-blessing = Blessing of Lilith +vampire-power-blessing-description = Swear your soul to Lilith, receive her blessing, and feast upon the bounty around you. + +#Passives +vampire-passive-unholystrength = Unholy Strength +vampire-passive-unholystrength-description = Infuse your upper body muscles with essence, granting you claws and increased strength. Effect: 10 Slash per hit + +vampire-passive-supernaturalstrength = Supernatural Strength +vampire-passive-supernaturalstrength-description = Increase your upper body muscles strength further, no barrier shall stand in your way. Effect: 15 Slash per hit, able to pry open doors by hand. + +vampire-passive-deathsembrace = Deaths Embrace +vampire-passive-deathsembrace-description = Embrace death and it shall pass you over. Effect: Heal when in a coffin, automatically return to your coffin upon death for 100 blood essence. \ No newline at end of file diff --git a/Resources/Prototypes/Actions/vampire.yml b/Resources/Prototypes/Actions/vampire.yml index 23482d9a6bd..5f7872ef9c4 100644 --- a/Resources/Prototypes/Actions/vampire.yml +++ b/Resources/Prototypes/Actions/vampire.yml @@ -1,7 +1,7 @@ - type: entity id: ActionVampireOpenMutationsMenu name: Mutations menu - description: Opens menu with vampires mutations. + description: "Opens menu with vampires mutations." categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -16,7 +16,7 @@ - type: entity id: ActionVampireToggleFangs name: Toggle Fangs - description: The vampire extends their fangs so they may drink from their victim. + description: "Extend or retract your fangs. Walking around with your fangs out might reveal your true nature." categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -35,8 +35,8 @@ - type: entity id: ActionVampireGlare - name: vampire-power-glare - description: vampire-power-glare-description + name: Glare + description: "Release a blinding flash from your eyes, stunning a unprotected mortal for 10 seconds. Activation Cost: 20 Essence. Cooldown: 60 Seconds" categories: [ HideSpawnMenu ] components: - type: EntityTargetAction @@ -57,8 +57,8 @@ - type: entity id: ActionVampireHypnotise - name: vampire-power-hypnotise - description: vampire-power-hypnotise-description + name: Hypnotise + description: "Stare deeply into a mortals eyes, forcing them to sleep for 60 seconds. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes" categories: [ HideSpawnMenu ] components: - type: EntityTargetAction @@ -79,8 +79,8 @@ - type: entity id: ActionVampireScreech - name: vampire-power-screech - description: vampire-power-screech-description + name: Screech + description: "Release a piercing scream, stunning unprotected mortals and shattering fragile objects nearby. Activation Cost: 20 Essence. Activation Delay: 5 Seconds. Cooldown: 5 Minutes" categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -99,8 +99,8 @@ - type: entity id: ActionVampireBloodSteal - name: vampire-power-bloodsteal - description: vampire-power-bloodsteal-description + name: Blood Steal + description: "Wrench the blood from all bodies nearby - living or dead. Activation Cost: 20 Essence. Cooldown: 60 Seconds" categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -119,8 +119,8 @@ - type: entity id: ActionVampireBatform - name: vampire-power-batform - description: vampire-power-batform-description + name: Bat Form + description: "Assume for form of a bat. Fast, Hard to Hit, Likes fruit. Activation Cost: 20 Essence. Cooldown: 30 Seconds" categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -139,8 +139,8 @@ - type: entity id: ActionVampireMouseform - name: vampire-power-mouseform - description: vampire-power-mouseform-description + name: Mouse Form + description: "Assume for form of a mouse. Fast, Small, Immune to doors. Activation Cost: 20 Essence. Cooldown: 30 Seconds" categories: [ HideSpawnMenu ] components: - type: InstantAction @@ -159,8 +159,8 @@ - type: entity id: ActionVampireCloakOfDarkness - name: vampire-power-cloakofdarkness - description: vampire-power-cloakofdarkness-description + name: Cloak of Darkness + description: "Cloak yourself from mortal eyes, rendering you invisible while stationary. Activation Cost: 30 Essence. Upkeep: 1 Essence/Second Cooldown: 10 Seconds" categories: [ HideSpawnMenu ] components: - type: InstantAction From 16647a0cb5aa2c3eaa68c8565a9b2162886e0d24 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 16:35:28 +0200 Subject: [PATCH 34/67] None type, base actions list --- .../Vampire/VampireMutationMenu.xaml.cs | 1 + .../Vampire/VampireSystem.Transform.cs | 27 +++++++++++-------- Content.Shared/Vampire/VampireComponent.cs | 13 ++++++--- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Content.Client/Vampire/VampireMutationMenu.xaml.cs b/Content.Client/Vampire/VampireMutationMenu.xaml.cs index 87c29565256..d4616dff9d7 100644 --- a/Content.Client/Vampire/VampireMutationMenu.xaml.cs +++ b/Content.Client/Vampire/VampireMutationMenu.xaml.cs @@ -53,6 +53,7 @@ private void UpdateGrid() string texturePath = Mutation switch { + VampireMutationsType.None => "/Textures/Interface/Actions/actions_vampire.rsi/deathsembrace.png", VampireMutationsType.Hemomancer => "/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png", VampireMutationsType.Umbrae => "/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png", VampireMutationsType.Gargantua => "/Textures/Interface/Actions/actions_vampire.rsi/gargantua.png", diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs index cbc159e4ff5..21f29b67f90 100644 --- a/Content.Server/Vampire/VampireSystem.Transform.cs +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -94,19 +94,24 @@ public void AddStartingAbilities(EntityUid vampire) if (!TryComp(vampire, out var comp)) return; - var action = _action.AddAction(vampire, VampireComponent.ToggleFangsActionPrototype); - if (!action.HasValue) - return; - - if (!TryComp(action, out var instantActionComponent)) - return; - - var actionEvent = instantActionComponent.Event as VampireSelfPowerEvent; + foreach (var actionId in comp.BaseVampireActions) + { + var action = _action.AddAction(vampire, actionId); + + if (!action.HasValue) + return; + + if (!TryComp(action, out var instantActionComponent)) + return; + + var actionEvent = instantActionComponent.Event as VampireSelfPowerEvent; - if (actionEvent == null) - return; + if (actionEvent == null) + return; - comp.UnlockedPowers.Add(actionEvent.DefinitionName, action); + comp.UnlockedPowers.Add(actionEvent.DefinitionName, action); + + } UpdateBloodDisplay(vampire); } diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index d1f51025418..7c87358c8a1 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -26,12 +26,13 @@ public sealed partial class VampireComponent : Component public static readonly string CurrencyProto = "BloodEssence"; [ViewVariables(VVAccess.ReadOnly), DataField("defaultMutation")] - public VampireMutationsType DefaultMutation = VampireMutationsType.Hemomancer; + public VampireMutationsType DefaultMutation = VampireMutationsType.None; [ViewVariables(VVAccess.ReadOnly), DataField("currentMutation")] - public VampireMutationsType CurrentMutation = VampireMutationsType.Hemomancer; + public VampireMutationsType CurrentMutation = VampireMutationsType.None; public readonly HashSet VampireMutations = new() { + VampireMutationsType.None, VampireMutationsType.Hemomancer, VampireMutationsType.Umbrae, VampireMutationsType.Gargantua, @@ -67,8 +68,11 @@ public sealed partial class VampireComponent : Component [ViewVariables(VVAccess.ReadWrite)] public EntityUid? MutationsAction; - [ValidatePrototypeId] - public static readonly string ToggleFangsActionPrototype = "ActionVampireToggleFangs"; + public readonly List> BaseVampireActions = new() + { + "ActionVampireToggleFangs", + "ActionVampireHypnotise" + }; [ValidatePrototypeId] public static readonly string DrinkBloodPrototype = "DrinkBlood"; @@ -213,6 +217,7 @@ public sealed partial class VampireSealthComponent : Component [Serializable, NetSerializable] public enum VampireMutationsType : byte { + None, Hemomancer, Umbrae, Gargantua, From 4b0e8beae82ecb180a098d7caf71f4a6d6c7bb7c Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 16:35:44 +0200 Subject: [PATCH 35/67] passive abilities --- .../Vampire/VampireSystem.Abilities.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 3ce315af347..3f0c494cb7d 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -252,26 +252,26 @@ private bool IsAbilityUsable(Entity vampire, VampirePowerProty #region Passive Powers - /*private void UnnaturalStrength(Entity vampire, VampirePowerDetails def) + private void UnnaturalStrength(Entity vampire) { - if (def.Damage is null) - return; + var damage = new DamageSpecifier(); + damage.DamageDict.Add("Slash", 15); var meleeComp = EnsureComp(vampire); - meleeComp.Damage += def.Damage; + meleeComp.Damage += damage; } - private void SupernaturalStrength(Entity vampire, VampirePowerDetails def) + private void SupernaturalStrength(Entity vampire) { var pryComp = EnsureComp(vampire); pryComp.Force = true; pryComp.PryPowered = true; - if (def.Damage is null) - return; + var damage = new DamageSpecifier(); + damage.DamageDict.Add("Slash", 15); var meleeComp = EnsureComp(vampire); - meleeComp.Damage += def.Damage; - }*/ + meleeComp.Damage += damage; + } #endregion #region Other Powers @@ -642,7 +642,7 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood return; } - var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume); + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume * 2); //Slurp _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); From 9d02ccac0192fc52e572570128c31e94846bd8f3 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 16:36:01 +0200 Subject: [PATCH 36/67] ability earning --- Content.Server/Vampire/VampireSystem.cs | 65 +++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 62def41a0ea..adb2421d9ce 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -209,12 +209,69 @@ public void UpdateBloodDisplay(EntityUid vampire) private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent component, VampireBloodChangedEvent args) { - Log.Warning($"Blood amount: {GetBloodEssence(uid)}"); + // Mutations if (GetBloodEssence(uid) >= FixedPoint2.New(150) && !_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) { - Log.Warning($"earned enought blood for mutation"); _action.AddAction(uid, ref component.MutationsAction, VampireComponent.MutationsActionPrototype); } + + //Hemomancer + + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireBloodSteal") && component.CurrentMutation == VampireMutationsType.Hemomancer) + { + var action = _action.AddAction(uid, "ActionVampireBloodSteal"); + component.UnlockedPowers.Add("BloodSteal", action); + } + + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireScreech") && component.CurrentMutation == VampireMutationsType.Hemomancer) + { + var action = _action.AddAction(uid, "ActionVampireScreech"); + component.UnlockedPowers.Add("Screech", action); + } + + //Umbrae + + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireGlare") && component.CurrentMutation == VampireMutationsType.Umbrae) + { + _action.AddAction(uid, "ActionVampireGlare"); + component.UnlockedPowers.Add("Glare", action); + } + + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireCloakOfDarkness") && component.CurrentMutation == VampireMutationsType.Umbrae) + { + _action.AddAction(uid, "ActionVampireCloakOfDarkness"); + component.UnlockedPowers.Add("CloakOfDarkness", action); + } + + //Gargantua + + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && && component.CurrentMutation == VampireMutationsType.Gargantua) + { + var vampire = new Entity(uid, component); + + UnnaturalStrength(vampire); + } + + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && && component.CurrentMutation == VampireMutationsType.Gargantua) + { + var vampire = new Entity(uid, component); + + SupernaturalStrength(vampire); + } + + //Bestia + + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireBatform") && component.CurrentMutation == VampireMutationsType.Bestia) + { + _action.AddAction(uid, "ActionVampireBatform"); + component.UnlockedPowers.Add("PolymorphBat", action); + } + + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireMouseform") && component.CurrentMutation == VampireMutationsType.Bestia) + { + _action.AddAction(uid, "ActionVampireMouseform"); + component.UnlockedPowers.Add("PolymorphMouse", action); + } } private FixedPoint2 GetBloodEssence(EntityUid vampire) @@ -272,10 +329,12 @@ private void ChangeMutation(EntityUid uid, VampireMutationsType newMutation, Vam if (component.MutationsAction != null) { _action.RemoveAction(uid, component.MutationsAction); - _uiSystem.TryToggleUi(uid, VampireMutationUiKey.Key, actor.PlayerSession); + TryOpenUi(uid, component.Owner, component); } component.CurrentMutation = newMutation; UpdateUi(uid, component); + var ev = new VampireBloodChangedEvent(); + RaiseLocalEvent(uid, ev); } private void GetState(EntityUid uid, VampireComponent component, ref ComponentGetState args) From 389059e6bd33e5a47e5b6a63cf0e92bece633c32 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 18:18:23 +0200 Subject: [PATCH 37/67] fix --- Content.Server/Vampire/VampireSystem.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index adb2421d9ce..8c4c34bce77 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -233,26 +233,26 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireGlare") && component.CurrentMutation == VampireMutationsType.Umbrae) { - _action.AddAction(uid, "ActionVampireGlare"); + var action = _action.AddAction(uid, "ActionVampireGlare"); component.UnlockedPowers.Add("Glare", action); } if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireCloakOfDarkness") && component.CurrentMutation == VampireMutationsType.Umbrae) { - _action.AddAction(uid, "ActionVampireCloakOfDarkness"); + var action = _action.AddAction(uid, "ActionVampireCloakOfDarkness"); component.UnlockedPowers.Add("CloakOfDarkness", action); } //Gargantua - if (GetBloodEssence(uid) >= FixedPoint2.New(200) && && component.CurrentMutation == VampireMutationsType.Gargantua) + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && component.CurrentMutation == VampireMutationsType.Gargantua) { var vampire = new Entity(uid, component); UnnaturalStrength(vampire); } - if (GetBloodEssence(uid) >= FixedPoint2.New(300) && && component.CurrentMutation == VampireMutationsType.Gargantua) + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && component.CurrentMutation == VampireMutationsType.Gargantua) { var vampire = new Entity(uid, component); @@ -263,13 +263,13 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireBatform") && component.CurrentMutation == VampireMutationsType.Bestia) { - _action.AddAction(uid, "ActionVampireBatform"); + var action = _action.AddAction(uid, "ActionVampireBatform"); component.UnlockedPowers.Add("PolymorphBat", action); } if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireMouseform") && component.CurrentMutation == VampireMutationsType.Bestia) { - _action.AddAction(uid, "ActionVampireMouseform"); + var action = _action.AddAction(uid, "ActionVampireMouseform"); component.UnlockedPowers.Add("PolymorphMouse", action); } } From 21fb36b56e17691fb44263f1adf7297e3b75f079 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 18:18:39 +0200 Subject: [PATCH 38/67] translate vampire preset --- .../game-ticking/game-presets/preset-vampire.ftl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl diff --git a/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl new file mode 100644 index 00000000000..faab5fe71a7 --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl @@ -0,0 +1,11 @@ +vampire-roundend-name = вампир +objective-issuer-vampire = [color=orange]Коллективный разум[/color] +roundend-prepend-vampire-drained-named = [color=white]{ $name }[/color] выпил в общей сложности [color=red]{ $number }[/color] крови. +roundend-prepend-vampire-absorbed = Кто-то выпил в общей сложности [color=red]{ $number }[/color] крови. +vampire-gamemode-title = Вампиры +vampire-gamemode-description = Кровожадные вампиры пробрались на станцию чтобы вдоволь напиться крови! +vampire-role-greeting = + Вы вампир, который пробрался на станцию под видом работника! + Ваши задачи указаны в меню персонажа. + Пейте кровь и эволюционируйте, чтобы выполнить их! +vampire-role-greeting-short = Вы вампир, который пробрался на станцию под видом работника! \ No newline at end of file From 0407ccf506e488b185191adfe8850aea83154589 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 18:18:55 +0200 Subject: [PATCH 39/67] translate vampire mutations menu --- .../ru-RU/_strings/Vampires/vampires.ftl | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl index 8da7987ea8f..4298220a3b9 100644 --- a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl +++ b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl @@ -1,4 +1,4 @@ -vampires-title = Vampires +vampires-title = Вампиры vampire-fangs-extended-examine = Вы видите блеск [color=white]острых клыков[/color]. vampire-fangs-extended = Вы вытягиваете свои клыки @@ -43,4 +43,40 @@ vampire-passive-supernaturalstrength = Supernatural Strength vampire-passive-supernaturalstrength-description = Increase your upper body muscles strength further, no barrier shall stand in your way. Effect: 15 Slash per hit, able to pry open doors by hand. vampire-passive-deathsembrace = Deaths Embrace -vampire-passive-deathsembrace-description = Embrace death and it shall pass you over. Effect: Heal when in a coffin, automatically return to your coffin upon death for 100 blood essence. \ No newline at end of file +vampire-passive-deathsembrace-description = Embrace death and it shall pass you over. Effect: Heal when in a coffin, automatically return to your coffin upon death for 100 blood essence. + +#Mutation menu + +vampire-mutation-menu-ui-window-name = Меню мутаций + +vampire-mutation-none-info = Ничего не выбрано + +vampire-mutation-hemomancer-info = + Гемомансер + + Фокусируеться на кровавой магии и манипуляции крови вокруг себя. + + Способности: + + - Визг + - Кража крови + +vampire-mutation-umbrae-info = + Тень + + Фокусируется на темноте, стелсе, мобильности. + + Способности: + + - Блик + - Плащ тьмы + +vampire-mutation-bestia-info = + Бестия + + Фокусируется на превращении и собирании трофеев + + Способности: + + - Форма летучей мыши + - Мышиная форма \ No newline at end of file From f270cdd7c2b34fe72bc52f16b3e8e293388e6572 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 18:19:07 +0200 Subject: [PATCH 40/67] mutations menu textures --- .../Actions/actions_vampire.rsi/bestia.png | Bin 0 -> 2272 bytes .../Actions/actions_vampire.rsi/hemomancer.png | Bin 0 -> 2057 bytes .../Actions/actions_vampire.rsi/umbrae.png | Bin 0 -> 2368 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/bestia.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/bestia.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/bestia.png new file mode 100644 index 0000000000000000000000000000000000000000..45e82590c0f310327eeaeb0303925114e290eb2a GIT binary patch literal 2272 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png new file mode 100644 index 0000000000000000000000000000000000000000..8c198b76910b6395cce7a8af4513459fc88d172e GIT binary patch literal 2368 zcmV-G3BUGPx-;7LS5R9J#KcYPUGJ{fUdQXb zoHH|hILkJ!^H}C(M@KvJ{pbJvznieu+R3B)c>L)oU)0Ly^PPRQS_R;h=Ql96=S`f+ zqx%4O=1_xQ|Nh!948)b|a}b+0Z9*J4aKK*8_>X?}6aW+N9B1GLye9d`M>CrrjNaMeEo_q z7A#09kV2rOXzxQ17K;_(udxPgG=T}w)?l%nTW-7UKD->G^b`JkRtq=@ZvOrCe5J9 zw%iu5U^+|~thF5gQRXQ|8;DHV#$&RnAIH>mp! z0u!utK|?oL@mHt^b&B~s|N3ZxsflwKYlvr3xTzR*zk!f~AKd;3Hx7?;@TJEn6bc-C z=`k#pWGsoXnx6hXG6Pva@X6^je0=I7%5!sVhuDs5RspE{br$PYmYd70_;nUb^PHcU zWT8~XSVJt+P0&`i5CWqu+xxe5E{c0`29jAEsmSIAP(o5ISNL?|Ec3+@h|s9EhQR2x z8ffYk4Znc}9O+Ol7HI`O25_XJTC5jv9*L9i5-c_rxn<-_ok&nh;W{phr3&yV z7tfvN{KQ!n%k#L>#gPi%_~@$v2$gH`b&F=u0t@B2Iot^sS2;M+W$VxuhI@wiVEQCV zO6q>SbD1L@x?>5pDv@ zM=G>6Yc(K57;j;cu^p{QAyGmx(lbmV8fUp#17K5TlrA?$&0nF~suK6Q`1s-{^r!my z%H};NDba1cAS^*UMH_4Jl)DxH2wde6b7LeT-Nd{uB90g4f1&72WcbTJU#8~QNJhJv zS)L{C#R*KnP$Ea&Z?M$3%oDHvf?Rr#JGS0Vx+{$%9USEWmOuwc0Z})$7J#D^39pNU z7bogOPzaP%NFljyaFlc`q&gW%B9ROwax673vmt#AXG&*DMU#y7Y-D?O8^3$|Ijl8A zy$IT9w9y#CL}vjdr|Ud^mS$U zY+;(MeVYkPzanJ)jEy5FD~G!R0BUbSGZh=L9Pt;6i3ghELL7?e^d zDT%ld)^(2%b)wY$29^2(Q71w(Xp!q4v!K zxmOoqGicJ%0a8eW6=Z8X1q>sgF3I6|R>BNh_&UIKT!avK%Egh+ss~-cKu8~! zuq>MpM9htnbh}A-afA@$5`*DEA-Q?@CMxv>`n$3eYb9FRXCxVxS#34xXo^=){S`+# z2qB3m4^Mfk@<4}+&zs6bUmBw|N-CD-7wC#~VXbAkRijiZla2SW*sQkC7%p9&0bnpb zz=i5G*QC~w@Zzkr!bllSZ$zeDw6zEeg~BY#TqO*xwX}4Tu1tdK#>dz)zJuO@el`t` zB9SaLFS8P?P^y(__+eM8gd`P7QCcZ8S1WQ&YF$SshEVA3FLUGQ4P0E9;Zkje>GDN%QG4$-aWy| z=~IAZU1}YR^(wb+yoGc5PjHpXRPnP;irqhUH&ewann8G=mm_4%%(D2;-kHi@MS! z=ER7)QC#Vugk)^j4P*xTNv%%+@Z;}It!5yVN&#^E*jpSw_7?X&_-!)TKGNA^ZD@cr=vJyd){R7=su1dyMGlEU(`Br;zVbE`0!!=1KET%3SwuTCjbBd m4rN$LW=%~1DgXcg2mk;800000(o>TF0000 Date: Wed, 30 Oct 2024 18:19:17 +0200 Subject: [PATCH 41/67] disable some --- Content.Shared/Vampire/VampireComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index 7c87358c8a1..f4eb614ab19 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -36,7 +36,7 @@ public sealed partial class VampireComponent : Component VampireMutationsType.Hemomancer, VampireMutationsType.Umbrae, VampireMutationsType.Gargantua, - VampireMutationsType.Dantalion, + //VampireMutationsType.Dantalion, VampireMutationsType.Bestia }; From 7818f28964f2303f399d36326246936cb23123d0 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 19:14:45 +0200 Subject: [PATCH 42/67] =?UTF-8?q?=D0=94=D0=A0=D0=90=D0=9D=D0=AB=D0=99=20?= =?UTF-8?q?=D0=91=D0=A0=D0=98=D0=A4=20=D0=A4=D0=90=D0=A2=D0=90=D0=9B=D0=9A?= =?UTF-8?q?=D0=A3=20=D0=9F=D0=9E=20=D0=9F=D0=A0=D0=98=D0=9A=D0=9E=D0=9B?= =?UTF-8?q?=D0=A3=20=D0=91=D0=95=D0=95=D0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTicking/Rules/VampireRuleSystem.cs | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index c13aeb98d17..235317469c4 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -41,13 +41,17 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnGetBriefing); + SubscribeLocalEvent(OnSelectAntag); //SubscribeLocalEvent(OnTextPrepend); } - private void OnSelectAntag(EntityUid uid, VampireRuleComponent comp, ref AfterAntagEntitySelectedEvent args) + private void OnSelectAntag(EntityUid mindId, VampireRuleComponent comp, ref AfterAntagEntitySelectedEvent args) { - MakeVampire(args.EntityUid, comp); + var ent = args.EntityUid; + _antag.SendBriefing(ent, MakeBriefing(ent), Color.Yellow, BriefingSound); + MakeVampire(ent, comp); } public bool MakeVampire(EntityUid target, VampireRuleComponent rule) { @@ -60,9 +64,9 @@ public bool MakeVampire(EntityUid target, VampireRuleComponent rule) var briefing = Loc.GetString("vampire-role-greeting", ("name", metaData?.EntityName ?? "Unknown")); var briefingShort = Loc.GetString("vampire-role-greeting-short", ("name", metaData?.EntityName ?? "Unknown")); - _antag.SendBriefing(target, briefing, Color.Yellow, BriefingSound); _role.MindHasRole(mindId, out var vampireRole); - if (vampireRole is not null) + _role.MindHasRole(mindId, out var briefingComp); + if (vampireRole is not null && briefingComp is null) { AddComp(vampireRole.Value.Owner); Comp(vampireRole.Value.Owner).Briefing = briefing; @@ -91,6 +95,27 @@ public bool MakeVampire(EntityUid target, VampireRuleComponent rule) return true; } + + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) + { + var ent = args.Mind.Comp.OwnedEntity; + + if (ent is null) + return; + args.Append(MakeBriefing(ent.Value)); + } + + private string MakeBriefing(EntityUid ent) + { + if (TryComp(ent, out var metaData)) + { + var briefing = Loc.GetString("vampire-role-greeting", ("name", metaData?.EntityName ?? "Unknown")); + + return briefing; + } + + return ""; + } /* private void OnTextPrepend(EntityUid uid, VampireRuleComponent comp, ref ObjectivesTextPrependEvent args) { From 5ddc73b87295dabee7ced4585fbbb662b1fad912 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 19:15:04 +0200 Subject: [PATCH 43/67] game preset --- Resources/Prototypes/game_presets.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index d9fd4f661cc..8a6962de55f 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -271,3 +271,21 @@ - BasicStationEventScheduler - MeteorSwarmScheduler - BasicRoundstartVariation + +- type: gamePreset + id: Vampire + alias: + - vamp + - vamps + - vampire + - vampires + name: vampire-gamemode-title + description: vampire-gamemode-description + showInVote: true + minPlayers: 50 # Sunrise-Edit: 50 players for VAMPIRES + rules: + - Vampire + - SubGamemodesRule + - BasicStationEventScheduler + - MeteorSwarmScheduler + - BasicRoundstartVariation From 059fab5d482e90bfa958c5c7cfbecae60c738150 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 19:42:15 +0200 Subject: [PATCH 44/67] mutation can infinity changed --- Content.Server/Vampire/VampireSystem.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 8c4c34bce77..00dd2bd1c85 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -210,10 +210,18 @@ public void UpdateBloodDisplay(EntityUid vampire) private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent component, VampireBloodChangedEvent args) { // Mutations - if (GetBloodEssence(uid) >= FixedPoint2.New(150) && !_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) + if (GetBloodEssence(uid) >= FixedPoint2.New(50) && !_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) { _action.AddAction(uid, ref component.MutationsAction, VampireComponent.MutationsActionPrototype); } + else if (_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) + { + if (!TryComp(uid, out ActionsComponent? comp) || component.MutationsAction is null) + return; + + _action.RemoveAction(uid, component.MutationsAction, comp); + _actionContainer.RemoveAction(component.MutationsAction.Value); + } //Hemomancer @@ -326,15 +334,14 @@ private void OnMutationSelected(EntityUid uid, VampireComponent component, Vampi } private void ChangeMutation(EntityUid uid, VampireMutationsType newMutation, VampireComponent component) { - if (component.MutationsAction != null) + var vampire = new Entity(uid, component); + if (SubtractBloodEssence(vampire, FixedPoint2.New(50))) { - _action.RemoveAction(uid, component.MutationsAction); - TryOpenUi(uid, component.Owner, component); + component.CurrentMutation = newMutation; + UpdateUi(uid, component); + var ev = new VampireBloodChangedEvent(); + RaiseLocalEvent(uid, ev); } - component.CurrentMutation = newMutation; - UpdateUi(uid, component); - var ev = new VampireBloodChangedEvent(); - RaiseLocalEvent(uid, ev); } private void GetState(EntityUid uid, VampireComponent component, ref ComponentGetState args) From a617f77a50ce9e525015eff2c4bcc7338f0d0971 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 19:54:48 +0200 Subject: [PATCH 45/67] fix --- Content.Server/Vampire/VampireSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 00dd2bd1c85..852f982509f 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -214,7 +214,7 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen { _action.AddAction(uid, ref component.MutationsAction, VampireComponent.MutationsActionPrototype); } - else if (_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) + else if (GetBloodEssence(uid) < FixedPoint2.New(50) && _actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) { if (!TryComp(uid, out ActionsComponent? comp) || component.MutationsAction is null) return; From 13c222013b233dd2e41ff1fe78678788e7a88d39 Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 21:45:13 +0200 Subject: [PATCH 46/67] fully rework abilities update system --- Content.Server/Vampire/VampireSystem.cs | 134 +++++++++++++++++++----- 1 file changed, 110 insertions(+), 24 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 852f982509f..7fc72ffac32 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -73,6 +73,8 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly MetabolizerSystem _metabolism = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + + private Dictionary _actionEntities = new(); public override void Initialize() { @@ -209,46 +211,103 @@ public void UpdateBloodDisplay(EntityUid vampire) private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent component, VampireBloodChangedEvent args) { + EntityUid? newEntity = null; + EntityUid entity = default; // Mutations - if (GetBloodEssence(uid) >= FixedPoint2.New(50) && !_actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) + if (GetBloodEssence(uid) >= FixedPoint2.New(50) && !_actionEntities.TryGetValue(VampireComponent.MutationsActionPrototype, out entity)) { - _action.AddAction(uid, ref component.MutationsAction, VampireComponent.MutationsActionPrototype); + _action.AddAction(uid, ref newEntity, VampireComponent.MutationsActionPrototype); + if (newEntity != null) + _actionEntities[VampireComponent.MutationsActionPrototype] = newEntity.Value; } - else if (GetBloodEssence(uid) < FixedPoint2.New(50) && _actionContainer.HasAction(uid, "ActionVampireOpenMutationsMenu")) + else if (GetBloodEssence(uid) < FixedPoint2.New(50) && _actionEntities.TryGetValue(VampireComponent.MutationsActionPrototype, out entity)) { - if (!TryComp(uid, out ActionsComponent? comp) || component.MutationsAction is null) + if (!TryComp(uid, out ActionsComponent? comp)) return; - _action.RemoveAction(uid, component.MutationsAction, comp); - _actionContainer.RemoveAction(component.MutationsAction.Value); + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove(VampireComponent.MutationsActionPrototype); } //Hemomancer - if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireBloodSteal") && component.CurrentMutation == VampireMutationsType.Hemomancer) + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionEntities.TryGetValue("ActionVampireBloodSteal", out entity) && component.CurrentMutation == VampireMutationsType.Hemomancer) + { + _action.AddAction(uid, ref newEntity , "ActionVampireBloodSteal"); + if (newEntity != null) + { + _actionEntities["ActionVampireBloodSteal"] = newEntity.Value; + component.UnlockedPowers.Add("BloodSteal", newEntity); + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireBloodSteal", out entity)) { - var action = _action.AddAction(uid, "ActionVampireBloodSteal"); - component.UnlockedPowers.Add("BloodSteal", action); + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireBloodSteal"); } - if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireScreech") && component.CurrentMutation == VampireMutationsType.Hemomancer) + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireScreech", out entity) && component.CurrentMutation == VampireMutationsType.Hemomancer) { - var action = _action.AddAction(uid, "ActionVampireScreech"); - component.UnlockedPowers.Add("Screech", action); + _action.AddAction(uid, ref newEntity , "ActionVampireScreech"); + if (newEntity != null) + { + _actionEntities["ActionVampireScreech"] = newEntity.Value; + component.UnlockedPowers.Add("Screech", newEntity); + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(300) && _actionEntities.TryGetValue("ActionVampireScreech", out entity)) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireScreech"); } //Umbrae - if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireGlare") && component.CurrentMutation == VampireMutationsType.Umbrae) + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionEntities.TryGetValue("ActionVampireGlare", out entity) && component.CurrentMutation == VampireMutationsType.Umbrae) { - var action = _action.AddAction(uid, "ActionVampireGlare"); - component.UnlockedPowers.Add("Glare", action); + _action.AddAction(uid, ref newEntity , "ActionVampireGlare"); + if (newEntity != null) + { + _actionEntities["ActionVampireGlare"] = newEntity.Value; + component.UnlockedPowers.Add("Glare", newEntity); + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireGlare", out entity)) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireGlare"); } - if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireCloakOfDarkness") && component.CurrentMutation == VampireMutationsType.Umbrae) + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireCloakOfDarkness", out entity) && component.CurrentMutation == VampireMutationsType.Umbrae) { - var action = _action.AddAction(uid, "ActionVampireCloakOfDarkness"); - component.UnlockedPowers.Add("CloakOfDarkness", action); + _action.AddAction(uid, ref newEntity , "ActionVampireCloakOfDarkness"); + if (newEntity != null) + { + _actionEntities["ActionVampireCloakOfDarkness"] = newEntity.Value; + component.UnlockedPowers.Add("CloakOfDarkness", newEntity); + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(300) && _actionEntities.TryGetValue("ActionVampireCloakOfDarkness", out entity)) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireCloakOfDarkness"); } //Gargantua @@ -269,16 +328,42 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen //Bestia - if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionContainer.HasAction(uid, "ActionVampireBatform") && component.CurrentMutation == VampireMutationsType.Bestia) + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionEntities.TryGetValue("ActionVampireBatform", out entity) && component.CurrentMutation == VampireMutationsType.Bestia) + { + _action.AddAction(uid, ref newEntity , "ActionVampireBatform"); + if (newEntity != null) + { + _actionEntities["ActionVampireBatform"] = newEntity.Value; + component.UnlockedPowers.Add("PolymorphBat", newEntity); + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireBatform", out entity)) { - var action = _action.AddAction(uid, "ActionVampireBatform"); - component.UnlockedPowers.Add("PolymorphBat", action); + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireBatform"); } - if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionContainer.HasAction(uid, "ActionVampireMouseform") && component.CurrentMutation == VampireMutationsType.Bestia) + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireMouseform", out entity) && component.CurrentMutation == VampireMutationsType.Bestia) { - var action = _action.AddAction(uid, "ActionVampireMouseform"); - component.UnlockedPowers.Add("PolymorphMouse", action); + _action.AddAction(uid, ref newEntity , "ActionVampireMouseform"); + if (newEntity != null) + { + _actionEntities["ActionVampireMouseform"] = newEntity.Value; + component.UnlockedPowers.Add("PolymorphMouse", newEntity); + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(300) && _actionEntities.TryGetValue("ActionVampireMouseform", out entity)) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireMouseform"); } } @@ -341,6 +426,7 @@ private void ChangeMutation(EntityUid uid, VampireMutationsType newMutation, Vam UpdateUi(uid, component); var ev = new VampireBloodChangedEvent(); RaiseLocalEvent(uid, ev); + TryOpenUi(uid, component.Owner, component); } } From 74a6cb307c460afb50891bd8f3e3ecfba730a8de Mon Sep 17 00:00:00 2001 From: Rinary Date: Wed, 30 Oct 2024 21:49:42 +0200 Subject: [PATCH 47/67] gargantua info --- Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl index 4298220a3b9..299171fa484 100644 --- a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl +++ b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl @@ -70,6 +70,16 @@ vampire-mutation-umbrae-info = - Блик - Плащ тьмы + +vampire-mutation-gargantua-info = + Гаргантюа + + Фокусируется на ближнем уроне и стойкости. + + Способности: + + - Нечестивая сила + - Сверхъестественная сила vampire-mutation-bestia-info = Бестия From 0863cfe7a599aaa67aa0c3ad3365600ef50aa3e3 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 10:08:23 +0200 Subject: [PATCH 48/67] update ignore list --- Tools/_sunrise/Schemas/ignore_list.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Tools/_sunrise/Schemas/ignore_list.yml b/Tools/_sunrise/Schemas/ignore_list.yml index 2342d75058a..9b1d3adaac0 100644 --- a/Tools/_sunrise/Schemas/ignore_list.yml +++ b/Tools/_sunrise/Schemas/ignore_list.yml @@ -137,13 +137,14 @@ ignore_list: - 'TR-263' ignore_files: - - italian.ftl - - russian.ftl - - popup.ftl - - controls.ftl - - input.ftl - - speech-chatsan.ftl - - speech-liar.ftl - - german.ftl - - southern.ftl - - tts-voices-sunrise.ftl \ No newline at end of file + - 'italian.ftl' + - 'russian.ftl' + - 'popup.ftl' + - 'controls.ftl' + - 'input.ftl' + - 'speech-chatsan.ftl' + - 'speech-liar.ftl' + - 'german.ftl' + - 'southern.ftl' + - 'tts-voices-sunrise.ftl' + - 'lobby.ftl' \ No newline at end of file From c69424c438cef323192443bab61e3cc4b7963d02 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 10:26:53 +0200 Subject: [PATCH 49/67] fix gargantua passives --- Content.Server/Vampire/VampireSystem.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 7fc72ffac32..72557e5834b 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -312,18 +312,22 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen //Gargantua - if (GetBloodEssence(uid) >= FixedPoint2.New(200) && component.CurrentMutation == VampireMutationsType.Gargantua) + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionEntities.TryGetValue("ActionVampireUnnaturalStrength", out entity) && component.CurrentMutation == VampireMutationsType.Gargantua) { var vampire = new Entity(uid, component); UnnaturalStrength(vampire); + + component.UnlockedPowers.Add("ActionVampireUnnaturalStrength", vampire); } - if (GetBloodEssence(uid) >= FixedPoint2.New(300) && component.CurrentMutation == VampireMutationsType.Gargantua) + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireSupernaturalStrength", out entity) && component.CurrentMutation == VampireMutationsType.Gargantua) { var vampire = new Entity(uid, component); SupernaturalStrength(vampire); + + component.UnlockedPowers.Add("ActionVampireSupernaturalStrength", vampire); } //Bestia From 72920146668acd4f1aaf75b579b16ef0397dbcbb Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 13:27:48 +0200 Subject: [PATCH 50/67] vampire objectives --- .../Rules/Components/VampireRuleComponent.cs | 17 +- .../GameTicking/Rules/VampireRuleSystem.cs | 12 +- .../Components/BloodDrainCondition.cs | 9 + .../Vampire/VampireSystem.Objectives.cs | 26 +++ Content.Server/Vampire/VampireSystem.cs | 1 + .../ru-RU/_prototypes/objectives/vampire.ftl | 4 + .../ru-RU/_strings/Vampires/vampires.ftl | 7 +- .../game-presets/preset-vampire.ftl | 2 +- .../Entities/Objects/Misc/heirloom.yml | 23 --- Resources/Prototypes/GameRules/midround.yml | 4 + .../Prototypes/Objectives/objectiveGroups.yml | 14 ++ Resources/Prototypes/Objectives/vampire.yml | 171 ++++++++++++++++++ 12 files changed, 257 insertions(+), 33 deletions(-) create mode 100644 Content.Server/Objectives/Components/BloodDrainCondition.cs create mode 100644 Content.Server/Vampire/VampireSystem.Objectives.cs create mode 100644 Resources/Locale/ru-RU/_prototypes/objectives/vampire.ftl delete mode 100644 Resources/Prototypes/Entities/Objects/Misc/heirloom.yml create mode 100644 Resources/Prototypes/Objectives/vampire.yml diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs index 22fc5928fe8..c6c97c37a58 100644 --- a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -11,11 +11,16 @@ public sealed partial class VampireRuleComponent : Component { public readonly List VampireMinds = new(); -/* - public readonly List> Objectives = new() + + public readonly List> BaseObjectives = new() + { + "VampireKillRandomPersonObjective", + "VampireDrainObjective" + }; + + public readonly List> EscapeObjectives = new() { - "ChangelingSurviveObjective", - "ChangelingStealDNAObjective", - "EscapeIdentityObjective" - }; */ + "VampireSurviveObjective", + "VampireEscapeObjective" + }; } \ No newline at end of file diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index 235317469c4..b4b4d3518c9 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -89,9 +89,17 @@ public bool MakeVampire(EntityUid target, VampireRuleComponent rule) if (HasComp(target)) _vampire.AddStartingAbilities(target); + + Random random = new Random(); - //foreach (var objective in rule.Objectives) - // _mind.TryAddObjective(mindId, mind, objective); + foreach (var objective in rule.BaseObjectives) + _mind.TryAddObjective(mindId, mind, objective); + + if (rule.EscapeObjectives.Count > 0) + { + var randomEscapeObjective = rule.EscapeObjectives[random.Next(rule.EscapeObjectives.Count)]; + _mind.TryAddObjective(mindId, mind, randomEscapeObjective); + } return true; } diff --git a/Content.Server/Objectives/Components/BloodDrainCondition.cs b/Content.Server/Objectives/Components/BloodDrainCondition.cs new file mode 100644 index 00000000000..dc20681a11a --- /dev/null +++ b/Content.Server/Objectives/Components/BloodDrainCondition.cs @@ -0,0 +1,9 @@ +using Content.Server.Vampire; +using Content.Server.Objectives.Systems; + +namespace Content.Server.Objectives.Components; + +[RegisterComponent, Access(typeof(VampireSystem))] +public sealed partial class BloodDrainConditionComponent : Component +{ +} \ No newline at end of file diff --git a/Content.Server/Vampire/VampireSystem.Objectives.cs b/Content.Server/Vampire/VampireSystem.Objectives.cs new file mode 100644 index 00000000000..57b88af2c19 --- /dev/null +++ b/Content.Server/Vampire/VampireSystem.Objectives.cs @@ -0,0 +1,26 @@ +using Content.Server.Objectives.Components; +using Content.Server.Objectives.Systems; +using Content.Shared.Objectives.Components; +using Content.Shared.Vampire.Components; +using Content.Shared.Vampire; + +namespace Content.Server.Vampire; + +public sealed partial class VampireSystem +{ + [Dependency] private readonly NumberObjectiveSystem _number = default!; + + private void InitializeObjectives() + { + + SubscribeLocalEvent(OnBloodDrainGetProgress); + } + + private void OnBloodDrainGetProgress(EntityUid uid, BloodDrainConditionComponent comp, ref ObjectiveGetProgressEvent args) + { + var target = _number.GetTarget(uid); + if (target != 0 && TryComp(uid, out var vampirecomp)) + args.Progress = MathF.Min(vampirecomp.TotalBloodDrank / target, 1f); + else args.Progress = 1f; + } +} \ No newline at end of file diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 72557e5834b..7812eff1bbb 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -90,6 +90,7 @@ public override void Initialize() SubscribeLocalEvent(OnMutationSelected); InitializePowers(); + InitializeObjectives(); } /// diff --git a/Resources/Locale/ru-RU/_prototypes/objectives/vampire.ftl b/Resources/Locale/ru-RU/_prototypes/objectives/vampire.ftl new file mode 100644 index 00000000000..801a1ecf49c --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/objectives/vampire.ftl @@ -0,0 +1,4 @@ +ent-VampireSurviveObjective = Выжить + .desc = Я должен выжить, чего бы этого мне не стоило. +ent-VampireEscapeObjective = Улететь со станции живым и свободным. + .desc = Я должен улететь на эвакуационном шаттле. Свободным. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl index 299171fa484..5b57732eceb 100644 --- a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl +++ b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl @@ -89,4 +89,9 @@ vampire-mutation-bestia-info = Способности: - Форма летучей мыши - - Мышиная форма \ No newline at end of file + - Мышиная форма + +## Objectives + +objective-condition-drain-title = Выпить { $count } крови. +objective-condition-drain-description = Я должен выпить { $count } крови. Это необходимо для моего выживания и дальнейшей эволюции. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl index faab5fe71a7..30d32525484 100644 --- a/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl +++ b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl @@ -1,5 +1,5 @@ vampire-roundend-name = вампир -objective-issuer-vampire = [color=orange]Коллективный разум[/color] +objective-issuer-vampire = [color=red]Жажда крови[/color] roundend-prepend-vampire-drained-named = [color=white]{ $name }[/color] выпил в общей сложности [color=red]{ $number }[/color] крови. roundend-prepend-vampire-absorbed = Кто-то выпил в общей сложности [color=red]{ $number }[/color] крови. vampire-gamemode-title = Вампиры diff --git a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml b/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml deleted file mode 100644 index f2e58637b94..00000000000 --- a/Resources/Prototypes/Entities/Objects/Misc/heirloom.yml +++ /dev/null @@ -1,23 +0,0 @@ -- type: entity - id: HeirloomVampire - name: Family Heirloom - suffix: Vampire - description: A relic of a bygone age, remember your heritage. - parent: BaseItem - components: - - type: Sprite - sprite: Objects/Tools/Toolboxes/toolbox_thief.rsi - state: icon - - type: UserInterface - interfaces: - enum.StoreUiKey.Key: - type: StoreBoundUserInterface - - type: ActivatableUI - key: enum.StoreUiKey.Key - - type: Store - categories: - - VampirePowers - - VampirePassives - currencyWhitelist: - - BloodEssence - - type: VampireHeirloom \ No newline at end of file diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 603ca28ccf1..d9a751be673 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -50,6 +50,10 @@ parent: BaseGameRule id: Vampire components: + - type: AntagRandomObjectives + sets: + - groups: VampireObjectiveGroupSteal + maxDifficulty: 10 - type: VampireRule - type: AntagSelection agentName: vampire-roundend-name diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 4129c329773..0e8dfc7e1d8 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -117,5 +117,19 @@ id: ThiefObjectiveGroupEscape weights: EscapeThiefShuttleObjective: 1 + +# Vampire + +- type: weightedRandom + id: VampireObjectiveGroupSteal + weights: + CMOHyposprayVampireStealObjective: 1 + RDHardsuitVampireStealObjective: 1 + EnergyShotgunVampireStealObjective: 1 + MagbootsVampireStealObjective: 1 + ClipboardVampireStealObjective: 1 + CaptainIDVampireStealObjective: 1 + CaptainJetpackVampireStealObjective: 1 + CaptainGunVampireStealObjective: 1 #Crew, wizard, when you code it... diff --git a/Resources/Prototypes/Objectives/vampire.yml b/Resources/Prototypes/Objectives/vampire.yml new file mode 100644 index 00000000000..184e6b4608e --- /dev/null +++ b/Resources/Prototypes/Objectives/vampire.yml @@ -0,0 +1,171 @@ +# Base + +- type: entity + abstract: true + parent: BaseObjective + id: BaseVampireObjective + components: + - type: Objective + difficulty: 1.5 + issuer: objective-issuer-vampire + - type: RoleRequirement + roles: + mindRoles: + - VampireRole + +- type: entity + abstract: true + parent: [BaseVampireObjective, BaseStealObjective] + id: BaseVampireStealObjective + components: + - type: StealCondition + verifyMapExistence: false + - type: Objective + difficulty: 1.5 + - type: ObjectiveLimit + limit: 2 + +# Steal + +- type: entity + parent: BaseVampireStealObjective + id: CMOHyposprayVampireStealObjective + components: + - type: NotJobRequirement + job: ChiefMedicalOfficer + - type: StealCondition + owner: job-name-cmo + stealGroup: Hypospray + +- type: entity + parent: BaseVampireStealObjective + id: RDHardsuitVampireStealObjective + components: + - type: NotJobRequirement + job: ResearchDirector + - type: StealCondition + owner: job-name-rd + stealGroup: ClothingOuterHardsuitRd + - type: Objective + difficulty: 1 + +- type: entity + parent: BaseVampireStealObjective + id: EnergyShotgunVampireStealObjective + components: + - type: Objective + difficulty: 2 + - type: NotJobRequirement + job: HeadOfSecurity + - type: StealCondition + stealGroup: WeaponEnergyShotgun + owner: job-name-hos + +- type: entity + parent: BaseVampireStealObjective + id: MagbootsVampireStealObjective + components: + - type: NotJobRequirement + job: ChiefEngineer + - type: StealCondition + stealGroup: ClothingShoesBootsMagAdv + owner: job-name-ce + +- type: entity + parent: BaseVampireStealObjective + id: ClipboardVampireStealObjective + components: + - type: NotJobRequirement + job: Quartermaster + - type: StealCondition + stealGroup: BoxFolderQmClipboard + owner: job-name-qm + +- type: entity + abstract: true + parent: BaseVampireStealObjective + id: BaseCaptainVampireObjective + components: + - type: Objective + difficulty: 2.5 + - type: NotJobRequirement + job: Captain + +- type: entity + parent: BaseCaptainVampireObjective + id: CaptainIDVampireStealObjective + components: + - type: StealCondition + stealGroup: CaptainIDCard + +- type: entity + parent: BaseCaptainVampireObjective + id: CaptainJetpackVampireStealObjective + components: + - type: StealCondition + stealGroup: JetpackCaptainFilled + +- type: entity + parent: BaseCaptainVampireObjective + id: CaptainGunVampireStealObjective + components: + - type: StealCondition + stealGroup: WeaponAntiqueLaser + owner: job-name-captain + +# States + +- type: entity + parent: [BaseVampireObjective, BaseSurviveObjective] + id: VampireSurviveObjective + name: Survive + description: I must survive no matter what. + components: + - type: Objective + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: deathsembrace + +- type: entity + parent: [BaseVampireObjective, BaseLivingObjective] + id: VampireEscapeObjective + name: Escape to centcomm alive and unrestrained. + description: I need to escape on the evacuation shuttle. Undercover. + components: + - type: Objective + difficulty: 1.3 + icon: + sprite: Structures/Furniture/chairs.rsi + state: shuttle + - type: EscapeShuttleCondition + +# Kill + +- type: entity + parent: [BaseVampireObjective, BaseKillObjective] + id: VampireKillRandomPersonObjective + description: Do it however you like, just make sure they don't make it to centcomm. + components: + - type: Objective + difficulty: 1.75 + unique: false + - type: TargetObjective + title: objective-condition-kill-person-title + - type: PickRandomPerson + +# Drain + +- type: entity + parent: BaseVampireObjective + id: VampireDrainObjective + components: + - type: Objective + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: fangs_extended + - type: NumberObjective + min: 100 + max: 500 + title: objective-condition-drain-title + description: objective-condition-drain-description + - type: BloodDrainCondition \ No newline at end of file From bf3df63932bd2d788aa17bf4d1f790a40e152d5e Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 13:41:54 +0200 Subject: [PATCH 51/67] new icons --- .../Actions/actions_vampire.rsi/bestia.png | Bin 2272 -> 2779 bytes .../Actions/actions_vampire.rsi/dantalion.png | Bin 0 -> 2863 bytes .../Actions/actions_vampire.rsi/gargantua.png | Bin 0 -> 2714 bytes .../actions_vampire.rsi/hemomancer.png | Bin 2057 -> 2615 bytes .../Actions/actions_vampire.rsi/umbrae.png | Bin 2368 -> 2928 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/dantalion.png create mode 100644 Resources/Textures/Interface/Actions/actions_vampire.rsi/gargantua.png diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/bestia.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/bestia.png index 45e82590c0f310327eeaeb0303925114e290eb2a..6819a99811c5436dd37a20f9dc2186a84b170a05 100644 GIT binary patch delta 2771 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 ZS-*e(ejq{s{{v{YqTR``*HV+Z2qy7PRtNw9 delta 2260 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 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/dantalion.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/dantalion.png new file mode 100644 index 0000000000000000000000000000000000000000..aa34ab26c4d8b2e115baa4a67844633834ffcd07 GIT binary patch literal 2863 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 U007cclK=n!07*qoM6N<$f>4S-LI3~& literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png index 086a9f8b9d96c131c240f20c9af43d1fbcafbb1a..402ced18a0c283f02485e26775ea32d8e0aa8578 100644 GIT binary patch literal 2615 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 diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png index 8c198b76910b6395cce7a8af4513459fc88d172e..15a1240b89d3ba18bdb16a8457ba24fb2ceaa74e 100644 GIT binary patch delta 2921 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 T5Wv3x3Z?& zC2e^DwN0TH@m8sQL`u~@K%qiaAKJH80##78qN)%OT8I#T5`sd~l0+mpPN2lZP3&Fo zuGe12>%E*aGkrMAHm>tn=4D4mJM;bL|NXz4u-4kiqx*RL=_p^+%IEW)eYIKz;Fae$ zFt+DSoXMm60C?t5gJ1vt+Aj>mmFsg5n>KAi95`^mUd{NAe)bdq6Ym^n^6$r)eD@>( zLP!K)0fEJTTCm2VttK!UZ2|%vaQz)$W_<50?7Hh#0DkktPXL&mo#mNBjgB4pzvz;t zkG*$%{faLZEJ!JkLZGB*??Vt4ixuIou?B54feFyoV6mNBZoBP1yhb-eLqi>Rf8)^$ z$lbelcV7R|kq=oYmZ_HJSuD->G^b`JkRtq= z@ZvOrCe5J9w%iu5U^+|~thF5g}qEeCi|0b8~Hn*p6#f0jT?R7VA}( zo6D^DbrwtWoS&Fvp;X3LLoCxx&{nq)0;4V4`?qy2ihFSel35(7$mRx6LQ*YP_;lhd z^TiT~(5SYC!05FaXzCUXzkvlD=}<0z7HI`O25_XJTC5jv9*L9i5-c_rxn<-_ zok&nh;W{phr3&yV7tfvN{KQ!n%k#L>#gPi%_~@$v2$gH`b&F=u0t@B2Iot^sS2;M+ zW$VxuhI@wiVEQCVO6q>SbD1L@x?>5pBXp zM<^`qSZ?RBO0i7T_3#{zZ5v1VL>O;jlCd4FNFh-|G146wC-kTK`O4-!C@Il@ZM`5YK|4hoYw?u3761rbmZBqH6!ye=Y+7v_JV z=uKq!%RgVH=GRC@yO~*@CGN!uOu$egN8N9*)VRzOul|BudXPJ|-cGtJjUyc#^>O@colvGF|xo&WjbS$Jg8A&3M3?*_bH7~P&A$<*JN@qz$ zlZ^IkWP5fSzkB;RtTjZv2-;}0(HO!+X8|STS^&P$xY8x&#&D&Bu@;HIRW1U-bML)C zBWMu!x>)h+RGUlmb!GT$VVbRdn+Z(7e7(YQt47?3lZ+*qF8`al-=G;Z5JH4rwP3D@ zf(`(!!|bGlLP-S}lu{^vDT%ld)^(2%b)wY$29^2(Q71w(Xp!q4v!KxmOoqGicJ%0a8eW6{bd_An1Bgr93wKAfu9c?tfed}3_F*rh@ge2-liMml7>0AW>7Hth0 zgCF?#flpw3JSW0yr`~9Tv@~^-Qmsr=H)#Y7(vcM9m3f-F#k%A$)#efap7KaXQ}{Z- zbzFoHc*@0*&Z-B0UBN&|AC|Bzn-E0IjgoY`NqBLD5aber;Xxs}dH5zO^#%I7vJ`72 zTH0qM8J1aXHR))IS5N&FM>+^0i6{?Gd8_h3hl|gf%0yopqcut@mgX1eigaPEWw}+O zR4bE>_p#Wlw$B(YU7i78Fh0PA>NMA+){*eythB;N8BK40M5bM|wFnD^!Ys;MB@C^# zv~-iMOoHpi$JjEygWiFDHVuv3FLUGQ4P0E9;Zkje>GDN~#oWnF3=i}fnEZoGwa`A=|_%T)2RPKw<>b~jVS zDVjlxT5AOX+;sQtK$vHGvc2^7_g`r;WZ>cb?~%*p7#&;BKyH}6!GU&*4T1K-S|k!j zNCbj^zyvgd7J&}hZMF#GpD>HM(k14^h`Lc+>7ayUY}XBB2Kq^@PXO@a?@g^{AeBl1 zaQxU?96$CJ_dWP+GTA=T* Date: Thu, 31 Oct 2024 13:42:06 +0200 Subject: [PATCH 52/67] balance --- Content.Server/Vampire/VampireSystem.Abilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 3f0c494cb7d..31395747fc2 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -642,7 +642,7 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood return; } - var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume * 2); + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume * 4); //Slurp _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); From 3b8cd68c4ead14703ec07b60089a1de7ac9832dd Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 13:42:14 +0200 Subject: [PATCH 53/67] fix --- Resources/Prototypes/GameRules/midround.yml | 2 +- Resources/Prototypes/Objectives/objectiveGroups.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index d9a751be673..79be64044c3 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -52,7 +52,7 @@ components: - type: AntagRandomObjectives sets: - - groups: VampireObjectiveGroupSteal + - groups: VampireObjectiveGroups maxDifficulty: 10 - type: VampireRule - type: AntagSelection diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 0e8dfc7e1d8..43ae0c2e6c4 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -120,6 +120,11 @@ # Vampire +- type: weightedRandom + id: VampireObjectiveGroups + weights: + VampireObjectiveGroupSteal: 1 + - type: weightedRandom id: VampireObjectiveGroupSteal weights: From 7978338e7f83509ec8b54b7d29c74f517f1f39d5 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:11:00 +0200 Subject: [PATCH 54/67] upd objectives --- .../Rules/Components/VampireRuleComponent.cs | 12 ++++++++++++ .../GameTicking/Rules/VampireRuleSystem.cs | 6 ++++++ Resources/Prototypes/GameRules/midround.yml | 4 ---- .../Prototypes/Objectives/objectiveGroups.yml | 19 ------------------- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs index c6c97c37a58..92263530c7c 100644 --- a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -23,4 +23,16 @@ public sealed partial class VampireRuleComponent : Component "VampireSurviveObjective", "VampireEscapeObjective" }; + + public readonly List> StealObjectives = new() + { + "CMOHyposprayVampireStealObjective" + "RDHardsuitVampireStealObjective" + "EnergyShotgunVampireStealObjective" + "MagbootsVampireStealObjective" + "ClipboardVampireStealObjective" + "CaptainIDVampireStealObjective" + "CaptainJetpackVampireStealObjective" + "CaptainGunVampireStealObjective" + } } \ No newline at end of file diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index b4b4d3518c9..657d0cc375e 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -100,6 +100,12 @@ public bool MakeVampire(EntityUid target, VampireRuleComponent rule) var randomEscapeObjective = rule.EscapeObjectives[random.Next(rule.EscapeObjectives.Count)]; _mind.TryAddObjective(mindId, mind, randomEscapeObjective); } + + if (rule.StealObjectives.Count > 0) + { + var randomEscapeObjective = rule.StealObjectives[random.Next(rule.StealObjectives.Count)]; + _mind.TryAddObjective(mindId, mind, randomEscapeObjective); + } return true; } diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 79be64044c3..603ca28ccf1 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -50,10 +50,6 @@ parent: BaseGameRule id: Vampire components: - - type: AntagRandomObjectives - sets: - - groups: VampireObjectiveGroups - maxDifficulty: 10 - type: VampireRule - type: AntagSelection agentName: vampire-roundend-name diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 43ae0c2e6c4..27cd55f2980 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -118,23 +118,4 @@ weights: EscapeThiefShuttleObjective: 1 -# Vampire - -- type: weightedRandom - id: VampireObjectiveGroups - weights: - VampireObjectiveGroupSteal: 1 - -- type: weightedRandom - id: VampireObjectiveGroupSteal - weights: - CMOHyposprayVampireStealObjective: 1 - RDHardsuitVampireStealObjective: 1 - EnergyShotgunVampireStealObjective: 1 - MagbootsVampireStealObjective: 1 - ClipboardVampireStealObjective: 1 - CaptainIDVampireStealObjective: 1 - CaptainJetpackVampireStealObjective: 1 - CaptainGunVampireStealObjective: 1 - #Crew, wizard, when you code it... From 1228f0e1fd1872869115cd3d7315e99df4444cf8 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:21:26 +0200 Subject: [PATCH 55/67] fix metabolizer type, balance 2 --- Content.Server/Vampire/VampireSystem.Abilities.cs | 5 +++-- Content.Shared/Vampire/VampireComponent.cs | 14 +------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 31395747fc2..871504f680e 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -642,13 +642,14 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood return; } - var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume * 4); + var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume); + var volumeToConsume2 = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, volumeToConsume * 4); //Slurp _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); //Spill an extra 5% on the floor - _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume * 0.05)); + _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume2 * 0.05)); //Thou shall not feed upon the blood of the holy //TODO: Replace with raised event? diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index f4eb614ab19..b8108c20185 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -47,7 +47,7 @@ public sealed partial class VampireComponent : Component [ValidatePrototypeId] public static readonly string MetabolizerVampire = "vampire"; [ValidatePrototypeId] - public static readonly string MetabolizerBloodsucker = "bloodsucker"; + public static readonly string MetabolizerBloodsucker = "Bloodsucker"; public static readonly DamageSpecifier MeleeDamage = new() { @@ -94,11 +94,6 @@ public sealed partial class VampireComponent : Component /// public Dictionary UnlockedPowers = new(); - /// - /// Link to the vampires heirloom - /// - public EntityUid? Heirloom = default!; - /// /// Current available balance, used to sync currency across heirlooms and add essence as we feed /// @@ -170,13 +165,6 @@ public sealed partial class UnholyComponent : Component { } [RegisterComponent] public sealed partial class CoffinComponent : Component { } -[RegisterComponent] -public sealed partial class VampireHeirloomComponent : Component -{ - //Use of the heirloom is limited to this entity - public EntityUid? VampireOwner = default!; -} - [RegisterComponent] public sealed partial class VampireFangsExtendedComponent : Component { } From 287eef4ff727f46980b21bd4c1621ebdc08b7633 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:22:10 +0200 Subject: [PATCH 56/67] fix metabolizer type 2 --- Content.Shared/Vampire/VampireComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index b8108c20185..4b086b61244 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -45,7 +45,7 @@ public sealed partial class VampireComponent : Component Tags = new() { "Pill" } }; [ValidatePrototypeId] - public static readonly string MetabolizerVampire = "vampire"; + public static readonly string MetabolizerVampire = "Vampire"; [ValidatePrototypeId] public static readonly string MetabolizerBloodsucker = "Bloodsucker"; From 5fc243654f9a62c7bdd28e5c33c1683a90cc4e1a Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:24:23 +0200 Subject: [PATCH 57/67] fix syntax --- .../Rules/Components/VampireRuleComponent.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs index 92263530c7c..7fe170f7d58 100644 --- a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -26,13 +26,13 @@ public sealed partial class VampireRuleComponent : Component public readonly List> StealObjectives = new() { - "CMOHyposprayVampireStealObjective" - "RDHardsuitVampireStealObjective" - "EnergyShotgunVampireStealObjective" - "MagbootsVampireStealObjective" - "ClipboardVampireStealObjective" - "CaptainIDVampireStealObjective" - "CaptainJetpackVampireStealObjective" + "CMOHyposprayVampireStealObjective", + "RDHardsuitVampireStealObjective", + "EnergyShotgunVampireStealObjective", + "MagbootsVampireStealObjective", + "ClipboardVampireStealObjective", + "CaptainIDVampireStealObjective", + "CaptainJetpackVampireStealObjective", "CaptainGunVampireStealObjective" } } \ No newline at end of file From d65aaa79f9fdd8e235ba6900cadce62dba0b26db Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:25:59 +0200 Subject: [PATCH 58/67] clear translation --- .../Locale/ru-RU/_strings/Vampires/vampires.ftl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl index 5b57732eceb..ea8bf7c56c3 100644 --- a/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl +++ b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl @@ -32,18 +32,15 @@ store-category-vampirepassives = Пассивные #Powers -vampire-power-blessing = Blessing of Lilith -vampire-power-blessing-description = Swear your soul to Lilith, receive her blessing, and feast upon the bounty around you. - #Passives -vampire-passive-unholystrength = Unholy Strength -vampire-passive-unholystrength-description = Infuse your upper body muscles with essence, granting you claws and increased strength. Effect: 10 Slash per hit +vampire-passive-unholystrength = Нечестивая сила +vampire-passive-unholystrength-description = Наполните мышцы верхней части тела кровью, наделяя вас когтями и повышенной силой. Эффект: 10 порезов за удар -vampire-passive-supernaturalstrength = Supernatural Strength -vampire-passive-supernaturalstrength-description = Increase your upper body muscles strength further, no barrier shall stand in your way. Effect: 15 Slash per hit, able to pry open doors by hand. +vampire-passive-supernaturalstrength = Сверхъестественная сила +vampire-passive-supernaturalstrength-description = Увеличьте силу мышц верхней части тела, и ни одна преграда не встанет на вашем пути. Эффект: 15 порезов за удар, возможность открывать двери руками. -vampire-passive-deathsembrace = Deaths Embrace -vampire-passive-deathsembrace-description = Embrace death and it shall pass you over. Effect: Heal when in a coffin, automatically return to your coffin upon death for 100 blood essence. +vampire-passive-deathsembrace = Объятия смерти +vampire-passive-deathsembrace-description = Примите смерть, и она обойдет вас стороной. Эффект: исцеление в гробу, автоматическое возвращение в гроб после смерти за 100 эссенции крови. #Mutation menu From cc5aa4b59ce66ce6523fe4cf69cb3a7269276b72 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:26:43 +0200 Subject: [PATCH 59/67] xd --- .../GameTicking/Rules/Components/VampireRuleComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs index 7fe170f7d58..3fc2b7c2d06 100644 --- a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -34,5 +34,5 @@ public sealed partial class VampireRuleComponent : Component "CaptainIDVampireStealObjective", "CaptainJetpackVampireStealObjective", "CaptainGunVampireStealObjective" - } + }; } \ No newline at end of file From c723b6095ea8954dfc098ac48a3d072848fdba6b Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:29:14 +0200 Subject: [PATCH 60/67] update --- Content.Server/Vampire/VampireSystem.Abilities.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 871504f680e..8a245920c6e 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -643,13 +643,13 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood } var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume); - var volumeToConsume2 = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, volumeToConsume * 4); + var volumeToDrain = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume * 4); //Slurp _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); //Spill an extra 5% on the floor - _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToConsume2 * 0.05)); + _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToDrain * 0.05)); //Thou shall not feed upon the blood of the holy //TODO: Replace with raised event? From eae5cedf3a1d0632a6c1d6445e08198cbb318d1a Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:54:33 +0200 Subject: [PATCH 61/67] round end info --- .../GameTicking/Rules/VampireRuleSystem.cs | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index 657d0cc375e..5e846ea0ae5 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -131,36 +131,28 @@ private string MakeBriefing(EntityUid ent) return ""; } -/* private void OnTextPrepend(EntityUid uid, VampireRuleComponent comp, ref ObjectivesTextPrependEvent args) + private void OnTextPrepend(EntityUid uid, VampireRuleComponent comp, ref ObjectivesTextPrependEvent args) { - var mostAbsorbedName = string.Empty; - var mostStolenName = string.Empty; - var mostAbsorbed = 0f; - var mostStolen = 0f; + var mostDrainedName = string.Empty; + var mostDrained = 0f; - foreach (var ling in EntityQuery()) + foreach (var vamp in EntityQuery()) { - if (!_mind.TryGetMind(ling.Owner, out var mindId, out var mind)) + if (!_mind.TryGetMind(vamp.Owner, out var mindId, out var mind)) continue; - if (!TryComp(ling.Owner, out var metaData)) + if (!TryComp(vamp.Owner, out var metaData)) continue; - if (ling.TotalAbsorbedEntities > mostAbsorbed) + if (vamp.TotalBloodDrank > mostDrained) { - mostAbsorbed = ling.TotalAbsorbedEntities; - mostAbsorbedName = _objective.GetTitle((mindId, mind), metaData.EntityName); - } - if (ling.TotalStolenDNA > mostStolen) - { - mostStolen = ling.TotalStolenDNA; - mostStolenName = _objective.GetTitle((mindId, mind), metaData.EntityName); + mostDrained = vamp.TotalBloodDrank; + mostDrainedName = _objective.GetTitle((mindId, mind), metaData.EntityName); } } var sb = new StringBuilder(); - sb.AppendLine(Loc.GetString($"roundend-prepend-changeling-absorbed{(!string.IsNullOrWhiteSpace(mostAbsorbedName) ? "-named" : "")}", ("name", mostAbsorbedName), ("number", mostAbsorbed))); - sb.AppendLine(Loc.GetString($"roundend-prepend-changeling-stolen{(!string.IsNullOrWhiteSpace(mostStolenName) ? "-named" : "")}", ("name", mostStolenName), ("number", mostStolen))); + sb.AppendLine(Loc.GetString($"roundend-prepend-vampire-drained{(!string.IsNullOrWhiteSpace(mostDrainedName) ? "-named" : "")}", ("name", mostDrainedName), ("number", mostDrained))); args.Text = sb.ToString(); }*/ From 62597327d6df241efed9d4fb31553b23ad668210 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:54:39 +0200 Subject: [PATCH 62/67] fix --- .../ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl index 30d32525484..fef658be5f4 100644 --- a/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl +++ b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl @@ -1,7 +1,7 @@ vampire-roundend-name = вампир objective-issuer-vampire = [color=red]Жажда крови[/color] roundend-prepend-vampire-drained-named = [color=white]{ $name }[/color] выпил в общей сложности [color=red]{ $number }[/color] крови. -roundend-prepend-vampire-absorbed = Кто-то выпил в общей сложности [color=red]{ $number }[/color] крови. +roundend-prepend-vampire-drained = Кто-то выпил в общей сложности [color=red]{ $number }[/color] крови. vampire-gamemode-title = Вампиры vampire-gamemode-description = Кровожадные вампиры пробрались на станцию чтобы вдоволь напиться крови! vampire-role-greeting = From 4e1b5978d0cba77c376caf15bafe931cf186775d Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:54:50 +0200 Subject: [PATCH 63/67] drain objective fix --- Content.Server/Objectives/Components/BloodDrainCondition.cs | 2 ++ Content.Server/Vampire/VampireSystem.Abilities.cs | 6 +++++- Content.Server/Vampire/VampireSystem.Objectives.cs | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Content.Server/Objectives/Components/BloodDrainCondition.cs b/Content.Server/Objectives/Components/BloodDrainCondition.cs index dc20681a11a..afaaafc3a8e 100644 --- a/Content.Server/Objectives/Components/BloodDrainCondition.cs +++ b/Content.Server/Objectives/Components/BloodDrainCondition.cs @@ -6,4 +6,6 @@ namespace Content.Server.Objectives.Components; [RegisterComponent, Access(typeof(VampireSystem))] public sealed partial class BloodDrainConditionComponent : Component { + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float BloodDranked = 0f; } \ No newline at end of file diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 8a245920c6e..097198698e5 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -643,7 +643,11 @@ private void DrinkDoAfter(Entity entity, ref VampireDrinkBlood } var volumeToConsume = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume); - var volumeToDrain = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume * 4); + var volumeToDrain = (FixedPoint2) Math.Min((float) victimBloodRemaining.Value, args.Volume * 8); + + if (_mind.TryGetMind(entity, out var mindId, out var mind)) + if (_mind.TryGetObjectiveComp(mindId, out var objective, mind)) + objective.BloodDranked += entity.Comp.TotalBloodDrank; //Slurp _audio.PlayPvs(entity.Comp.BloodDrainSound, entity.Owner, AudioParams.Default.WithVolume(-3f)); diff --git a/Content.Server/Vampire/VampireSystem.Objectives.cs b/Content.Server/Vampire/VampireSystem.Objectives.cs index 57b88af2c19..3e94fb6494c 100644 --- a/Content.Server/Vampire/VampireSystem.Objectives.cs +++ b/Content.Server/Vampire/VampireSystem.Objectives.cs @@ -19,8 +19,8 @@ private void InitializeObjectives() private void OnBloodDrainGetProgress(EntityUid uid, BloodDrainConditionComponent comp, ref ObjectiveGetProgressEvent args) { var target = _number.GetTarget(uid); - if (target != 0 && TryComp(uid, out var vampirecomp)) - args.Progress = MathF.Min(vampirecomp.TotalBloodDrank / target, 1f); + if (target != 0) + args.Progress = MathF.Min(comp.BloodDranked / target, 1f); else args.Progress = 1f; } } \ No newline at end of file From 411005384648583a79ab971a8f692a73e8eb449c Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:57:50 +0200 Subject: [PATCH 64/67] fix --- Content.Server/Vampire/VampireSystem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index 7812eff1bbb..fa9df67bece 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Nutrition.EntitySystems; using Content.Server.Polymorph.Systems; using Content.Server.Storage.EntitySystems; +using Content.Server.Mind; using Content.Shared.Actions; using Content.Shared.Body.Systems; using Content.Shared.Buckle; @@ -43,6 +44,7 @@ namespace Content.Server.Vampire; public sealed partial class VampireSystem : EntitySystem { + [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly IAdminLogManager _admin = default!; [Dependency] private readonly FoodSystem _food = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; From 6aeb74122d699aa99b96e1a82aa8e48868e1d7a1 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 14:59:01 +0200 Subject: [PATCH 65/67] fix syntax --- Content.Server/GameTicking/Rules/VampireRuleSystem.cs | 2 +- Content.Server/Vampire/VampireSystem.Abilities.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs index 5e846ea0ae5..a5522848dcc 100644 --- a/Content.Server/GameTicking/Rules/VampireRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -155,5 +155,5 @@ private void OnTextPrepend(EntityUid uid, VampireRuleComponent comp, ref Objecti sb.AppendLine(Loc.GetString($"roundend-prepend-vampire-drained{(!string.IsNullOrWhiteSpace(mostDrainedName) ? "-named" : "")}", ("name", mostDrainedName), ("number", mostDrained))); args.Text = sb.ToString(); - }*/ + } } diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs index 097198698e5..9891eafe6a3 100644 --- a/Content.Server/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -6,6 +6,7 @@ using Content.Server.Storage.Components; using Content.Server.Store.Components; using Content.Shared.Actions; +using Content.Server.Objectives.Components; using Content.Shared.Bed.Sleep; using Content.Shared.Body.Components; using Content.Shared.Chat.Prototypes; From 0a1a9e5d3df2acd31adc0817f0078a33e3c98e88 Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 15:21:35 +0200 Subject: [PATCH 66/67] fix --- Content.Server/Vampire/VampireSystem.cs | 34 ++++++++++++++----- .../game-presets/preset-vampire.ftl | 3 +- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Content.Server/Vampire/VampireSystem.cs b/Content.Server/Vampire/VampireSystem.cs index fa9df67bece..464a6e929f9 100644 --- a/Content.Server/Vampire/VampireSystem.cs +++ b/Content.Server/Vampire/VampireSystem.cs @@ -241,7 +241,10 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (newEntity != null) { _actionEntities["ActionVampireBloodSteal"] = newEntity.Value; - component.UnlockedPowers.Add("BloodSteal", newEntity); + if (!component.UnlockedPowers.ContainsKey("BloodSteal")) + { + component.UnlockedPowers.Add("BloodSteal", newEntity); + } } } else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireBloodSteal", out entity)) @@ -260,7 +263,10 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (newEntity != null) { _actionEntities["ActionVampireScreech"] = newEntity.Value; - component.UnlockedPowers.Add("Screech", newEntity); + if (!component.UnlockedPowers.ContainsKey("Screech")) + { + component.UnlockedPowers.Add("Screech", newEntity); + } } } else if (GetBloodEssence(uid) < FixedPoint2.New(300) && _actionEntities.TryGetValue("ActionVampireScreech", out entity)) @@ -281,7 +287,10 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (newEntity != null) { _actionEntities["ActionVampireGlare"] = newEntity.Value; - component.UnlockedPowers.Add("Glare", newEntity); + if (!component.UnlockedPowers.ContainsKey("Glare")) + { + component.UnlockedPowers.Add("Glare", newEntity); + } } } else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireGlare", out entity)) @@ -300,7 +309,10 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (newEntity != null) { _actionEntities["ActionVampireCloakOfDarkness"] = newEntity.Value; - component.UnlockedPowers.Add("CloakOfDarkness", newEntity); + if (!component.UnlockedPowers.ContainsKey("CloakOfDarkness")) + { + component.UnlockedPowers.Add("CloakOfDarkness", newEntity); + } } } else if (GetBloodEssence(uid) < FixedPoint2.New(300) && _actionEntities.TryGetValue("ActionVampireCloakOfDarkness", out entity)) @@ -321,7 +333,7 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen UnnaturalStrength(vampire); - component.UnlockedPowers.Add("ActionVampireUnnaturalStrength", vampire); + _actionEntities["ActionVampireUnnaturalStrength"] = vampire; } if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireSupernaturalStrength", out entity) && component.CurrentMutation == VampireMutationsType.Gargantua) @@ -330,7 +342,7 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen SupernaturalStrength(vampire); - component.UnlockedPowers.Add("ActionVampireSupernaturalStrength", vampire); + _actionEntities["ActionVampireSupernaturalStrength"] = vampire; } //Bestia @@ -341,7 +353,10 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (newEntity != null) { _actionEntities["ActionVampireBatform"] = newEntity.Value; - component.UnlockedPowers.Add("PolymorphBat", newEntity); + if (!component.UnlockedPowers.ContainsKey("PolymorphBat")) + { + component.UnlockedPowers.Add("PolymorphBat", newEntity); + } } } else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireBatform", out entity)) @@ -360,7 +375,10 @@ private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent componen if (newEntity != null) { _actionEntities["ActionVampireMouseform"] = newEntity.Value; - component.UnlockedPowers.Add("PolymorphMouse", newEntity); + if (!component.UnlockedPowers.ContainsKey("PolymorphMouse")) + { + component.UnlockedPowers.Add("PolymorphMouse", newEntity); + } } } else if (GetBloodEssence(uid) < FixedPoint2.New(300) && _actionEntities.TryGetValue("ActionVampireMouseform", out entity)) diff --git a/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl index fef658be5f4..4df784c9a4a 100644 --- a/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl +++ b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl @@ -8,4 +8,5 @@ vampire-role-greeting = Вы вампир, который пробрался на станцию под видом работника! Ваши задачи указаны в меню персонажа. Пейте кровь и эволюционируйте, чтобы выполнить их! -vampire-role-greeting-short = Вы вампир, который пробрался на станцию под видом работника! \ No newline at end of file +vampire-role-greeting-short = Вы вампир, который пробрался на станцию под видом работника! +roles-antag-vamire-name = Вампир \ No newline at end of file From c24223a26d28b1889e76868687cc705a4478eeca Mon Sep 17 00:00:00 2001 From: Rinary Date: Thu, 31 Oct 2024 15:22:36 +0200 Subject: [PATCH 67/67] clear --- Content.Shared/Vampire/VampireComponent.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs index 4b086b61244..231ea6d2fd9 100644 --- a/Content.Shared/Vampire/VampireComponent.cs +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -20,8 +20,6 @@ public sealed partial class VampireComponent : Component public static readonly string SleepStatusEffectProto = "ForcedSleep"; [ValidatePrototypeId] public static readonly string ScreamEmoteProto = "Scream"; - [ValidatePrototypeId] - public static readonly string HeirloomProto = "HeirloomVampire"; [ValidatePrototypeId] public static readonly string CurrencyProto = "BloodEssence";