From a971f130100dfab3516e340d3d1f15c03f184f3a Mon Sep 17 00:00:00 2001 From: Stellark Date: Sun, 15 Mar 2026 19:33:31 +0300 Subject: [PATCH 1/7] vampire upgrade --- .../Vampire/VampireSystem.Abilities.cs | 15 +- .../Imperial/Vampire/VampireSystem.Ghoul.cs | 9 +- .../Imperial/Vampire/VampireSystem.cs | 127 +++++++++--- .../Vampire/Components/GhoulComponent.cs | 4 +- .../Vampire/Components/VampireComponent.cs | 60 +++++- .../Vampire/Event/VampireActionEvent.cs | 6 - .../Imperial/Vampire/SharedVampireSystem.cs | 41 ++++ .../Locale/ru-RU/Imperial/Vampire/vampire.ftl | 29 +-- .../Prototypes/Imperial/Vampire/alert.yml | 8 + .../Prototypes/Imperial/Vampire/game_rule.yml | 9 +- .../Prototypes/Imperial/Vampire/purposes.yml | 186 ++++++++++++++++-- .../Imperial/Vampire/vampire_abilities.yml | 24 +-- Resources/Prototypes/secret_weights.yml | 1 + 13 files changed, 418 insertions(+), 101 deletions(-) diff --git a/Content.Server/Imperial/Vampire/VampireSystem.Abilities.cs b/Content.Server/Imperial/Vampire/VampireSystem.Abilities.cs index 9c00ed6903a..d16ad9aac7c 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.Abilities.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.Abilities.cs @@ -178,7 +178,8 @@ private void OnStartSleep(VampireSleepEvent args) BreakOnMove = true, BreakOnDamage = true, NeedHand = false, - BlockDuplicate = true + BlockDuplicate = true, + Hidden = true }; _doAfter.TryStartDoAfter(doAfterArgs); @@ -267,14 +268,6 @@ private void OnTurn(VampireTurnEvent args) return; } - if (vamp.GhoulQuantity < args.NecessaryGhoulQuantity) - { - _popup.PopupEntity(Loc.GetString("vampire-popup-ghoul-quantity", ("quantity", args.NecessaryGhoulQuantity - vamp.GhoulQuantity)), - args.Performer, args.Performer, PopupType.Medium); - - return; - } - switch (vamp.SelectedSubgroup) { case VampireAbilityType.Hemomancer: @@ -312,10 +305,8 @@ private void OnTurn(VampireTurnEvent args) args.Handled = true; } - public override void Update(float frameTime) + public void AbilitiesUpdate() { - base.Update(frameTime); - var queryVampBat = EntityQueryEnumerator(); while (queryVampBat.MoveNext(out var uid, out var vamp)) { diff --git a/Content.Server/Imperial/Vampire/VampireSystem.Ghoul.cs b/Content.Server/Imperial/Vampire/VampireSystem.Ghoul.cs index c7f25b0a830..21c21a25492 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.Ghoul.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.Ghoul.cs @@ -11,8 +11,6 @@ using Content.Shared.Interaction; using Content.Shared.Radio.Components; using Content.Shared.Radio; -using Robust.Shared.Player; -using Content.Shared.Mind.Components; namespace Content.Server.Imperial.Vampire; @@ -23,13 +21,8 @@ private void OnGetDrinkingGhoul(EntityUid uid, GhoulComponent comp, GetVerbsEven if (!args.CanAccess || !args.CanInteract || uid == args.Target || !_mobState.IsAlive(args.Target)) return; - // если у цели нет крови/разума, кнопки не добавляем - if (!HasComp(args.Target) || !HasComp(args.Target) - || !HasComp(args.Target)) - return; - // верб для питья крови - if (!HasComp(args.Target) && !HasComp(args.Target)) + if (!HasComp(args.Target) && !HasComp(args.Target) && HasComp(args.Target)) { var verbDrinkBloodGhoul = new InnateVerb { diff --git a/Content.Server/Imperial/Vampire/VampireSystem.cs b/Content.Server/Imperial/Vampire/VampireSystem.cs index df4f10c1e0b..f9d793c094e 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.cs @@ -26,6 +26,8 @@ using Content.Shared.Mindshield.Components; using Content.Shared.Mind.Components; using Content.Shared.Mobs; +using Content.Server.Bible.Components; +using Content.Shared.Alert; namespace Content.Server.Imperial.Vampire; @@ -48,6 +50,7 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly AlertsSystem _alert = default!; private void VampireInitialize() { @@ -76,14 +79,10 @@ private void OnGetVerbsCombined(EntityUid uid, VampireComponent vamp, GetVerbsEv || !_mobState.IsAlive(args.Target)) return; - // если у цели нет крови/разума, кнопки не добавляем - if (!HasComp(args.Target) || !HasComp(args.Target) - || !HasComp(args.Target)) - return; - // верб для превращения цели в упыря - if (!HasComp(args.Target) && !HasComp(args.Target) - && !HasComp(args.Target) && !_statusEffects.HasStatusEffect(uid, vamp.CooldownStatusEffectAppealGhouls)) + if (!HasComp(args.Target) && !HasComp(args.Target) && HasComp(args.Target) + && !HasComp(args.Target) && !_statusEffects.HasStatusEffect(uid, vamp.CooldownStatusEffectAppealGhouls) + && HasComp(args.Target)) { var verbConvert = new InnateVerb { @@ -97,7 +96,7 @@ private void OnGetVerbsCombined(EntityUid uid, VampireComponent vamp, GetVerbsEv } // верб для питья крови - if (!HasComp(args.Target) && !HasComp(args.Target)) + if (!HasComp(args.Target) && !HasComp(args.Target) && HasComp(args.Target)) { var verbDrinkBlood = new InnateVerb { @@ -131,6 +130,12 @@ private void StartConversion(EntityUid vampire, EntityUid target) if (!TryComp(vampire, out var vamp)) return; + if (vamp.GhoulQuantity == vamp.MaxNumberGhouls) + { + _popup.PopupEntity(Loc.GetString("vampire-popup-max-number-ghouls"), vampire, vampire, PopupType.Medium); + return; + } + _popup.PopupEntity(Loc.GetString("vampire-verb-envelope-vampire-transform", ("target", MetaData(target).EntityName)), vampire, vampire, PopupType.Medium); @@ -204,29 +209,48 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) // вычисляем текущее количество крови float currentBlood = vamp != null ? vamp.CritThreshold - vamp.BloodDamage : ghoul!.CritThreshold - ghoul.BloodDamage; - if (vamp != null && currentBlood >= 100) + if (currentBlood >= 100 || !HasComp(target) || !HasComp(target)) { - // мы просто засчитываем эту кровь в TotalDrunk, но BloodDamage не понижаем - vamp.TotalDrunk += amount; - _audio.PlayPvs(vamp.DrinkSound, drinker); - - damage.DamageDict["Bloodloss"] = FixedPoint2.New(amount * 2); - _damage.TryChangeDamage(target, damage); - - var eui = new VampireRequestedEui(drinker, EntityManager, _actions, _vampireSystem, _prototypeManager); - eui.GrantAbilities(drinker, vamp.SelectedSubgroup); - - // после того, как вампир выпивает кровь его глаза становятся красными - if (TryComp(drinker, out var humanoidAppearance)) + if (vamp != null) { - humanoidAppearance.EyeColor = Color.Red; - Dirty(drinker, humanoidAppearance); + // мы просто засчитываем эту кровь в TotalDrunk, но BloodDamage не понижаем + if (currentBlood >= 100 && HasComp(target) && HasComp(target)) + { + vamp.TotalDrunk += amount; + var eui = new VampireRequestedEui(drinker, EntityManager, _actions, _vampireSystem, _prototypeManager); + eui.GrantAbilities(drinker, vamp.SelectedSubgroup); + } + // мы просто понижаем BloodDamage, но в TotalDrunk не засчитываем + else if (!HasComp(target) || !HasComp(target)) + { + if (currentBlood >= 100) + { + _popup.PopupEntity(Loc.GetString("vampire-popup-target-no-mind-full"), drinker, drinker, PopupType.Medium); + return; + } + + vamp.BloodDamage = Math.Max(vamp.BloodDamage - amount / 2, 0); + _vampireSystem.SetBloodAlert(drinker, vamp); + _popup.PopupEntity(Loc.GetString("vampire-popup-target-no-mind"), drinker, drinker, PopupType.Medium); + } + + _audio.PlayPvs(vamp.DrinkSound, drinker); + + damage.DamageDict["Bloodloss"] = FixedPoint2.New(amount); + _damage.TryChangeDamage(target, damage); + + // после того, как вампир выпивает кровь его глаза становятся красными + if (TryComp(drinker, out var humanoidAppearance)) + { + humanoidAppearance.EyeColor = Color.Red; + Dirty(drinker, humanoidAppearance); + } + + if (_mobState.IsAlive(target)) + StartDrinking(drinker, target); + + return; } - - if (_mobState.IsAlive(target)) - StartDrinking(drinker, target); - - return; } // увеличиваем количество крови @@ -239,7 +263,17 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) } else if (ghoul != null) { - ghoul.BloodDamage = Math.Max(ghoul.BloodDamage - amount, 0f); + if (ghoul.BloodDamage - amount < 0) + { + _popup.PopupEntity(Loc.GetString("vampire-drinking-full-blood"), drinker, drinker, PopupType.Medium); + return; + } + + if (!HasComp(target) || !HasComp(target)) + ghoul.BloodDamage = Math.Max(ghoul.BloodDamage - amount / 2, 0); + else + ghoul.BloodDamage = Math.Max(ghoul.BloodDamage - amount, 0f); + _vampireSystem.SetGhoulBloodAlert(drinker, ghoul); _audio.PlayPvs(ghoul.DrinkSound, drinker); } @@ -316,4 +350,39 @@ private void OnSelectingSubgroup(VampireSelectingSubgroupEvent args) var eui = new VampireRequestedEui(args.Performer, EntityManager, _actions, _vampireSystem, _prototypeManager); _eui.OpenEui(eui, actor.PlayerSession); } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + BaseUpdate(frameTime); + AbilitiesUpdate(); + } + + public void BaseUpdate(float frameTime) + { + var querySearch = EntityQueryEnumerator(); + while (querySearch.MoveNext(out var uid, out var comp)) + { + comp.UpdateDelay += frameTime; + var priests = _lookup.GetEntitiesInRange(Transform(uid).Coordinates, 7).FirstOrNull(); + + if (priests == null || _mobState.IsDead(priests.Value)) + { + _alert.ClearAlert(uid, comp.AdjacentChaplainAlert); + comp.UpdateDelay = 0f; + continue; + } + + if (comp.UpdateDelay < 1) + continue; + + _damage.TryChangeDamage(uid, comp.DivineDamage); + _audio.PlayPvs(comp.DivineDamageSound, uid); + _popup.PopupEntity(Loc.GetString("vampire-popup-chaplain-closely"), uid, uid, PopupType.Medium); + _alert.ShowAlert(uid, comp.AdjacentChaplainAlert); + + comp.UpdateDelay = 0; + } + } } diff --git a/Content.Shared/Imperial/Vampire/Components/GhoulComponent.cs b/Content.Shared/Imperial/Vampire/Components/GhoulComponent.cs index 1a96a3bfce5..7d6de3cb6ad 100644 --- a/Content.Shared/Imperial/Vampire/Components/GhoulComponent.cs +++ b/Content.Shared/Imperial/Vampire/Components/GhoulComponent.cs @@ -19,7 +19,7 @@ public sealed partial class GhoulComponent : Component /// Интервал между тиками потери крови /// [DataField] - public TimeSpan BloodDecayInterval = TimeSpan.FromSeconds(30); + public TimeSpan BloodDecayInterval = TimeSpan.FromSeconds(60); /// /// количество урона за каждый тик @@ -43,7 +43,7 @@ public sealed partial class GhoulComponent : Component /// количество выпитой крови за 1 тик /// [DataField("bloodPerTick")] - public float BloodPerTick = 1; + public float BloodPerTick = 3; /// /// сколько занимает излечение упыря diff --git a/Content.Shared/Imperial/Vampire/Components/VampireComponent.cs b/Content.Shared/Imperial/Vampire/Components/VampireComponent.cs index 11a8bd55839..c7be591d377 100644 --- a/Content.Shared/Imperial/Vampire/Components/VampireComponent.cs +++ b/Content.Shared/Imperial/Vampire/Components/VampireComponent.cs @@ -1,4 +1,6 @@ using Content.Shared.Alert; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; using Content.Shared.StatusIcon; using Robust.Shared.Audio; using Robust.Shared.GameStates; @@ -91,7 +93,7 @@ public sealed partial class VampireComponent : Component /// количество выпитой крови за 1 тик /// [DataField("bloodPerTick")] - public float BloodPerTick = 1; + public float BloodPerTick = 3; /// /// кд между призывами катаны @@ -186,6 +188,32 @@ public sealed partial class VampireComponent : Component [DataField("bloodLossDisguiseIsActive")] public float BloodLossDisguiseIsActive = 1; + /// + /// время следующего тика потери крови + /// + public TimeSpan NextBloodDecay = TimeSpan.Zero; + + /// + /// Интервал между тиками потери крови + /// + [DataField] + public TimeSpan BloodDecayInterval = TimeSpan.FromSeconds(45); + + /// + /// количество урона за каждый тик + /// + [DataField] + public float BloodDecayAmount = 2f; + + [DataField] + public EntProtoId GhoulPuddleID = "VampirePuddle"; + + /// + /// длительность тряски при критическом состоянии + /// + [DataField] + public TimeSpan ShakingTime = TimeSpan.FromSeconds(5); + [DataField] public string VampirePuddleID = "VampirePuddle"; @@ -312,9 +340,37 @@ public sealed partial class VampireComponent : Component /// длительность cooldown на обращение в упырей /// [DataField("cooldownTimeAppealGhouls")] - public TimeSpan CooldownTimeAppealGhouls = TimeSpan.FromMinutes(1); + public TimeSpan CooldownTimeAppealGhouls = TimeSpan.FromMinutes(2.5f); [DataField] public string CooldownStatusEffectAppealGhouls = "AppealGhoulsCooldown"; + + [DataField] + public DamageSpecifier DivineDamage = new DamageSpecifier + { + DamageDict = new Dictionary + { + ["Heat"] = 2 + } + }; + + [DataField] + public SoundSpecifier DivineDamageSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg") + { + Params = AudioParams.Default.WithVolume(3) + }; + + [AutoNetworkedField] + public float UpdateDelay; + + [DataField] + public ProtoId AdjacentChaplainAlert = "VampireAdjacentChaplainAlert"; + + /// + /// максимальное количество упырей, которое может иметь вампир + /// + [DataField] + public int MaxNumberGhouls = 5; + } } diff --git a/Content.Shared/Imperial/Vampire/Event/VampireActionEvent.cs b/Content.Shared/Imperial/Vampire/Event/VampireActionEvent.cs index 96a3162869c..d16b5fce80a 100644 --- a/Content.Shared/Imperial/Vampire/Event/VampireActionEvent.cs +++ b/Content.Shared/Imperial/Vampire/Event/VampireActionEvent.cs @@ -343,12 +343,6 @@ public sealed partial class VampireTurnEvent : InstantActionEvent { [DataField("costBlood")] public float CostBlood = 30; - - /// - /// необходимо иметь упырей для обращения - /// - [DataField("necessaryGhoulQuantity")] - public int NecessaryGhoulQuantity = 25; } public sealed partial class VampireSwordEvent : InstantActionEvent diff --git a/Content.Shared/Imperial/Vampire/SharedVampireSystem.cs b/Content.Shared/Imperial/Vampire/SharedVampireSystem.cs index 3eece42440e..43132d84135 100644 --- a/Content.Shared/Imperial/Vampire/SharedVampireSystem.cs +++ b/Content.Shared/Imperial/Vampire/SharedVampireSystem.cs @@ -435,9 +435,50 @@ private void BaseUpdate() } } + var vampirelQuery = EntityQueryEnumerator(); + while (vampirelQuery.MoveNext(out var uid, out var comp)) + { + if (_mobStateSystem.IsDead(uid)) + continue; + + if (comp.NextBloodDecay == TimeSpan.Zero) + { + comp.NextBloodDecay = _gameTiming.CurTime + comp.BloodDecayInterval; + Dirty(uid, comp); + } + + if (_gameTiming.CurTime >= comp.NextBloodDecay) + { + // наносим урон каждые BloodDecayInterval секунд + DealBloodDamage(uid, comp.BloodDecayAmount, comp); + comp.NextBloodDecay = _gameTiming.CurTime + comp.BloodDecayInterval; + Dirty(uid, comp); + + // если урон больше количества крови, то применяем дебафы + if (comp.BloodDamage >= comp.CritThreshold) + { + if (TryComp(uid, out var stamina)) + { + var dmg = new DamageSpecifier(); + dmg.DamageDict["Bloodloss"] = FixedPoint2.New(30); + + _damage.TryChangeDamage(uid, dmg); + SpawnBloodPuddle(uid, comp.GhoulPuddleID); + _stamina.TakeStaminaDamage(uid, 70f, stamina); + + if (_net.IsServer) + _jitterSystem.DoJitter(uid, comp.ShakingTime, refresh: false, amplitude: 15f, frequency: 4f); + } + } + } + } + var ghoulQuery = EntityQueryEnumerator(); while (ghoulQuery.MoveNext(out var uid, out var comp)) { + if (_mobStateSystem.IsDead(uid)) + continue; + // заставляем упырей пить кровь if (comp.NextBloodDecay == TimeSpan.Zero) { diff --git a/Resources/Locale/ru-RU/Imperial/Vampire/vampire.ftl b/Resources/Locale/ru-RU/Imperial/Vampire/vampire.ftl index c15b4468db0..dd63d3271d6 100644 --- a/Resources/Locale/ru-RU/Imperial/Vampire/vampire.ftl +++ b/Resources/Locale/ru-RU/Imperial/Vampire/vampire.ftl @@ -10,39 +10,39 @@ vampire-subgroup-gargantua-button = Выбрать Gargantua vampire-selecting-window-subgroupHemomancer-text = {"["}bold]Кровавые щупальца[/bold]: Разблокируется на 100 крови. Из указанной точки вырываются щупальца, наносящие 15 урона и сваливающие жертву на землю. - {"["}bold]Воздушный побег[/bold]: Разблокируется на 200 крови. Призывает рой атакующих летучих мышей и превращает вампира в летучую мышь на 10 секунд. + {"["}bold]Воздушный побег[/bold]: Разблокируется на 100 крови. Призывает рой атакующих летучих мышей и превращает вампира в летучую мышь на 10 секунд. - {"["}bold]Кровавое перевоплощение[/bold]: Разблокируется на 300 крови. На 4 секунды превращает вас в кровь, позволяя проходить сквозь препятствия. + {"["}bold]Кровавое перевоплощение[/bold]: Разблокируется на 200 крови. На 4 секунды превращает вас в кровь, позволяя проходить сквозь препятствия. - {"["}bold]Гнев Носферату[/bold]: Разблокируется на 400 крови. На 25 секунд: в 2 раза больше урона, +50% скорости атаки, +50% скорости передвижения. + {"["}bold]Гнев Носферату[/bold]: Разблокируется на 350 крови. На 25 секунд: в 2 раза больше урона, +50% скорости атаки, +50% скорости передвижения. vampire-selecting-window-subgroupUmbrae-text = {"["}bold]Свобода[/bold]: Разблокируется на 100 крови. Разрывает наручники и увеличивает скорость в 1.5 раза на 10 секунд. - {"["}bold]Переключить режим невидимости[/bold]: Разблокируется на 200 крови. Делает вампира невидимым за счёт 2 ед. крови в секунду. Любая атака/урон прерывает эффект. + {"["}bold]Переключить режим невидимости[/bold]: Разблокируется на 100 крови. Делает вампира невидимым за счёт 2 ед. крови в секунду. Любая атака/урон прерывает эффект. - {"["}bold]Кровавый якорь[/bold]: Разблокируется на 300 крови. Создает якорь в точке активации. При повторном нажатии телепортирует к себе. Якорь исчезает через 2 минуты или после использования. + {"["}bold]Кровавый якорь[/bold]: Разблокируется на 200 крови. Создает якорь в точке активации. При повторном нажатии телепортирует к себе. Якорь исчезает через 2 минуты или после использования. - {"["}bold]Теневой капкан[/bold]: Разблокируется на 400 крови. Устанавливает полу-невидимую ловушку. При срабатывании ослепляет жертву и наносит 20 урона. + {"["}bold]Теневой капкан[/bold]: Разблокируется на 200 крови. Устанавливает полу-невидимую ловушку. При срабатывании ослепляет жертву и наносит 20 урона. - {"["}bold]Клон[/bold]: Разблокируется на 450 крови. Создаёт вашу копию, отвлекающую экипаж, пока вы в невидимости покидаете место боя. + {"["}bold]Клон[/bold]: Разблокируется на 350 крови. Создаёт вашу копию, отвлекающую экипаж, пока вы в невидимости покидаете место боя. vampire-selecting-window-subgroupGargantua-text = {"["}bold]Прилив крови[/bold]: Разблокируется на 100 крови. Увеличивает скорость передвижения в 2.5 раза на 10 секунд. {"["}bold]Вампирский скачок[/bold]: Разблокируется на 200 крови. Телепортирует на короткую дистанцию, оставляя облако дыма на месте отправления. - {"["}bold]Сверк[/bold]: Разблокируется на 300 крови. Ослепляющая вспышка в радиусе 3 тайлов, оглушающая всех жертв и полностью истощающая их выносливость. + {"["}bold]Сверк[/bold]: Разблокируется на 200 крови. Ослепляющая вспышка в радиусе 3 тайлов, оглушающая всех жертв и полностью истощающая их выносливость. - {"["}bold]Гнев Носферату[/bold]: Разблокируется на 400 крови. На 25 секунд: в 2 раза больше урона, +50% скорости атаки, +50% скорости передвижения. + {"["}bold]Гнев Носферату[/bold]: Разблокируется на 300 крови. На 25 секунд: в 2 раза больше урона, +50% скорости атаки, +50% скорости передвижения. - {"["}bold]Рывок[/bold]: Разблокируется на 450 крови. Вы кидаетесь в сторону, куда нажали. Если вы попадёте в человека, вы оглушите его. Если попадёте в предмет, то он получит 200 ед урона. + {"["}bold]Рывок[/bold]: Разблокируется на 350 крови. Вы кидаетесь в сторону, куда нажали. Если вы попадёте в человека, вы оглушите его. Если попадёте в предмет, то он получит 200 ед урона. vampire-selecting-window-subgroupHemomancer-turn = [bold]Обращение[/bold]: Разблокируется на 450 крови. Катана становится вечной: она больше не имеет ограничения по времени и не исчезает после использования. -vampire-selecting-window-subgroupUmbrae-turn = [bold]Обращение[/bold]: Разблокируется на 500 крови. Способность "Переключить режим невидимости" больше не расходует кровь при активации и поддержании. +vampire-selecting-window-subgroupUmbrae-turn = [bold]Обращение[/bold]: Разблокируется на 400 крови. Способность "Переключить режим невидимости" больше не расходует кровь при активации и поддержании. -vampire-selecting-window-subgroupGargantua-turn = [bold]Обращение[/bold]: Разблокируется на 500 крови. Гнев Носферату активируется без затрат крови. Кулдаун между использованиями: 10 секунд. +vampire-selecting-window-subgroupGargantua-turn = [bold]Обращение[/bold]: Разблокируется на 400 крови. Гнев Носферату активируется без затрат крови. Кулдаун между использованиями: 10 секунд. vampire-popup-not-enough-blood = Вам не хватает крови! @@ -68,6 +68,9 @@ vampire-popup-vampire-turned = Вы не можете излечить упыр vampire-popup-ghoul-quantity = Вам необходимо обратить еще { $quantity } упырей vampire-popup-warning-vampire-turned = Вы уже обращены! vampire-popup-anchor-destroyed = Кровавый якорь был разрушен! +vampire-popup-target-no-mind = Жертва без разума - её кровь утоляет жажду, но не даёт силы +vampire-popup-target-no-mind-full = Вы уже сыты, а для получения сил жертва должна иметь разум! +vampire-popup-max-number-ghouls = Максимальное количество рабов уже достигнуто vampire-push-markup-eyes = [color=red]{CAPITALIZE(POSS-PRONOUN($user))} глаза наполнены кровью.[/color] @@ -85,3 +88,5 @@ vampire-verb-envelope-vampire-complete = {$target} был успешно обр vampire-dead-text = Со смертью вампира с вас снимаются все проклятия. vampire-dead-button = Хорошо vampire-dead-title = Смерть вампира + +vampire-popup-chaplain-closely = Присутствие святого причиняет вам боль! diff --git a/Resources/Prototypes/Imperial/Vampire/alert.yml b/Resources/Prototypes/Imperial/Vampire/alert.yml index 0fbcb227845..e2fd848b28a 100644 --- a/Resources/Prototypes/Imperial/Vampire/alert.yml +++ b/Resources/Prototypes/Imperial/Vampire/alert.yml @@ -84,3 +84,11 @@ state: brain name: Задержка обращения description: Вы недавно пытались обратить человека в упыря. Функция обращения временно недоступна. + +- type: alert + id: VampireAdjacentChaplainAlert + icons: + - sprite: Imperial/Crook/Chaplain/wood_cross.rsi + state: icon + name: Священная сила + description: Рядом находится священник. Держитесь подальше! diff --git a/Resources/Prototypes/Imperial/Vampire/game_rule.yml b/Resources/Prototypes/Imperial/Vampire/game_rule.yml index 37a25d4e726..cf805f6e9d0 100644 --- a/Resources/Prototypes/Imperial/Vampire/game_rule.yml +++ b/Resources/Prototypes/Imperial/Vampire/game_rule.yml @@ -3,10 +3,15 @@ id: Vampire noSpawn: true components: + - type: AntagRandomObjectives + sets: + - groups: VampireObjectiveGroups + maxDifficulty: 1 - type: AntagObjectives objectives: - VampireDrinkBloodObjective - - VampireConvertedGhoulsObjective + - VampireKillRandomPersonObjective + # - VampireConvertedGhoulsObjective - VampireEscapeShuttleObjective - type: GameRule - type: VampireRole @@ -19,10 +24,10 @@ max: 3 lateJoinAdditional: true allowNonHumans: false - multiAntagSetting: NotExclusive blacklist: components: - AntagImmune + - BibleUser briefing: text: vampire-role-greeting-human color: "#8B0000" diff --git a/Resources/Prototypes/Imperial/Vampire/purposes.yml b/Resources/Prototypes/Imperial/Vampire/purposes.yml index e1f656e2589..3551033c185 100644 --- a/Resources/Prototypes/Imperial/Vampire/purposes.yml +++ b/Resources/Prototypes/Imperial/Vampire/purposes.yml @@ -10,6 +10,25 @@ roles: - VampireRole +- type: weightedRandom + id: VampireObjectiveGroups + weights: + VampireObjectiveGroupSteal: 1 + +- type: weightedRandom + id: VampireObjectiveGroupSteal + weights: + VampireBibleStealObjective: 1 + VampireWardenHatStealObjective: 1 + VampireForensicScannerStealObjective: 1 + VampireCMOHyposprayStealObjective: 1 + VampireCorgiMeatStealObjective: 1 + VampireFireAxeStealObjective: 1 + VampireCaptainSwordStealObjective: 0.5 + VampireHandTeleporterStealObjective: 0.5 + VampireEnergyMagnumStealObjective: 0.5 + VampireNukeDiskStealObjective: 0.3 + - type: entity parent: [BaseVampireObjective, BaseKillObjective] id: VampireKillRandomPersonObjective @@ -38,26 +57,26 @@ sprite: Imperial/Stellark/Vampire/purposes.rsi state: drink_blood - type: NumberObjective - min: 350 - max: 400 + min: 250 + max: 300 title: objective-condition-blood-title description: objective-condition-blood-description - type: VampireDrinkBloodPurposes -- type: entity - parent: BaseVampireObjective - id: VampireConvertedGhoulsObjective - components: - - type: Objective - icon: - sprite: /Textures/Mobs/Species/Human/organs.rsi - state: brain - - type: NumberObjective - min: 15 - max: 20 - title: objective-condition-converted-ghouls-title - description: objective-condition-converted-ghouls-description - - type: VampireConvertedGhoulsPurposes +# - type: entity +# parent: BaseVampireObjective +# id: VampireConvertedGhoulsObjective +# components: +# - type: Objective +# icon: +# sprite: /Textures/Mobs/Species/Human/organs.rsi +# state: brain +# - type: NumberObjective +# min: 15 +# max: 20 +# title: objective-condition-converted-ghouls-title +# description: objective-condition-converted-ghouls-description +# - type: VampireConvertedGhoulsPurposes - type: entity parent: [BaseVampireObjective, BaseLivingObjective] @@ -71,3 +90,138 @@ state: shuttle - type: EscapeShuttleCondition +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireBibleStealObjective + components: + - type: NotJobRequirement + jobs: [ Chaplain ] + - type: StealCondition + stealGroup: Bible + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 0.4 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireCaptainSwordStealObjective + components: + - type: NotJobRequirement + jobs: [ Captain ] + - type: StealCondition + stealGroup: CaptainSword + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 1.5 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireWardenHatStealObjective + components: + - type: StealCondition + stealGroup: ClothingHeadHatWarden + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 1.2 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireFireAxeStealObjective + components: + - type: NotJobRequirement + jobs: [ AtmosphericTechnician ] + - type: StealCondition + stealGroup: FireAxe + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 0.8 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireForensicScannerStealObjective + components: + - type: NotJobRequirement + jobs: [ Detective ] + - type: StealCondition + stealGroup: ForensicScanner + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 1.0 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireCMOHyposprayStealObjective + components: + - type: NotJobRequirement + jobs: [ ChiefMedicalOfficer ] + - type: StealCondition + stealGroup: Hypospray + owner: job-name-cmo + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 1.5 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireHandTeleporterStealObjective + components: + - type: NotJobRequirement + jobs: [ ResearchDirector ] + - type: StealCondition + stealGroup: HandTeleporter + owner: job-name-rd + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 2.0 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireEnergyMagnumStealObjective + components: + - type: NotJobRequirement + jobs: [ HeadOfSecurity ] + - type: StealCondition + stealGroup: WeaponEnergyMagnum + owner: job-name-hos + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 3.0 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireCorgiMeatStealObjective + components: + - type: NotJobRequirement + jobs: [ HeadOfPersonnel ] + - type: ObjectiveLimit + limit: 3 + - type: StealCondition + stealGroup: FoodMeatCorgi + owner: objective-condition-steal-Ian + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 2.5 + +- type: entity + parent: [BaseVampireObjective, BaseStealObjective] + id: VampireNukeDiskStealObjective + components: + - type: NotCommandRequirement + - type: NotJobRequirement + jobs: [ Captain ] + - type: StealCondition + stealGroup: NukeDisk + owner: objective-condition-steal-station + verifyMapExistence: false + checkStealAreas: true + - type: Objective + difficulty: 4.0 diff --git a/Resources/Prototypes/Imperial/Vampire/vampire_abilities.yml b/Resources/Prototypes/Imperial/Vampire/vampire_abilities.yml index 18f3ac48b14..e6444dac892 100644 --- a/Resources/Prototypes/Imperial/Vampire/vampire_abilities.yml +++ b/Resources/Prototypes/Imperial/Vampire/vampire_abilities.yml @@ -17,9 +17,9 @@ - VampireTurnAction thresholds: 0: 100 - 1: 200 - 2: 300 - 3: 400 + 1: 100 + 2: 200 + 3: 350 4: 450 upgrades: - VampireSwordPlusAction @@ -36,11 +36,11 @@ - VampireTurnAction thresholds: 0: 100 - 1: 200 - 2: 300 - 3: 400 - 4: 450 - 5: 500 + 1: 100 + 2: 200 + 3: 200 + 4: 350 + 5: 400 upgrades: - VampireInvisiblePlusAction @@ -57,9 +57,9 @@ thresholds: 0: 100 1: 200 - 2: 300 - 3: 400 - 4: 450 - 5: 500 + 2: 200 + 3: 300 + 4: 350 + 5: 400 upgrades: - VampireNosferatyPlusAction diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index 7a409a89198..07b7f9bc275 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -10,3 +10,4 @@ KesslerSyndrome: 0.05 Revolutionary: 0.15 Wizard: 0.05 # Why not, should probably be lower + Vampire: 0.15 # imperial space: add a new vampire antagonist. start From ee3d766ce51f122ebef36405e42e3cf033492935 Mon Sep 17 00:00:00 2001 From: Stellark Date: Sun, 15 Mar 2026 20:56:33 +0300 Subject: [PATCH 2/7] fix --- Content.Server/Imperial/Vampire/VampireSystem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Content.Server/Imperial/Vampire/VampireSystem.cs b/Content.Server/Imperial/Vampire/VampireSystem.cs index f9d793c094e..64cc66e582a 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.cs @@ -217,6 +217,7 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) if (currentBlood >= 100 && HasComp(target) && HasComp(target)) { vamp.TotalDrunk += amount; + _vampireSystem.SetBloodCounterAlert(drinker, vamp); var eui = new VampireRequestedEui(drinker, EntityManager, _actions, _vampireSystem, _prototypeManager); eui.GrantAbilities(drinker, vamp.SelectedSubgroup); } @@ -257,6 +258,7 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) if (vamp != null) { vamp.BloodDamage = Math.Max(vamp.BloodDamage - amount, 0f); + _vampireSystem.SetBloodCounterAlert(drinker, vamp); _vampireSystem.SetBloodAlert(drinker, vamp); vamp.TotalDrunk += amount; _audio.PlayPvs(vamp.DrinkSound, drinker); From 1ed0b6eb3ca96dc2bd01f83de5538c8d0dc99e5d Mon Sep 17 00:00:00 2001 From: Stellark Date: Mon, 16 Mar 2026 20:26:55 +0300 Subject: [PATCH 3/7] fix --- Content.Server/Imperial/Vampire/VampireSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Server/Imperial/Vampire/VampireSystem.cs b/Content.Server/Imperial/Vampire/VampireSystem.cs index 31254d6d429..d334f28c5e6 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.cs @@ -27,6 +27,7 @@ using Content.Shared.Mobs; using Content.Server.Bible.Components; using Content.Shared.Alert; +using Content.Shared.Humanoid; namespace Content.Server.Imperial.Vampire; From f50b6209d3c0a8d3d485b0f1db164f9a481a565e Mon Sep 17 00:00:00 2001 From: Stellark Date: Mon, 16 Mar 2026 20:38:40 +0300 Subject: [PATCH 4/7] fix --- Content.Server/Imperial/Vampire/VampireSystem.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Content.Server/Imperial/Vampire/VampireSystem.cs b/Content.Server/Imperial/Vampire/VampireSystem.cs index d334f28c5e6..18e9d291d6f 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.cs @@ -281,7 +281,11 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) } // после того, как вампир выпивает кровь его глаза становятся красными - TrySetEntityEyeColor(drinker, Color.Red); + if (TryComp(drinker, out var humanoidAppearance)) + { + humanoidAppearance.EyeColor = Color.Red; + Dirty(drinker, humanoidAppearance); + } // наносим жертве урон от кровопотери damage.DamageDict["Bloodloss"] = FixedPoint2.New(amount * 2); From b588a39613e4a37cef584904943cddffdea8eab0 Mon Sep 17 00:00:00 2001 From: Stellark Date: Mon, 16 Mar 2026 20:44:55 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D1=83=D0=B6=D0=B0=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Server/Imperial/Vampire/VampireSystem.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Content.Server/Imperial/Vampire/VampireSystem.cs b/Content.Server/Imperial/Vampire/VampireSystem.cs index 18e9d291d6f..64cc66e582a 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.cs @@ -18,6 +18,7 @@ using Content.Server.Cloning; using Robust.Shared.Prototypes; using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute; +using Content.Shared.Humanoid; using Content.Server.EUI; using Content.Shared.Actions; using Content.Shared.Interaction; @@ -27,7 +28,6 @@ using Content.Shared.Mobs; using Content.Server.Bible.Components; using Content.Shared.Alert; -using Content.Shared.Humanoid; namespace Content.Server.Imperial.Vampire; @@ -281,10 +281,10 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) } // после того, как вампир выпивает кровь его глаза становятся красными - if (TryComp(drinker, out var humanoidAppearance)) + if (TryComp(drinker, out var appear)) { - humanoidAppearance.EyeColor = Color.Red; - Dirty(drinker, humanoidAppearance); + appear.EyeColor = Color.Red; + Dirty(drinker, appear); } // наносим жертве урон от кровопотери From b914a04c321c5ca413419a8a6ff56c3480ddc292 Mon Sep 17 00:00:00 2001 From: Stellark Date: Mon, 16 Mar 2026 21:47:54 +0300 Subject: [PATCH 6/7] Update VampireSystem.cs --- .../Imperial/Vampire/VampireSystem.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Content.Server/Imperial/Vampire/VampireSystem.cs b/Content.Server/Imperial/Vampire/VampireSystem.cs index 64cc66e582a..2bd11c7ab79 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.cs @@ -18,7 +18,6 @@ using Content.Server.Cloning; using Robust.Shared.Prototypes; using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute; -using Content.Shared.Humanoid; using Content.Server.EUI; using Content.Shared.Actions; using Content.Shared.Interaction; @@ -28,6 +27,9 @@ using Content.Shared.Mobs; using Content.Server.Bible.Components; using Content.Shared.Alert; +using System.Runtime.CompilerServices; +using Content.Server.Body; +using System.Linq; namespace Content.Server.Imperial.Vampire; @@ -51,6 +53,8 @@ public sealed partial class VampireSystem : EntitySystem [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly AlertsSystem _alert = default!; + [Dependency] private readonly VisualBodySystem _visualBodySystem = default!; + private void VampireInitialize() { @@ -240,12 +244,11 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) damage.DamageDict["Bloodloss"] = FixedPoint2.New(amount); _damage.TryChangeDamage(target, damage); - // после того, как вампир выпивает кровь его глаза становятся красными - if (TryComp(drinker, out var humanoidAppearance)) - { - humanoidAppearance.EyeColor = Color.Red; - Dirty(drinker, humanoidAppearance); - } + var eui = new VampireRequestedEui(drinker, EntityManager, _actions, _vampireSystem, _prototypeManager); + eui.GrantAbilities(drinker, vamp.SelectedSubgroup); + + // после того, как вампир выпивает кровь его глаза становятся красными + TrySetEntityEyeColor(drinker, Color.Red); if (_mobState.IsAlive(target)) StartDrinking(drinker, target); @@ -281,11 +284,7 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) } // после того, как вампир выпивает кровь его глаза становятся красными - if (TryComp(drinker, out var appear)) - { - appear.EyeColor = Color.Red; - Dirty(drinker, appear); - } + TrySetEntityEyeColor(drinker, Color.Red); // наносим жертве урон от кровопотери damage.DamageDict["Bloodloss"] = FixedPoint2.New(amount * 2); @@ -387,4 +386,14 @@ public void BaseUpdate(float frameTime) comp.UpdateDelay = 0; } } + + private bool TrySetEntityEyeColor(EntityUid uid, Color eyeColor) + { + if (!_visualBodySystem.TryGatherMarkingsData(uid, null, out var profiles, out _, out var markings)) return false; + + var coloredProfile = profiles.ToDictionary(pair => pair.Key, pair => pair.Value with { EyeColor = eyeColor }); + _visualBodySystem.ApplyProfiles(uid, coloredProfile); + + return true; + } } From 6f3695f339dd03b7b9d998abc699bc80777385ea Mon Sep 17 00:00:00 2001 From: Stellark Date: Mon, 16 Mar 2026 22:02:55 +0300 Subject: [PATCH 7/7] fix upstream bug --- Content.Server/Imperial/Vampire/VampireSystem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Content.Server/Imperial/Vampire/VampireSystem.cs b/Content.Server/Imperial/Vampire/VampireSystem.cs index 2bd11c7ab79..85f516256c2 100644 --- a/Content.Server/Imperial/Vampire/VampireSystem.cs +++ b/Content.Server/Imperial/Vampire/VampireSystem.cs @@ -222,8 +222,6 @@ private void DrinkingComplete(EntityUid drinker, EntityUid target, float amount) { vamp.TotalDrunk += amount; _vampireSystem.SetBloodCounterAlert(drinker, vamp); - var eui = new VampireRequestedEui(drinker, EntityManager, _actions, _vampireSystem, _prototypeManager); - eui.GrantAbilities(drinker, vamp.SelectedSubgroup); } // мы просто понижаем BloodDamage, но в TotalDrunk не засчитываем else if (!HasComp(target) || !HasComp(target))