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.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..d4616dff9d7 --- /dev/null +++ b/Content.Client/Vampire/VampireMutationMenu.xaml.cs @@ -0,0 +1,92 @@ +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.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", + 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.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/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index d6a109321ab..c193578f822 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -13,12 +13,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 { @@ -34,12 +38,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); @@ -47,6 +53,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(); @@ -116,7 +136,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/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index 3497d4a6d78..3b7d9306ce1 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Chemistry.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; @@ -230,6 +231,31 @@ private void TryMetabolize(Entity(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(); + } } // TODO REFACTOR THIS diff --git a/Content.Server/Body/Systems/StomachSystem.cs b/Content.Server/Body/Systems/StomachSystem.cs index 9fc7ff10e46..a8cf946f63b 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.Timing; using Robust.Shared.Utility; @@ -130,5 +131,10 @@ public bool TryTransferSolution( return true; } + + public void SetSpecialDigestible(StomachComponent component, EntityWhitelist? whitelist) + { + component.SpecialDigestible = whitelist; + } } } diff --git a/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs new file mode 100644 index 00000000000..3fc2b7c2d06 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/VampireRuleComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared.NPC.Prototypes; +using Content.Shared.Roles; +using Content.Shared.Vampire.Components; +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> BaseObjectives = new() + { + "VampireKillRandomPersonObjective", + "VampireDrainObjective" + }; + + public readonly List> EscapeObjectives = new() + { + "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 new file mode 100644 index 00000000000..a5522848dcc --- /dev/null +++ b/Content.Server/GameTicking/Rules/VampireRuleSystem.cs @@ -0,0 +1,159 @@ +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.Server.GameObjects; +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!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = 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(OnGetBriefing); + + SubscribeLocalEvent(OnSelectAntag); + //SubscribeLocalEvent(OnTextPrepend); + } + + private void OnSelectAntag(EntityUid mindId, VampireRuleComponent comp, ref AfterAntagEntitySelectedEvent args) + { + var ent = args.EntityUid; + _antag.SendBriefing(ent, MakeBriefing(ent), Color.Yellow, BriefingSound); + MakeVampire(ent, 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")); + + _role.MindHasRole(mindId, out var vampireRole); + _role.MindHasRole(mindId, out var briefingComp); + if (vampireRole is not null && briefingComp is 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); + var interfaceComponent = EnsureComp(target); + + if (HasComp(target)) + _uiSystem.SetUiState(target, VampireMutationUiKey.Key, new VampireMutationBoundUserInterfaceState(vampireComponent.VampireMutations, vampireComponent.CurrentMutation)); + + vampireComponent.Balance = new() { { VampireComponent.CurrencyProto, 0 } }; + + rule.VampireMinds.Add(mindId); + + if (HasComp(target)) + _vampire.AddStartingAbilities(target); + + Random random = new Random(); + + 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); + } + + if (rule.StealObjectives.Count > 0) + { + var randomEscapeObjective = rule.StealObjectives[random.Next(rule.StealObjectives.Count)]; + _mind.TryAddObjective(mindId, mind, randomEscapeObjective); + } + + 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) + { + var mostDrainedName = string.Empty; + var mostDrained = 0f; + + foreach (var vamp in EntityQuery()) + { + if (!_mind.TryGetMind(vamp.Owner, out var mindId, out var mind)) + continue; + + if (!TryComp(vamp.Owner, out var metaData)) + continue; + + if (vamp.TotalBloodDrank > mostDrained) + { + mostDrained = vamp.TotalBloodDrank; + mostDrainedName = _objective.GetTitle((mindId, mind), metaData.EntityName); + } + } + + var sb = new StringBuilder(); + 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/Objectives/Components/BloodDrainCondition.cs b/Content.Server/Objectives/Components/BloodDrainCondition.cs new file mode 100644 index 00000000000..afaaafc3a8e --- /dev/null +++ b/Content.Server/Objectives/Components/BloodDrainCondition.cs @@ -0,0 +1,11 @@ +using Content.Server.Vampire; +using Content.Server.Objectives.Systems; + +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/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/Content.Server/Store/Conditions/BuyBeforeCondition.cs b/Content.Server/Store/Conditions/BuyBeforeCondition.cs index fcfb5f92c9d..ad38923b669 100644 --- a/Content.Server/Store/Conditions/BuyBeforeCondition.cs +++ b/Content.Server/Store/Conditions/BuyBeforeCondition.cs @@ -1,3 +1,4 @@ +using Content.Server.Store.Systems; using Content.Shared.Store; using Content.Shared.Store.Components; using Robust.Shared.Prototypes; @@ -15,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/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index f12e52235f4..928dcde79bb 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -161,8 +161,8 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi { return; } - } + } if (!IsOnStartingMap(uid, component)) component.RefundAllowed = false; @@ -180,7 +180,8 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi RaiseLocalEvent(buyer, ref ev); // Sunrise-End - } + } + //spawn entity if (listing.ProductEntity != null) { @@ -257,6 +258,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi if (upgradeActionId != null) HandleRefundComp(uid, component, upgradeActionId.Value); + } if (listing.ProductEvent != null) @@ -267,6 +269,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi RaiseLocalEvent(buyer, listing.ProductEvent); } + //log dat shit. _admin.Add(LogType.StorePurchase, LogImpact.Low, diff --git a/Content.Server/Vampire/VampireSystem.Abilities.cs b/Content.Server/Vampire/VampireSystem.Abilities.cs new file mode 100644 index 00000000000..9891eafe6a3 --- /dev/null +++ b/Content.Server/Vampire/VampireSystem.Abilities.cs @@ -0,0 +1,762 @@ +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.Actions; +using Content.Server.Objectives.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.Database; +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.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; +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 +{ + private FrozenDictionary _powerCache = default!; + private FrozenDictionary _passiveCache = default!; + + private void InitializePowers() + { + _powerCache = BuildPowerCache(); + _passiveCache = BuildPassiveCache(); + + //Abilities + SubscribeLocalEvent(OnVampireOpenMutationsMenu); + 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 OnVampireOpenMutationsMenu(EntityUid uid, VampireComponent component, VampireOpenMutationsMenu ev) + { + TryOpenUi(uid, ev.Performer, component); + 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; + + 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 (!IsPowerUnlocked(vampire, def.ID)) + 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 false; + } + + //Block if we are stunned + if (!def.UsableWhileStunned && HasComp(vampire)) + { + _popup.PopupEntity(Loc.GetString("vampire-stunned"), vampire, vampire, PopupType.MediumCaution); + 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 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; + } + + return true; + } + + + #region Passive Powers + private void UnnaturalStrength(Entity vampire) + { + var damage = new DamageSpecifier(); + damage.DamageDict.Add("Slash", 15); + + var meleeComp = EnsureComp(vampire); + meleeComp.Damage += damage; + } + private void SupernaturalStrength(Entity vampire) + { + var pryComp = EnsureComp(vampire); + pryComp.Force = true; + pryComp.PryPowered = true; + + var damage = new DamageSpecifier(); + damage.DamageDict.Add("Slash", 15); + + var meleeComp = EnsureComp(vampire); + meleeComp.Damage += damage; + } + #endregion + + #region Other Powers + 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)) + { + 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)) + { + _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 bool CloakOfDarkness(Entity vampire, float upkeep, float passiveVisibilityRate, float movementVisibilityRate) + { + if (HasComp(vampire)) + { + RemComp(vampire); + RemComp(vampire); + RemComp(vampire); + _popup.PopupEntity(Loc.GetString("vampire-cloak-disable"), vampire, vampire); + return false; + } + else + { + 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); + return true; + } + } + #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 VampireHypnotiseDoAfterEvent() { Duration = duration }, + eventTarget: vampire, + target: target, + used: target) + { + BreakOnMove = true, + BreakOnDamage = 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 VampireHypnotiseDoAfterEvent 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, VampireDeathsEmbraceComponent component, MobStateChangedEvent args) + { + if (args.OldMobState != MobState.Dead && args.NewMobState == MobState.Dead) + { + //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, VampireDeathsEmbraceComponent component, EntGotInsertedIntoContainerMessage args) + { + if (HasComp(args.Container.Owner)) + { + component.HomeCoffin = args.Container.Owner; + 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"); + + } + } + /// + /// When leaving a container, remove the healing component + /// + private void OnRemovedFromContainer(EntityUid uid, VampireDeathsEmbraceComponent component, EntGotRemovedFromContainerMessage args) + { + 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) + { + 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); + + 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 + /// Revive them if they are dead and below a ~150ish damage + /// + 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 + 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 + /// + /// Toggle if fangs are extended + /// + private bool ToggleFangs(Entity vampire) + { + if (HasComp(vampire)) + { + 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 + { + 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; + } + } + /// + /// Check and start drinking blood from a humanoid + /// + private bool TryDrink(Entity vampire, EntityUid target, TimeSpan doAfterDelay) + { + //Do a precheck + if (!HasComp(vampire)) + return false; + + if (!HasComp(vampire)) + return false; + + if (!_interaction.InRangeUnobstructed(vampire.Owner, 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, PopupType.SmallCaution); + return false; + } + + var doAfterEventArgs = new DoAfterArgs(EntityManager, vampire, doAfterDelay, + new VampireDrinkBloodDoAfterEvent() { Volume = vampire.Comp.MouthVolume }, + eventTarget: vampire, + target: target, + used: target) + { + BreakOnMove = true, + BreakOnDamage = true, + MovementThreshold = 0.01f, + DistanceThreshold = 1.0f, + NeedHand = false, + Hidden = true + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + return true; + } + private void DrinkDoAfter(Entity entity, ref VampireDrinkBloodDoAfterEvent args) + { + if (args.Cancelled) + return; + + if (!HasComp(entity)) + return; + + if (_food.IsMouthBlocked(entity, entity)) + return; + + 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); + 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)); + + //Spill an extra 5% on the floor + _blood.TryModifyBloodLevel(args.Target.Value, -(volumeToDrain * 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); + _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; + } + //Check for zombie + else + { + //Pull out some of the blood + var bloodSolution = _solution.SplitSolution(targetBloodstream.BloodSolution.Value, volumeToConsume); + + if (!TryIngestBlood(entity, bloodSolution)) + { + //Undo, put the blood back + _solution.AddSolution(targetBloodstream.BloodSolution.Value, bloodSolution); + return; + } + + _admin.Add(LogType.Damaged, LogImpact.Low, $"{ToPrettyString(entity):user} drank {volumeToConsume}u of {ToPrettyString(args.Target):target}'s blood"); + AddBloodEssence(entity, volumeToConsume * 0.95); + + args.Repeat = true; + } + } + /// + /// 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.TryGetBodyOrganEntityComps((vampire.Owner, body), out var stomachs)) + { + //Pick the first one that has space available + var firstStomach = stomachs.FirstOrNull(stomach => _stomach.CanTransferSolution(stomach.Owner, ingestedSolution, stomach.Comp1)); + 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.Owner, ingestedSolution, firstStomach.Value.Comp1); + } + + //No stomach + return false; + } + #endregion + + private bool IsPowerUnlocked(VampireComponent vampire, string name) + { + return vampire.UnlockedPowers.ContainsKey(name); + } + /*private bool IsPowerActive(VampireComponent vampire, VampirePowerProtype def) => IsPowerActive(vampire, def.ID); + private bool IsPowerActive(VampireComponent vampire, string name) + { + return vampire.ActivePowers.Contains(name); + } + private bool SetPowerActive(VampireComponent vampire, string name, bool active) + { + if (active) + { + return vampire.ActivePowers.Add(name); + } + else + { + 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; + } + + /// + /// Cache all power prototypes in a dictionary by keyed by ID + /// + /// + private FrozenDictionary BuildPowerCache() + { + 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.Objectives.cs b/Content.Server/Vampire/VampireSystem.Objectives.cs new file mode 100644 index 00000000000..3e94fb6494c --- /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) + args.Progress = MathF.Min(comp.BloodDranked / target, 1f); + else args.Progress = 1f; + } +} \ No newline at end of file diff --git a/Content.Server/Vampire/VampireSystem.Transform.cs b/Content.Server/Vampire/VampireSystem.Transform.cs new file mode 100644 index 00000000000..21f29b67f90 --- /dev/null +++ b/Content.Server/Vampire/VampireSystem.Transform.cs @@ -0,0 +1,145 @@ +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.Chemistry; +using Content.Shared.Nutrition.Components; +using Content.Shared.Vampire; +using Content.Shared.Vampire.Components; +using Content.Shared.Weapons.Melee; +using Robust.Shared.Audio; + +namespace Content.Server.Vampire; + +public sealed partial class VampireSystem +{ + /// + /// Convert the players into a vampire, all programatic because i dont want to replace the players body + /// + private void MakeVampire(EntityUid vampireUid) + { + var vampireComponent = EnsureComp(vampireUid); + var vampire = new Entity(vampireUid, vampireComponent); + + //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; + + MakeVulnerableToHoly(vampire); + + //Initialise currency + vampireComponent.Balance = new() { { VampireComponent.CurrencyProto, 0 } }; + + //Add the summon heirloom ability + AddStartingAbilities(vampire); + + //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)) + { + 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; + + //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.ClearMetabolizerTypes(metabolizer); + _stomach.SetSpecialDigestible(stomachComponent, VampireComponent.AcceptableFoods); + } + + _metabolism.TryAddMetabolizerType(metabolizer, VampireComponent.MetabolizerVampire); + _metabolism.TryAddMetabolizerType(metabolizer, VampireComponent.MetabolizerBloodsucker); + } + } + } + + public void AddStartingAbilities(EntityUid vampire) + { + if (!TryComp(vampire, out var comp)) + return; + + 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; + + comp.UnlockedPowers.Add(actionEvent.DefinitionName, action); + + } + + UpdateBloodDisplay(vampire); + } + + //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)) + { + _metabolism.TryRemoveMetabolizerType(metabolizer, VampireComponent.MetabolizerVampire); + } + } + + 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 new file mode 100644 index 00000000000..464a6e929f9 --- /dev/null +++ b/Content.Server/Vampire/VampireSystem.cs @@ -0,0 +1,482 @@ +using Content.Server.Administration.Logs; +using Content.Server.Atmos.Rotting; +using Content.Server.Beam; +using Content.Server.Body.Systems; +using Content.Server.Chat.Systems; +using Content.Server.Interaction; +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; +using Content.Shared.Bed.Sleep; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Construction.Components; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.FixedPoint; +using Content.Shared.Hands.EntitySystems; +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; +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; +using Robust.Shared.Prototypes; +using System.Linq; + +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!; + [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 SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IMapManager _mapMan = default!; + [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!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = 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!; + [Dependency] private readonly MetabolizerSystem _metabolism = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + + private Dictionary _actionEntities = new(); + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnComponentStartup); + + //SubscribeLocalEvent(OnUseSelfPower); + //SubscribeLocalEvent(OnUseTargetedPower); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnVampireBloodChangedEvent); + + SubscribeLocalEvent(GetState); + SubscribeLocalEvent(OnMutationSelected); + + InitializePowers(); + InitializeObjectives(); + } + + /// + /// Handles healing and damaging in space + /// + public override void Update(float frameTime) + { + base.Update(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; + if (!SubtractBloodEssence((uid, vampire), stealth.Upkeep)) + RemCompDeferred(uid); + } + stealth.NextStealthTick -= frameTime; + } + + var healingQuery = EntityQueryEnumerator(); + while (healingQuery.MoveNext(out var uid, out _, out var healing)) + { + if (healing == null) + continue; + + if (healing.NextHealTick <= 0) + { + 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)) + { + if (vampireComponent.NextSpaceDamageTick <= 0) + { + vampireComponent.NextSpaceDamageTick = 1; + DoSpaceDamage(vampire); + } + vampireComponent.NextSpaceDamageTick -= frameTime; + } + }*/ + } + + private void OnComponentStartup(EntityUid uid, VampireComponent component, ComponentStartup args) + { + //MakeVampire(uid); + } + + private void OnExamined(EntityUid uid, VampireComponent component, ExaminedEvent args) + { + 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) + { + if (quantity < 0) + return false; + + vampire.Comp.TotalBloodDrank += quantity.Float(); + vampire.Comp.Balance[VampireComponent.CurrencyProto] += quantity; + + UpdateBloodDisplay(vampire); + + var ev = new VampireBloodChangedEvent(); + RaiseLocalEvent(vampire, ev); + + return true; + } + private bool SubtractBloodEssence(Entity vampire, FixedPoint2 quantity) + { + if (quantity < 0) + return false; + + if (vampire.Comp.Balance[VampireComponent.CurrencyProto] < quantity) + return false; + + vampire.Comp.Balance[VampireComponent.CurrencyProto] -= quantity; + + UpdateBloodDisplay(vampire); + + var ev = new VampireBloodChangedEvent(); + RaiseLocalEvent(vampire, ev); + + return true; + } + /// + /// Use the charges display on SummonHeirloom to show the remaining blood essence + /// + /// + 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 (!comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var balance)) + return; + + var chargeDisplay = (int) Math.Round((decimal) balance); + var mutationsAction = GetPowerEntity(comp, VampireComponent.MutationsActionPrototype); + + if (mutationsAction == null) + return; + + _action.SetCharges(mutationsAction, chargeDisplay); + } + + private void OnVampireBloodChangedEvent(EntityUid uid, VampireComponent component, VampireBloodChangedEvent args) + { + EntityUid? newEntity = null; + EntityUid entity = default; + // Mutations + if (GetBloodEssence(uid) >= FixedPoint2.New(50) && !_actionEntities.TryGetValue(VampireComponent.MutationsActionPrototype, out entity)) + { + _action.AddAction(uid, ref newEntity, VampireComponent.MutationsActionPrototype); + if (newEntity != null) + _actionEntities[VampireComponent.MutationsActionPrototype] = newEntity.Value; + } + else if (GetBloodEssence(uid) < FixedPoint2.New(50) && _actionEntities.TryGetValue(VampireComponent.MutationsActionPrototype, out entity)) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove(VampireComponent.MutationsActionPrototype); + } + + //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; + if (!component.UnlockedPowers.ContainsKey("BloodSteal")) + { + component.UnlockedPowers.Add("BloodSteal", newEntity); + } + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireBloodSteal", out entity)) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireBloodSteal"); + } + + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireScreech", out entity) && component.CurrentMutation == VampireMutationsType.Hemomancer) + { + _action.AddAction(uid, ref newEntity , "ActionVampireScreech"); + if (newEntity != null) + { + _actionEntities["ActionVampireScreech"] = newEntity.Value; + if (!component.UnlockedPowers.ContainsKey("Screech")) + { + 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) && !_actionEntities.TryGetValue("ActionVampireGlare", out entity) && component.CurrentMutation == VampireMutationsType.Umbrae) + { + _action.AddAction(uid, ref newEntity , "ActionVampireGlare"); + if (newEntity != null) + { + _actionEntities["ActionVampireGlare"] = newEntity.Value; + if (!component.UnlockedPowers.ContainsKey("Glare")) + { + 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) && !_actionEntities.TryGetValue("ActionVampireCloakOfDarkness", out entity) && component.CurrentMutation == VampireMutationsType.Umbrae) + { + _action.AddAction(uid, ref newEntity , "ActionVampireCloakOfDarkness"); + if (newEntity != null) + { + _actionEntities["ActionVampireCloakOfDarkness"] = newEntity.Value; + if (!component.UnlockedPowers.ContainsKey("CloakOfDarkness")) + { + 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 + + if (GetBloodEssence(uid) >= FixedPoint2.New(200) && !_actionEntities.TryGetValue("ActionVampireUnnaturalStrength", out entity) && component.CurrentMutation == VampireMutationsType.Gargantua) + { + var vampire = new Entity(uid, component); + + UnnaturalStrength(vampire); + + _actionEntities["ActionVampireUnnaturalStrength"] = vampire; + } + + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireSupernaturalStrength", out entity) && component.CurrentMutation == VampireMutationsType.Gargantua) + { + var vampire = new Entity(uid, component); + + SupernaturalStrength(vampire); + + _actionEntities["ActionVampireSupernaturalStrength"] = vampire; + } + + //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; + if (!component.UnlockedPowers.ContainsKey("PolymorphBat")) + { + component.UnlockedPowers.Add("PolymorphBat", newEntity); + } + } + } + else if (GetBloodEssence(uid) < FixedPoint2.New(200) && _actionEntities.TryGetValue("ActionVampireBatform", out entity)) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + + _action.RemoveAction(uid, entity, comp); + _actionContainer.RemoveAction(entity); + _actionEntities.Remove("ActionVampireBatform"); + } + + if (GetBloodEssence(uid) >= FixedPoint2.New(300) && !_actionEntities.TryGetValue("ActionVampireMouseform", out entity) && component.CurrentMutation == VampireMutationsType.Bestia) + { + _action.AddAction(uid, ref newEntity , "ActionVampireMouseform"); + if (newEntity != null) + { + _actionEntities["ActionVampireMouseform"] = newEntity.Value; + if (!component.UnlockedPowers.ContainsKey("PolymorphMouse")) + { + 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"); + } + } + + private FixedPoint2 GetBloodEssence(EntityUid vampire) + { + if (!TryComp(vampire, out var comp)) + return 0; + + if (!comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var val)) + return 0; + + return val; + } + + private void DoSpaceDamage(Entity vampire) + { + _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); + 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 || 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; + } + + 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) + { + var vampire = new Entity(uid, component); + if (SubtractBloodEssence(vampire, FixedPoint2.New(50))) + { + component.CurrentMutation = newMutation; + UpdateUi(uid, component); + var ev = new VampireBloodChangedEvent(); + RaiseLocalEvent(uid, ev); + TryOpenUi(uid, component.Owner, 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/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/Store/ListingPrototype.cs b/Content.Shared/Store/ListingPrototype.cs index 05ac5cc4cd5..f3b3e408c7f 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 Content.Shared.Store.Components; using Content.Shared.StoreDiscount.Components; using Robust.Shared.Prototypes; diff --git a/Content.Shared/Vampire/VampireComponent.cs b/Content.Shared/Vampire/VampireComponent.cs new file mode 100644 index 00000000000..231ea6d2fd9 --- /dev/null +++ b/Content.Shared/Vampire/VampireComponent.cs @@ -0,0 +1,268 @@ +using Content.Shared.Body.Prototypes; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.StatusEffect; +using Content.Shared.Store; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Vampire.Components; + +[RegisterComponent] +public sealed partial class VampireComponent : Component +{ + //Static prototype references + [ValidatePrototypeId] + public static readonly string SleepStatusEffectProto = "ForcedSleep"; + [ValidatePrototypeId] + public static readonly string ScreamEmoteProto = "Scream"; + [ValidatePrototypeId] + public static readonly string CurrencyProto = "BloodEssence"; + + [ViewVariables(VVAccess.ReadOnly), DataField("defaultMutation")] + public VampireMutationsType DefaultMutation = VampireMutationsType.None; + [ViewVariables(VVAccess.ReadOnly), DataField("currentMutation")] + public VampireMutationsType CurrentMutation = VampireMutationsType.None; + + public readonly HashSet VampireMutations = new() + { + VampireMutationsType.None, + VampireMutationsType.Hemomancer, + VampireMutationsType.Umbrae, + VampireMutationsType.Gargantua, + //VampireMutationsType.Dantalion, + VampireMutationsType.Bestia + }; + + public static readonly EntityWhitelist AcceptableFoods = new() + { + Tags = new() { "Pill" } + }; + [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 } } + }; + public static readonly DamageSpecifier HolyDamage = new() + { + DamageDict = new Dictionary() { { "Burn", 10 } } + }; + public static readonly DamageSpecifier SpaceDamage = new() + { + DamageDict = new Dictionary() { { "Burn", 2.5 } } + }; + + [ValidatePrototypeId] + public static readonly string MutationsActionPrototype = "ActionVampireOpenMutationsMenu"; + + [ViewVariables(VVAccess.ReadWrite)] + public EntityUid? MutationsAction; + + public readonly List> BaseVampireActions = new() + { + "ActionVampireToggleFangs", + "ActionVampireHypnotise" + }; + + [ValidatePrototypeId] + public static readonly string DrinkBloodPrototype = "DrinkBlood"; + + /// + /// Total blood drank, counter for end of round screen + /// + [ViewVariables(VVAccess.ReadWrite)] + public float TotalBloodDrank = 0; + + /// + /// How much blood per mouthful + /// + [ViewVariables(VVAccess.ReadWrite)] + public float MouthVolume = 5; + + /// + /// All unlocked abilities + /// + public Dictionary UnlockedPowers = new(); + + /// + /// Current available balance, used to sync currency across heirlooms and add essence as we feed + /// + 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"); +} + + +/// +/// Contains all details about the ability and its effects or restrictions +/// +[DataDefinition] +[Prototype("vampirePower")] +public sealed partial class VampirePowerProtype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } + + [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; +} + +[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 { } + +/// +/// Marks a container as a coffin, for the purposes of vampire healing +/// +[RegisterComponent] +public sealed partial class CoffinComponent : Component { } + +[RegisterComponent] +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; + + 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 +{ + [ViewVariables(VVAccess.ReadWrite)] + public float NextStealthTick = 0; + + [ViewVariables(VVAccess.ReadWrite)] + public float Upkeep = 0; +} + +[Serializable, NetSerializable] +public enum VampireMutationsType : byte +{ + None, + Hemomancer, + Umbrae, + Gargantua, + Dantalion, + 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 +{ + ToggleFangs, + Glare, + DeathsEmbrace, + Screech, + Hypnotise, + Polymorph, + NecroticTouch, + BloodSteal, + CloakOfDarkness, + StellarWeakness, + SummonHeirloom, + + //Passives + UnnaturalStrength, + SupernaturalStrength +}*/ diff --git a/Content.Shared/Vampire/VampireEvents.cs b/Content.Shared/Vampire/VampireEvents.cs new file mode 100644 index 00000000000..149a7d19ec6 --- /dev/null +++ b/Content.Shared/Vampire/VampireEvents.cs @@ -0,0 +1,61 @@ +using Content.Shared.Actions; +using Content.Shared.DoAfter; +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 VampireToggleFangsEvent : 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 { } +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(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DefinitionName = default!; +}; +public abstract partial class VampireTargetedPowerEvent : EntityTargetActionEvent +{ + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DefinitionName = default!; +}; +public sealed partial class VampirePassiveActionEvent : BaseActionEvent +{ + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DefinitionName = default!; +}; + +//Purchase passive events +[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 +{ + [DataField] + public float Volume = 0; + + public override DoAfterEvent Clone() => this; +} + +[Serializable, NetSerializable] +public sealed partial class VampireHypnotiseDoAfterEvent : DoAfterEvent +{ + [DataField] + public TimeSpan? Duration = TimeSpan.Zero; + + public override DoAfterEvent Clone() => this; +} diff --git a/Resources/Audio/Ambience/Antag/vampire_start.ogg b/Resources/Audio/Ambience/Antag/vampire_start.ogg new file mode 100644 index 00000000000..8d8616f6a44 Binary files /dev/null and b/Resources/Audio/Ambience/Antag/vampire_start.ogg differ diff --git a/Resources/Audio/Effects/Vampire/glare.ogg b/Resources/Audio/Effects/Vampire/glare.ogg new file mode 100644 index 00000000000..44e46ea28ef Binary files /dev/null and b/Resources/Audio/Effects/Vampire/glare.ogg differ diff --git a/Resources/Audio/Effects/Vampire/screech_tone.ogg b/Resources/Audio/Effects/Vampire/screech_tone.ogg new file mode 100644 index 00000000000..4a7e072542f Binary files /dev/null and b/Resources/Audio/Effects/Vampire/screech_tone.ogg differ diff --git a/Resources/Locale/en-US/_strings/Vampires/vampires.ftl b/Resources/Locale/en-US/_strings/Vampires/vampires.ftl new file mode 100644 index 00000000000..4274692292e --- /dev/null +++ b/Resources/Locale/en-US/_strings/Vampires/vampires.ftl @@ -0,0 +1,72 @@ +vampires-title = Vampires + +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 + +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 + +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 + +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 {MAKEPLURAL(THE($target))} eyes! + +store-currency-display-blood-essence = Blood Essence +store-category-vampirepowers = Powers +store-category-vampirepassives = Passives + +#Powers +vampire-power-summonheirloom = Summon Heirloom +vampire-power-summonheirloom-description = Summon a family heirloom, gifted by lilith herself. + +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-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-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-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-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-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-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-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-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/Locale/en-US/_strings/chapel/bible.ftl b/Resources/Locale/en-US/_strings/chapel/bible.ftl index c59492b70a6..191aa870a7f 100644 --- a/Resources/Locale/en-US/_strings/chapel/bible.ftl +++ b/Resources/Locale/en-US/_strings/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/Locale/ru-RU/_prototypes/actions/vampire.ftl b/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl new file mode 100644 index 00000000000..7b1ba86a452 --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/actions/vampire.ftl @@ -0,0 +1,18 @@ +ent-ActionVampireOpenMutationsMenu = Меню мутаций + .desc = Открывает меню с мутациями вампира. +ent-ActionVampireToggleFangs = Высунуть клыки + .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/_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 new file mode 100644 index 00000000000..ea8bf7c56c3 --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/Vampires/vampires.ftl @@ -0,0 +1,94 @@ +vampires-title = Вампиры + +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 + +#Passives +vampire-passive-unholystrength = Нечестивая сила +vampire-passive-unholystrength-description = Наполните мышцы верхней части тела кровью, наделяя вас когтями и повышенной силой. Эффект: 10 порезов за удар + +vampire-passive-supernaturalstrength = Сверхъестественная сила +vampire-passive-supernaturalstrength-description = Увеличьте силу мышц верхней части тела, и ни одна преграда не встанет на вашем пути. Эффект: 15 порезов за удар, возможность открывать двери руками. + +vampire-passive-deathsembrace = Объятия смерти +vampire-passive-deathsembrace-description = Примите смерть, и она обойдет вас стороной. Эффект: исцеление в гробу, автоматическое возвращение в гроб после смерти за 100 эссенции крови. + +#Mutation menu + +vampire-mutation-menu-ui-window-name = Меню мутаций + +vampire-mutation-none-info = Ничего не выбрано + +vampire-mutation-hemomancer-info = + Гемомансер + + Фокусируеться на кровавой магии и манипуляции крови вокруг себя. + + Способности: + + - Визг + - Кража крови + +vampire-mutation-umbrae-info = + Тень + + Фокусируется на темноте, стелсе, мобильности. + + Способности: + + - Блик + - Плащ тьмы + +vampire-mutation-gargantua-info = + Гаргантюа + + Фокусируется на ближнем уроне и стойкости. + + Способности: + + - Нечестивая сила + - Сверхъестественная сила + +vampire-mutation-bestia-info = + Бестия + + Фокусируется на превращении и собирании трофеев + + Способности: + + - Форма летучей мыши + - Мышиная форма + +## 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/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/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..4df784c9a4a --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/game-ticking/game-presets/preset-vampire.ftl @@ -0,0 +1,12 @@ +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-drained = Кто-то выпил в общей сложности [color=red]{ $number }[/color] крови. +vampire-gamemode-title = Вампиры +vampire-gamemode-description = Кровожадные вампиры пробрались на станцию чтобы вдоволь напиться крови! +vampire-role-greeting = + Вы вампир, который пробрался на станцию под видом работника! + Ваши задачи указаны в меню персонажа. + Пейте кровь и эволюционируйте, чтобы выполнить их! +vampire-role-greeting-short = Вы вампир, который пробрался на станцию под видом работника! +roles-antag-vamire-name = Вампир \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_strings/metabolism/metabolizer-types.ftl b/Resources/Locale/ru-RU/_strings/metabolism/metabolizer-types.ftl index 7a17abf3656..f3766a35c03 100644 --- a/Resources/Locale/ru-RU/_strings/metabolism/metabolizer-types.ftl +++ b/Resources/Locale/ru-RU/_strings/metabolism/metabolizer-types.ftl @@ -9,3 +9,4 @@ metabolizer-type-plant = Растение metabolizer-type-dwarf = Дварф metabolizer-type-moth = Ниан metabolizer-type-arachnid = Арахнид +metabolizer-type-vampire = Вампир \ 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..5f7872ef9c4 --- /dev/null +++ b/Resources/Prototypes/Actions/vampire.yml @@ -0,0 +1,176 @@ +- type: entity + id: ActionVampireOpenMutationsMenu + name: Mutations menu + description: "Opens menu with vampires mutations." + categories: [ HideSpawnMenu ] + components: + - type: InstantAction + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: summonheirloom + event: + !type:VampireOpenMutationsMenu + useDelay: 5 + +- type: entity + id: ActionVampireToggleFangs + name: Toggle Fangs + description: "Extend or retract your fangs. Walking around with your fangs out might reveal your true nature." + categories: [ HideSpawnMenu ] + components: + - type: InstantAction + checkCanInteract: false + priority: 1 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: fangs_retracted + iconOn: + sprite: Interface/Actions/actions_vampire.rsi + state: fangs_extended + event: + !type:VampireToggleFangsEvent + definitionName: ToggleFangs + +- type: entity + id: ActionVampireGlare + 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 + whitelist: + components: + - Body + priority: 2 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: glare + sound: !type:SoundPathSpecifier + path: /Audio/Effects/Vampire/glare.ogg + event: + !type:VampireGlareEvent + definitionName: Glare + useDelay: 60 + +- type: entity + id: ActionVampireHypnotise + 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 + whitelist: + components: + - HumanoidAppearance + canTargetSelf: false + interactOnMiss: false + priority: 2 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: hypnotise + event: + !type:VampireHypnotiseEvent + definitionName: Hypnotise + useDelay: 300 + +- type: entity + id: ActionVampireScreech + 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 + checkCanInteract: false + priority: 2 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: screech + sound: !type:SoundPathSpecifier + path: /Audio/Effects/Vampire/screech_tone.ogg + event: + !type:VampireScreechEvent + definitionName: Screech + useDelay: 60 + +- type: entity + id: ActionVampireBloodSteal + 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 + checkCanInteract: false + priority: 2 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: bloodsteal + sound: !type:SoundPathSpecifier + path: /Audio/Effects/demon_consume.ogg + event: + !type:VampireBloodStealEvent + definitionName: BloodSteal + useDelay: 60 + +- type: entity + id: ActionVampireBatform + 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 + checkCanInteract: false + priority: 2 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: batform + sound: !type:SoundPathSpecifier + path: /Audio/Effects/teleport_arrival.ogg + event: + !type:VampirePolymorphEvent + definitionName: PolymorphBat + useDelay: 30 + +- type: entity + id: ActionVampireMouseform + 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 + checkCanInteract: false + priority: 2 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: mouseform + sound: !type:SoundPathSpecifier + path: /Audio/Effects/teleport_arrival.ogg + event: + !type:VampirePolymorphEvent + definitionName: PolymorphMouse + useDelay: 30 + +- type: entity + id: ActionVampireCloakOfDarkness + 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 + checkCanInteract: false + priority: 2 + itemIconStyle: NoItem + icon: + sprite: Interface/Actions/actions_vampire.rsi + state: cloakofdarkness + event: + !type:VampireCloakOfDarknessEvent + definitionName: CloakOfDarkness + useDelay: 10 \ No newline at end of file diff --git a/Resources/Prototypes/Chemistry/metabolizer_types.yml b/Resources/Prototypes/Chemistry/metabolizer_types.yml index 3f7bf05b35e..bc6502967b5 100644 --- a/Resources/Prototypes/Chemistry/metabolizer_types.yml +++ b/Resources/Prototypes/Chemistry/metabolizer_types.yml @@ -44,3 +44,7 @@ - type: metabolizerType id: Arachnid name: metabolizer-type-arachnid + +- type: metabolizerType + id: Vampire + name: metabolizer-type-vampire diff --git a/Resources/Prototypes/Chemistry/reactive_groups.yml b/Resources/Prototypes/Chemistry/reactive_groups.yml index fa001f3083d..d9773289708 100644 --- a/Resources/Prototypes/Chemistry/reactive_groups.yml +++ b/Resources/Prototypes/Chemistry/reactive_groups.yml @@ -6,3 +6,6 @@ - type: reactiveGroup id: Acidic + +- type: reactiveGroup + id: Unholy 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/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/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 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index f5e91e4fd86..6e805ea11c3 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,12 @@ damageOnUntrainedUse: types: Caustic: 50 + damageOnUnholyUse: ## What an unholy creature takes when picking up the bible + types: + Caustic: 50 + damageUnholy: ## What damage is dealt when a chaplain hits an unholy creature + types: + types: 20 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 3ed0ee53489..1ec71798ee5 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml @@ -508,6 +508,7 @@ max: 4 - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Coffin - type: Construction graph: CrateCoffin node: cratecoffin 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/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 4129c329773..27cd55f2980 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -117,5 +117,5 @@ id: ThiefObjectiveGroupEscape weights: EscapeThiefShuttleObjective: 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 diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml index 96739a50d3c..a732a3ad7c0 100644 --- a/Resources/Prototypes/Polymorphs/polymorph.yml +++ b/Resources/Prototypes/Polymorphs/polymorph.yml @@ -186,3 +186,11 @@ forced: true revertOnCrit: false revertOnDeath: false + +#Vampire +- type: polymorph + id: VampireBat + configuration: + entity: MobBat + revertOnDeath: true + revertOnCrit: true diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 105ae110487..03e5af7fe8f 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -1059,10 +1059,28 @@ factor: 3 Medicine: effects: + #If vampire + - !type:HealthChange + conditions: + - !type:OrganType + type: Vampire + damage: + types: + Heat: 2 + - !type:Emote + conditions: + - !type:OrganType + type: Vampire + emote: Scream + probability: 0.3 + #If not vampire - !type:HealthChange conditions: - !type:TotalDamage max: 50 + - !type:OrganType + type: Vampire + shouldHave: false damage: types: Blunt: -0.2 @@ -1071,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: 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 8e7534424ae..87ee2a4ff10 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -189,3 +189,12 @@ - type: MindRole antagPrototype: Changeling - type: ChangelingRole + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleVampire + name: Vampire Role + components: + - type: MindRole + antagPrototype: Vampire + - type: VampireRole 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/Prototypes/Store/currency.yml b/Resources/Prototypes/Store/currency.yml index b2e29f146ad..7e05f108371 100644 --- a/Resources/Prototypes/Store/currency.yml +++ b/Resources/Prototypes/Store/currency.yml @@ -10,6 +10,11 @@ displayName: store-currency-display-stolen-essence canWithdraw: false +- type: currency + id: BloodEssence + displayName: store-currency-display-blood-essence + canWithdraw: false + - type: currency id: WizCoin displayName: store-currency-display-wizcoin diff --git a/Resources/Prototypes/Vampire/vampire-powers.yml b/Resources/Prototypes/Vampire/vampire-powers.yml new file mode 100644 index 00000000000..17ff5bdb7aa --- /dev/null +++ b/Resources/Prototypes/Vampire/vampire-powers.yml @@ -0,0 +1,99 @@ +- type: vampirePower + id: SummonHeirloom + usableWhileCuffed: 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 + soundHit: /Audio/Weapons/slash.ogg + +- type: vampirePassive + id: SupernaturalStrength + catalogEntry: VampireUnnaturalStrength # Must match the catalog id + compsToAdd: + - type: MeleeWeapon + damage: + types: + Slash: 15 + animation: WeaponArcClaw + soundHit: /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/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 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 00000000000..4e01f86eae2 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/batform.png differ 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 00000000000..6819a99811c Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/bestia.png differ 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 00000000000..1d8df631083 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/bloodsteal.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/cloakofdarkness.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/cloakofdarkness.png new file mode 100644 index 00000000000..a45cec69736 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/cloakofdarkness.png differ 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 00000000000..aa34ab26c4d Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/dantalion.png differ 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 00000000000..d74908cd402 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/deathsembrace.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_extended.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_extended.png new file mode 100644 index 00000000000..6e93d2169bf Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_extended.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_retracted.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_retracted.png new file mode 100644 index 00000000000..84ef3097330 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fangs_retracted.png differ 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 00000000000..7cf40988b4a Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/fullpotential.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/gargantua.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/gargantua.png new file mode 100644 index 00000000000..87b87604a56 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/gargantua.png differ 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 00000000000..44245909936 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/glare.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png new file mode 100644 index 00000000000..402ced18a0c Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/hemomancer.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/hypnotise.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/hypnotise.png new file mode 100644 index 00000000000..8c5d431f5d2 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/hypnotise.png differ 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..ea1d753d5ff --- /dev/null +++ b/Resources/Textures/Interface/Actions/actions_vampire.rsi/meta.json @@ -0,0 +1,59 @@ +{ + "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": "fangs_retracted" + }, + { + "name": "fangs_extended" + }, + { + "name": "glare" + }, + { + "name": "screech" + }, + { + "name": "hypnotise" + }, + { + "name": "bloodsteal" + }, + { + "name": "batform" + }, + { + "name": "mouseform" + }, + { + "name": "summonheirloom" + }, + { + "name": "cloakofdarkness" + }, + { + "name": "unholystrength" + }, + { + "name": "supernaturalstrength" + }, + { + "name": "deathsembrace" + }, + { + "name": "mutation" + }, + { + "name": "stellarweakness" + }, + { + "name": "fullpotential" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/mouseform.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/mouseform.png new file mode 100644 index 00000000000..88fcf650119 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/mouseform.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/mutation.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/mutation.png new file mode 100644 index 00000000000..c063a59c355 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/mutation.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/screech.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/screech.png new file mode 100644 index 00000000000..d9ac7edba56 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/screech.png differ 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 00000000000..e9aaeb59cfd Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/stellarweakness.png differ 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 00000000000..824c3e96309 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/summonheirloom.png differ 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 00000000000..39963eb9ef9 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/supernaturalstrength.png differ 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 00000000000..15a1240b89d Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/umbrae.png differ diff --git a/Resources/Textures/Interface/Actions/actions_vampire.rsi/unholystrength.png b/Resources/Textures/Interface/Actions/actions_vampire.rsi/unholystrength.png new file mode 100644 index 00000000000..b549cb0ced2 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_vampire.rsi/unholystrength.png differ diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/Vampire.png b/Resources/Textures/Interface/Misc/job_icons.rsi/Vampire.png new file mode 100644 index 00000000000..dc0cd27fb34 Binary files /dev/null and b/Resources/Textures/Interface/Misc/job_icons.rsi/Vampire.png differ 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" } ] } 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