Новый антагонист: Вампиры (#560)

* Initial Commit

* Unfinished

* Continuing, more abilities

* Remove necromicon healing

* oops

* Continuing

* Continuing

* Checkpoint incase i need to undo something

* Rewrite to use event per power, working passives

* refactor fixes

* so many fixes

* some fixes

* some fixes

* new textures, status icon

* vampire antag role, mind role

* проверку на дауна вызывали?(если Null, у сервера ступор головного фатала)

* fix 1

* fix 2

* fix fatal, fix build

* breafing audio

* fix icons style, store in action

* clear store things, add gamerule, mid round game rule

* some rework

* clear any store systems

* clear x2

* if you have 150 Blood Essence, you get mutation

* upd

* some changes

* simple mutation menu

* я вот сижу, в 12 ночи, пишу какую-то очередную хуйню, вместо приятного сна...

* upd

* normal mutations menu

* actions translate

* None type, base actions list

* passive abilities

* ability earning

* fix

* translate vampire preset

* translate vampire mutations menu

* mutations menu textures

* disable some

* ДРАНЫЙ БРИФ ФАТАЛКУ ПО ПРИКОЛУ БЕЕМ

* game preset

* mutation can infinity changed

* fix

* fully rework abilities update system

* gargantua info

* update ignore list

* fix gargantua passives

* vampire objectives

* new icons

* balance

* fix

* upd objectives

* fix metabolizer type, balance 2

* fix metabolizer type 2

* fix syntax

* clear translation

* xd

* update

* round end info

* fix

* drain objective fix

* fix

* fix syntax

* fix

* clear

---------

Co-authored-by: Rainfey <rainfey0+github@gmail.com>
This commit is contained in:
Rinary 2024-10-31 18:06:01 +02:00 committed by GitHub
parent f927438ccd
commit a85433e877
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 3098 additions and 15 deletions

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'vampire-mutation-menu-ui-window-name'}"
MinSize="250 300"
SetSize="250 300">
<BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True">
<GridContainer Name="Grid" Columns="3" Margin="0 5" >
</GridContainer>
</ScrollContainer>
</BoxContainer>
</DefaultWindow>

View file

@ -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<VampireMutationsType>? OnIdSelected;
private HashSet<VampireMutationsType> _possibleMutations = new();
private VampireMutationsType _selectedId;
public VampireMutationMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entityManager.System<SpriteSystem>();
_resourceCache = IoCManager.Resolve<IResourceCache>();
}
public void UpdateState(HashSet<VampireMutationsType> 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();
}
}

View file

@ -165,5 +165,19 @@ public sealed partial class AdminVerbSystem
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<VampireRuleComponent>(targetPlayer, "Vampire");
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-vampire"),
};
args.Verbs.Add(vampire);
}
}

View file

@ -13,12 +13,16 @@ using Content.Shared.Inventory;
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 @@ namespace Content.Server.Bible
[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<BibleComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<BibleComponent, EntGotInsertedIntoContainerMessage>(OnInsertedContainer);
SubscribeLocalEvent<SummonableComponent, GetVerbsEvent<AlternativeVerb>>(AddSummonVerb);
SubscribeLocalEvent<SummonableComponent, GetItemActionsEvent>(GetSummonAction);
SubscribeLocalEvent<SummonableComponent, SummonActionEvent>(OnSummon);
@ -47,6 +53,20 @@ namespace Content.Server.Bible
SubscribeLocalEvent<FamiliarComponent, GhostRoleSpawnerUsedEvent>(OnSpawned);
}
private void OnInsertedContainer(EntityUid uid, BibleComponent component, EntGotInsertedIntoContainerMessage args)
{
//If an unholy creature picks up the bible, knock them down
if (HasComp<UnholyComponent>(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<EntityUid> _addQueue = new();
private readonly Queue<EntityUid> _remQueue = new();
@ -116,7 +136,21 @@ namespace Content.Server.Bible
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<UnholyComponent>(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<FamiliarComponent>(args.Target.Value))
{
if (_random.Prob(component.FailChance))

View file

@ -27,6 +27,14 @@ namespace Content.Server.Bible.Components
[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!;
/// <summary>
/// Chance the bible will fail to heal someone with no helmet
/// </summary>

View file

@ -2,6 +2,7 @@ using Content.Server.Body.Components;
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 @@ namespace Content.Server.Body.Systems
_solutionContainerSystem.UpdateChemicals(soln.Value);
}
public bool TryAddMetabolizerType(MetabolizerComponent component, string metabolizerType)
{
if (!_prototypeManager.HasIndex<MetabolizerTypePrototype>(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

View file

@ -3,6 +3,7 @@ using Content.Shared.Chemistry.EntitySystems;
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 @@ namespace Content.Server.Body.Systems
return true;
}
public void SetSpecialDigestible(StomachComponent component, EntityWhitelist? whitelist)
{
component.SpecialDigestible = whitelist;
}
}
}

View file

@ -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<EntityUid> VampireMinds = new();
public readonly List<ProtoId<EntityPrototype>> BaseObjectives = new()
{
"VampireKillRandomPersonObjective",
"VampireDrainObjective"
};
public readonly List<ProtoId<EntityPrototype>> EscapeObjectives = new()
{
"VampireSurviveObjective",
"VampireEscapeObjective"
};
public readonly List<ProtoId<EntityPrototype>> StealObjectives = new()
{
"CMOHyposprayVampireStealObjective",
"RDHardsuitVampireStealObjective",
"EnergyShotgunVampireStealObjective",
"MagbootsVampireStealObjective",
"ClipboardVampireStealObjective",
"CaptainIDVampireStealObjective",
"CaptainJetpackVampireStealObjective",
"CaptainGunVampireStealObjective"
};
}

View file

@ -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<VampireRuleComponent>
{
[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<AntagPrototype> VampirePrototypeId = "Vampire";
// public readonly ProtoId<NpcFactionPrototype> ChangelingFactionId = "Changeling";
// public readonly ProtoId<NpcFactionPrototype> NanotrasenFactionId = "NanoTrasen";
public readonly ProtoId<CurrencyPrototype> Currency = "BloodEssence";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VampireRuleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<VampireRuleComponent, AfterAntagEntitySelectedEvent>(OnSelectAntag);
//SubscribeLocalEvent<VampireRuleComponent, ObjectivesTextPrependEvent>(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<MetaDataComponent>(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<VampireRoleComponent>(mindId, out var vampireRole);
_role.MindHasRole<RoleBriefingComponent>(mindId, out var briefingComp);
if (vampireRole is not null && briefingComp is null)
{
AddComp<RoleBriefingComponent>(vampireRole.Value.Owner);
Comp<RoleBriefingComponent>(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<VampireComponent>(target);
var interfaceComponent = EnsureComp<UserInterfaceComponent>(target);
if (HasComp<UserInterfaceComponent>(target))
_uiSystem.SetUiState(target, VampireMutationUiKey.Key, new VampireMutationBoundUserInterfaceState(vampireComponent.VampireMutations, vampireComponent.CurrentMutation));
vampireComponent.Balance = new() { { VampireComponent.CurrencyProto, 0 } };
rule.VampireMinds.Add(mindId);
if (HasComp<VampireComponent>(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<VampireRuleComponent> 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<MetaDataComponent>(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<VampireComponent>())
{
if (!_mind.TryGetMind(vamp.Owner, out var mindId, out var mind))
continue;
if (!TryComp<MetaDataComponent>(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();
}
}

View file

@ -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;
}

View file

@ -0,0 +1,8 @@
using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent]
public sealed partial class VampireRoleComponent : BaseMindRoleComponent
{
}

View file

@ -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
/// <summary>
/// Listing(s) that if bought, block this purchase, if any.
/// </summary>
[DataField]
public HashSet<ProtoId<ListingPrototype>>? Blacklist;
public override bool Condition(ListingConditionArgs args)

View file

@ -161,8 +161,8 @@ public sealed partial class StoreSystem
{
return;
}
}
}
if (!IsOnStartingMap(uid, component))
component.RefundAllowed = false;
@ -180,7 +180,8 @@ public sealed partial class StoreSystem
RaiseLocalEvent(buyer, ref ev);
// Sunrise-End
}
}
//spawn entity
if (listing.ProductEntity != null)
{
@ -257,6 +258,7 @@ public sealed partial class StoreSystem
if (upgradeActionId != null)
HandleRefundComp(uid, component, upgradeActionId.Value);
}
if (listing.ProductEvent != null)
@ -267,6 +269,7 @@ public sealed partial class StoreSystem
RaiseLocalEvent(buyer, listing.ProductEvent);
}
//log dat shit.
_admin.Add(LogType.StorePurchase,
LogImpact.Low,

View file

@ -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<string, VampirePowerProtype> _powerCache = default!;
private FrozenDictionary<string, VampirePassiveProtype> _passiveCache = default!;
private void InitializePowers()
{
_powerCache = BuildPowerCache();
_passiveCache = BuildPassiveCache();
//Abilities
SubscribeLocalEvent<VampireComponent, VampireOpenMutationsMenu>(OnVampireOpenMutationsMenu);
SubscribeLocalEvent<VampireComponent, VampireToggleFangsEvent>(OnVampireToggleFangs);
SubscribeLocalEvent<VampireComponent, VampireGlareEvent>(OnVampireGlare);
SubscribeLocalEvent<VampireComponent, VampireScreechEvent>(OnVampireScreech);
SubscribeLocalEvent<VampireComponent, VampirePolymorphEvent>(OnVampirePolymorph);
SubscribeLocalEvent<VampireComponent, VampireHypnotiseEvent>(OnVampireHypnotise);
SubscribeLocalEvent<VampireComponent, VampireBloodStealEvent>(OnVampireBloodSteal);
SubscribeLocalEvent<VampireComponent, VampireCloakOfDarknessEvent>(OnVampireCloakOfDarkness);
//Hypnotise
SubscribeLocalEvent<VampireComponent, VampireHypnotiseDoAfterEvent>(HypnotiseDoAfter);
//Drink Blood
SubscribeLocalEvent<VampireComponent, BeforeInteractHandEvent>(OnInteractHandEvent);
SubscribeLocalEvent<VampireComponent, VampireDrinkBloodDoAfterEvent>(DrinkDoAfter);
//Deaths embrace
SubscribeLocalEvent<VampireDeathsEmbraceComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
SubscribeLocalEvent<VampireDeathsEmbraceComponent, EntGotRemovedFromContainerMessage>(OnRemovedFromContainer);
SubscribeLocalEvent<VampireDeathsEmbraceComponent, MobStateChangedEvent>(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<VampireComponent>(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<VampireComponent>(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<VampireComponent>(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<VampireComponent>(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<VampireComponent>(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<VampireComponent>(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<VampireComponent>(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<HumanoidAppearanceComponent>(args.Target))
return;
if (args.Target == uid)
return;
if (!TryGetPowerDefinition(VampireComponent.DrinkBloodPrototype, out var def))
return;
var vampire = new Entity<VampireComponent>(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<VampireComponent> vampire, VampirePowerProtype def)
{
if (!IsPowerUnlocked(vampire, def.ID))
return false;
//Block if we are cuffed
if (!def.UsableWhileCuffed && TryComp<CuffableComponent>(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<StunnedComponent>(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<ReplacementAccentComponent>(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<VampireComponent> vampire)
{
var damage = new DamageSpecifier();
damage.DamageDict.Add("Slash", 15);
var meleeComp = EnsureComp<MeleeWeaponComponent>(vampire);
meleeComp.Damage += damage;
}
private void SupernaturalStrength(Entity<VampireComponent> vampire)
{
var pryComp = EnsureComp<PryingComponent>(vampire);
pryComp.Force = true;
pryComp.PryPowered = true;
var damage = new DamageSpecifier();
damage.DamageDict.Add("Slash", 15);
var meleeComp = EnsureComp<MeleeWeaponComponent>(vampire);
meleeComp.Damage += damage;
}
#endregion
#region Other Powers
private void Screech(Entity<VampireComponent> vampire, TimeSpan? duration, DamageSpecifier? damage = null)
{
foreach (var entity in _entityLookup.GetEntitiesInRange(vampire, 3, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries))
{
if (HasComp<BibleUserComponent>(entity))
continue;
if (HasComp<VampireComponent>(entity))
continue;
if (HasComp<HumanoidAppearanceComponent>(entity))
{
_stun.TryParalyze(entity, duration ?? TimeSpan.FromSeconds(3), false);
_chat.TryEmoteWithoutChat(entity, _prototypeManager.Index<EmotePrototype>(VampireComponent.ScreamEmoteProto), true);
}
if (damage != null)
_damageableSystem.TryChangeDamage(entity, damage);
}
}
private void Glare(Entity<VampireComponent> vampire, EntityUid? target, TimeSpan? duration, DamageSpecifier? damage = null)
{
if (!target.HasValue)
return;
if (HasComp<VampireComponent>(target))
return;
if (HasComp<FlashImmunityComponent>(target))
return;
if (HasComp<BibleUserComponent>(target))
{
_stun.TryParalyze(vampire, duration ?? TimeSpan.FromSeconds(3), true);
_chat.TryEmoteWithoutChat(vampire.Owner, _prototypeManager.Index<EmotePrototype>(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<VampireComponent> vampire, string? polymorphTarget)
{
if (polymorphTarget == null)
return;
_polymorph.PolymorphEntity(vampire, polymorphTarget);
}
private void BloodSteal(Entity<VampireComponent> vampire)
{
var transform = Transform(vampire.Owner);
var targets = new HashSet<EntityUid>();
foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, 3, LookupFlags.Approximate | LookupFlags.Dynamic))
{
if (entity == vampire.Owner)
continue;
if (!HasComp<HumanoidAppearanceComponent>(entity))
continue;
if (_rotting.IsRotten(entity))
continue;
if (HasComp<BibleUserComponent>(entity))
continue;
if (!TryComp<BloodstreamComponent>(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<VampireComponent> vampire, float upkeep, float passiveVisibilityRate, float movementVisibilityRate)
{
if (HasComp<VampireSealthComponent>(vampire))
{
RemComp<StealthOnMoveComponent>(vampire);
RemComp<StealthComponent>(vampire);
RemComp<VampireSealthComponent>(vampire);
_popup.PopupEntity(Loc.GetString("vampire-cloak-disable"), vampire, vampire);
return false;
}
else
{
EnsureComp<StealthComponent>(vampire);
var stealthMovement = EnsureComp<StealthOnMoveComponent>(vampire);
stealthMovement.PassiveVisibilityRate = passiveVisibilityRate;
stealthMovement.MovementVisibilityRate = movementVisibilityRate;
var vampireStealth = EnsureComp<VampireSealthComponent>(vampire);
vampireStealth.Upkeep = upkeep;
_popup.PopupEntity(Loc.GetString("vampire-cloak-enable"), vampire, vampire);
return true;
}
}
#endregion
#region Hypnotise
private bool TryHypnotise(Entity<VampireComponent> 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<VampireComponent> vampire, ref VampireHypnotiseDoAfterEvent args)
{
if (!args.Target.HasValue)
return;
if (args.Cancelled)
return;
_statusEffects.TryAddStatusEffect<ForcedSleepingComponent>(args.Target.Value, VampireComponent.SleepStatusEffectProto, args.Duration ?? TimeSpan.FromSeconds(30), false);
}
#endregion
#region Deaths Embrace
/// <summary>
/// When the vampire dies, attempt to activate the Deaths Embrace power
/// </summary>
private void OnVampireStateChanged(EntityUid uid, VampireDeathsEmbraceComponent component, MobStateChangedEvent args)
{
if (args.OldMobState != MobState.Dead && args.NewMobState == MobState.Dead)
{
//Home still exists?
TryMoveToCoffin((uid, component));
}
}
/// <summary>
/// When the vampire is inserted into a container (ie locker, crate etc) check for a coffin, and bind their home to it
/// </summary>
private void OnInsertedIntoContainer(EntityUid uid, VampireDeathsEmbraceComponent component, EntGotInsertedIntoContainerMessage args)
{
if (HasComp<CoffinComponent>(args.Container.Owner))
{
component.HomeCoffin = args.Container.Owner;
var vhComp = EnsureComp<VampireHealingComponent>(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");
}
}
/// <summary>
/// When leaving a container, remove the healing component
/// </summary>
private void OnRemovedFromContainer(EntityUid uid, VampireDeathsEmbraceComponent component, EntGotRemovedFromContainerMessage args)
{
RemComp<VampireHealingComponent>(args.Entity);
}
/// <summary>
/// Attempt to move the vampire to their bound coffin
/// </summary>
private bool TryMoveToCoffin(Entity<VampireDeathsEmbraceComponent> 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<EntityStorageComponent>(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;
}
/// <summary>
/// Heal the vampire while they are in the coffin
/// Revive them if they are dead and below a ~150ish damage
/// </summary>
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<MobStateComponent>(vampire, out var mobStateComponent))
return;
if (!_mobState.IsDead(vampire, mobStateComponent))
return;
if (!_mobThreshold.TryGetThresholdForState(vampire, MobState.Dead, out var threshold))
return;
if (!TryComp<DamageableComponent>(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
/// <summary>
/// Toggle if fangs are extended
/// </summary>
private bool ToggleFangs(Entity<VampireComponent> vampire)
{
if (HasComp<VampireFangsExtendedComponent>(vampire))
{
RemComp<VampireFangsExtendedComponent>(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<VampireFangsExtendedComponent>(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;
}
}
/// <summary>
/// Check and start drinking blood from a humanoid
/// </summary>
private bool TryDrink(Entity<VampireComponent> vampire, EntityUid target, TimeSpan doAfterDelay)
{
//Do a precheck
if (!HasComp<VampireFangsExtendedComponent>(vampire))
return false;
if (!HasComp<TransformComponent>(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<VampireComponent> entity, ref VampireDrinkBloodDoAfterEvent args)
{
if (args.Cancelled)
return;
if (!HasComp<VampireFangsExtendedComponent>(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<BloodstreamComponent>(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<BloodDrainConditionComponent>(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<BibleUserComponent>(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;
}
}
/// <summary>
/// Attempt to insert the solution into the first stomach that has space available
/// </summary>
private bool TryIngestBlood(Entity<VampireComponent> vampire, Solution ingestedSolution, bool force = false)
{
//Get all stomaches
if (TryComp<BodyComponent>(vampire.Owner, out var body) && _body.TryGetBodyOrganEntityComps<StomachComponent>((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);
}
}*/
/// <summary>
/// Gets the Action EntityUid for a specific power
/// </summary>
private EntityUid? GetPowerEntity(VampireComponent vampire, string name)
{
if (!vampire.UnlockedPowers.TryGetValue(name, out var ability))
return null;
return ability;
}
/// <summary>
/// Cache all power prototypes in a dictionary by keyed by ID
/// </summary>
/// <returns></returns>
private FrozenDictionary<string, VampirePowerProtype> BuildPowerCache()
{
var protos = _prototypeManager.EnumeratePrototypes<VampirePowerProtype>();
return protos.ToFrozenDictionary(x => x.ID);
}
/// <summary>
/// Cache all passive prototypes in a dictionary by keyed by listing id
/// </summary>
/// <returns></returns>
private FrozenDictionary<string, VampirePassiveProtype> BuildPassiveCache()
{
var protos = _prototypeManager.EnumeratePrototypes<VampirePassiveProtype>();
return protos.ToFrozenDictionary(x => x.CatalogEntry);
}
}

View file

@ -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<BloodDrainConditionComponent, ObjectiveGetProgressEvent>(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;
}
}

View file

@ -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
{
/// <summary>
/// Convert the players into a vampire, all programatic because i dont want to replace the players body
/// </summary>
private void MakeVampire(EntityUid vampireUid)
{
var vampireComponent = EnsureComp<VampireComponent>(vampireUid);
var vampire = new Entity<VampireComponent>(vampireUid, vampireComponent);
//Render them unable to rot, immune to pressure and thirst
RemComp<PerishableComponent>(vampire);
RemComp<BarotraumaComponent>(vampire);
RemComp<ThirstComponent>(vampire); //Unsure, should vampires thirst.. or hunger?
//Render immune to cold, but not heat
if (TryComp<TemperatureComponent>(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);
}
/// <summary>
/// Add vulnerability to holy water when ingested or slashed, and take damage from the bible
/// </summary>
private void MakeVulnerableToHoly(Entity<VampireComponent> vampire)
{
//React to being beaten with the bible
EnsureComp<UnholyComponent>(vampire);
//Take damage from holy water splash
if (TryComp<ReactiveComponent>(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<BodyComponent>(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<MetabolizerComponent>(organ.Id, out var metabolizer))
{
if (TryComp<StomachComponent>(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<VampireComponent>(vampire, out var comp))
return;
foreach (var actionId in comp.BaseVampireActions)
{
var action = _action.AddAction(vampire, actionId);
if (!action.HasValue)
return;
if (!TryComp<InstantActionComponent>(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<BodyComponent>(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<MetabolizerComponent>(organ.Id, out var metabolizer))
{
_metabolism.TryRemoveMetabolizerType(metabolizer, VampireComponent.MetabolizerVampire);
}
}
if (TryComp<ReactiveComponent>(vampire, out var reactive))
{
if (reactive.ReactiveGroups == null)
return;
reactive.ReactiveGroups.Remove("Unholy");
}
RemComp<UnholyComponent>(vampire);
}
}

View file

@ -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<string, EntityUid> _actionEntities = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VampireComponent, ComponentStartup>(OnComponentStartup);
//SubscribeLocalEvent<VampireComponent, VampireSelfPowerEvent>(OnUseSelfPower);
//SubscribeLocalEvent<VampireComponent, VampireTargetedPowerEvent>(OnUseTargetedPower);
SubscribeLocalEvent<VampireComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<VampireComponent, VampireBloodChangedEvent>(OnVampireBloodChangedEvent);
SubscribeLocalEvent<VampireComponent, ComponentGetState>(GetState);
SubscribeLocalEvent<VampireComponent, VampireMutationPrototypeSelectedMessage>(OnMutationSelected);
InitializePowers();
InitializeObjectives();
}
/// <summary>
/// Handles healing and damaging in space
/// </summary>
public override void Update(float frameTime)
{
base.Update(frameTime);
var stealthQuery = EntityQueryEnumerator<VampireComponent, VampireSealthComponent>();
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<VampireSealthComponent>(uid);
}
stealth.NextStealthTick -= frameTime;
}
var healingQuery = EntityQueryEnumerator<VampireComponent, VampireHealingComponent>();
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<VampireComponent>();
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<VampireFangsExtendedComponent>(uid) && args.IsInDetailsRange && !_food.IsMouthBlocked(uid))
args.AddMarkup($"{Loc.GetString("vampire-fangs-extended-examine")}{Environment.NewLine}");
}
private bool AddBloodEssence(Entity<VampireComponent> 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<VampireComponent> 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;
}
/// <summary>
/// Use the charges display on SummonHeirloom to show the remaining blood essence
/// </summary>
/// <param name="vampire"></param>
public void UpdateBloodDisplay(EntityUid vampire)
{
if (!TryComp<VampireComponent>(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<VampireComponent>(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<VampireComponent>(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<VampireComponent>(vampire, out var comp))
return 0;
if (!comp.Balance.TryGetValue(VampireComponent.CurrencyProto, out var val))
return 0;
return val;
}
private void DoSpaceDamage(Entity<VampireComponent> 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<PrayableComponent>(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<VampireComponent>(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);
}
}

View file

@ -0,0 +1,3 @@
namespace Content.Shared.Store.Events;
public record struct StorePurchasedListingEvent(EntityUid Purchaser, ListingData Listing, EntityUid? Item, EntityUid? Action);

View file

@ -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;

View file

@ -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<StatusEffectPrototype>]
public static readonly string SleepStatusEffectProto = "ForcedSleep";
[ValidatePrototypeId<EmotePrototype>]
public static readonly string ScreamEmoteProto = "Scream";
[ValidatePrototypeId<CurrencyPrototype>]
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<VampireMutationsType> VampireMutations = new()
{
VampireMutationsType.None,
VampireMutationsType.Hemomancer,
VampireMutationsType.Umbrae,
VampireMutationsType.Gargantua,
//VampireMutationsType.Dantalion,
VampireMutationsType.Bestia
};
public static readonly EntityWhitelist AcceptableFoods = new()
{
Tags = new() { "Pill" }
};
[ValidatePrototypeId<MetabolizerTypePrototype>]
public static readonly string MetabolizerVampire = "Vampire";
[ValidatePrototypeId<MetabolizerTypePrototype>]
public static readonly string MetabolizerBloodsucker = "Bloodsucker";
public static readonly DamageSpecifier MeleeDamage = new()
{
DamageDict = new Dictionary<string, FixedPoint2>() { { "Slash", 10 } }
};
public static readonly DamageSpecifier HolyDamage = new()
{
DamageDict = new Dictionary<string, FixedPoint2>() { { "Burn", 10 } }
};
public static readonly DamageSpecifier SpaceDamage = new()
{
DamageDict = new Dictionary<string, FixedPoint2>() { { "Burn", 2.5 } }
};
[ValidatePrototypeId<EntityPrototype>]
public static readonly string MutationsActionPrototype = "ActionVampireOpenMutationsMenu";
[ViewVariables(VVAccess.ReadWrite)]
public EntityUid? MutationsAction;
public readonly List<ProtoId<EntityPrototype>> BaseVampireActions = new()
{
"ActionVampireToggleFangs",
"ActionVampireHypnotise"
};
[ValidatePrototypeId<VampirePowerProtype>]
public static readonly string DrinkBloodPrototype = "DrinkBlood";
/// <summary>
/// Total blood drank, counter for end of round screen
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float TotalBloodDrank = 0;
/// <summary>
/// How much blood per mouthful
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float MouthVolume = 5;
/// <summary>
/// All unlocked abilities
/// </summary>
public Dictionary<string, EntityUid?> UnlockedPowers = new();
/// <summary>
/// Current available balance, used to sync currency across heirlooms and add essence as we feed
/// </summary>
public Dictionary<ProtoId<CurrencyPrototype>, 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");
}
/// <summary>
/// Contains all details about the ability and its effects or restrictions
/// </summary>
[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();
}
/// <summary>
/// Marks an entity as taking damage when hit by a bible, rather than being healed
/// </summary>
[RegisterComponent]
public sealed partial class UnholyComponent : Component { }
/// <summary>
/// Marks a container as a coffin, for the purposes of vampire healing
/// </summary>
[RegisterComponent]
public sealed partial class CoffinComponent : Component { }
[RegisterComponent]
public sealed partial class VampireFangsExtendedComponent : Component { }
/// <summary>
/// When added, heals the entity by the specified amount
/// </summary>
[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<VampireMutationsType> MutationList;
public readonly VampireMutationsType SelectedMutation;
public VampireMutationBoundUserInterfaceState(HashSet<VampireMutationsType> 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
}*/

View file

@ -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<VampirePowerProtype>))]
public string DefinitionName = default!;
};
public abstract partial class VampireTargetedPowerEvent : EntityTargetActionEvent
{
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<VampirePowerProtype>))]
public string DefinitionName = default!;
};
public sealed partial class VampirePassiveActionEvent : BaseActionEvent
{
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<VampirePowerProtype>))]
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;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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.

View file

@ -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!

View file

@ -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 секунд

View file

@ -0,0 +1,4 @@
ent-VampireSurviveObjective = Выжить
.desc = Я должен выжить, чего бы этого мне не стоило.
ent-VampireEscapeObjective = Улететь со станции живым и свободным.
.desc = Я должен улететь на эвакуационном шаттле. Свободным.

View file

@ -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 } крови. Это необходимо для моего выживания и дальнейшей эволюции.

View file

@ -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 = Сделать вампиром

View file

@ -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 = Вампир

View file

@ -9,3 +9,4 @@ metabolizer-type-plant = Растение
metabolizer-type-dwarf = Дварф
metabolizer-type-moth = Ниан
metabolizer-type-arachnid = Арахнид
metabolizer-type-vampire = Вампир

View file

@ -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

View file

@ -44,3 +44,7 @@
- type: metabolizerType
id: Arachnid
name: metabolizer-type-arachnid
- type: metabolizerType
id: Vampire
name: metabolizer-type-vampire

View file

@ -6,3 +6,6 @@
- type: reactiveGroup
id: Acidic
- type: reactiveGroup
id: Unholy

View file

@ -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

View file

@ -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

View file

@ -195,6 +195,8 @@
type: StrippableBoundUserInterface
enum.StoreUiKey.Key:
type: StoreBoundUserInterface
enum.VampireMutationUiKey.Key:
type: VampireMutationBoundUserInterface
- type: Puller
- type: Speech
speechSounds: Alto

View file

@ -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"

View file

@ -508,6 +508,7 @@
max: 4
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Coffin
- type: Construction
graph: CrateCoffin
node: cratecoffin

View file

@ -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 ]

View file

@ -117,5 +117,5 @@
id: ThiefObjectiveGroupEscape
weights:
EscapeThiefShuttleObjective: 1
#Crew, wizard, when you code it...

View file

@ -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

View file

@ -186,3 +186,11 @@
forced: true
revertOnCrit: false
revertOnDeath: false
#Vampire
- type: polymorph
id: VampireBat
configuration:
entity: MobBat
revertOnDeath: true
revertOnCrit: true

View file

@ -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:

View file

@ -0,0 +1,7 @@
- type: antag
id: Vampire
name: roles-antag-vamire-name
antagonist: true
setPreference: true
objective: roles-antag-vampire-description
# guides: [ Changelings ]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

View file

@ -205,6 +205,9 @@
},
{
"name": "Changeling"
},
{
"name": "Vampire"
}
]
}

View file

@ -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
- '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'