From 6c8829787f262080497df238af0e8409571d2a52 Mon Sep 17 00:00:00 2001
From: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Date: Fri, 20 Dec 2024 00:38:24 -0600
Subject: [PATCH 01/90] initial
base bloodsucker functionality
---
beestation.dme | 49 ++
code/__DEFINES/antagonists.dm | 1 +
code/__DEFINES/atom_hud.dm | 1 +
code/__DEFINES/bloodsuckers.dm | 176 ++++++
code/__DEFINES/language.dm | 3 +-
code/__DEFINES/role_preferences.dm | 2 +
code/_onclick/hud/bloodsucker.dm | 89 +++
code/_onclick/hud/hud.dm | 4 +
code/controllers/subsystem/sunlight.dm | 89 +++
code/datums/brain_damage/special.dm | 163 +++++
.../crafting/crafting_lists/structures.dm | 85 +++
.../crafting_lists/weaponry/weapons.dm | 34 ++
code/datums/hud.dm | 1 +
.../dynamic/dynamic_rulesets_latejoin.dm | 33 +
.../dynamic/dynamic_rulesets_midround.dm | 42 ++
.../dynamic/dynamic_rulesets_roundstart.dm | 42 ++
.../items/implants/implant_mindshield.dm | 9 +
code/game/objects/items/melee/misc.dm | 94 +++
.../items/stacks/sheets/organic/wood.dm | 26 +
.../objects/structures/bloodsucker_crypt.dm | 576 ++++++++++++++++++
.../structures/crates_lockers/crates.dm | 20 +-
.../crates_lockers/crates/coffins.dm | 271 ++++++++
.../antagonists/bloodsucker/clans/_clan.dm | 260 ++++++++
.../bloodsucker/clans/assignclan.dm | 47 ++
.../bloodsucker/clans/clan_flavortext.dm | 48 ++
.../bloodsucker/clans/malkavian.dm | 72 +++
.../bloodsucker/clans/nosferatu.dm | 45 ++
.../antagonists/bloodsucker/clans/tremere.dm | 82 +++
.../antagonists/bloodsucker/clans/venture.dm | 119 ++++
.../bloodsucker/conversion_bloodsucker.dm | 121 ++++
.../bloodsucker/datum_bloodsucker.dm | 505 +++++++++++++++
.../bloodsucker/daylight_bloodsucker.dm | 171 ++++++
.../bloodsucker/frenzy_bloodsucker.dm | 97 +++
.../bloodsucker/life_bloodsucker.dm | 302 +++++++++
.../bloodsucker/misc_procs_bloodsucker.dm | 172 ++++++
.../bloodsucker/moodlets_bloodsucker.dm | 50 ++
.../bloodsucker/names_bloodsucker.dm | 135 ++++
.../bloodsucker/objectives_bloodsucker.dm | 329 ++++++++++
.../antagonists/bloodsucker/powers/_power.dm | 216 +++++++
.../antagonists/bloodsucker/powers/cloak.dm | 69 +++
.../antagonists/bloodsucker/powers/feed.dm | 249 ++++++++
.../bloodsucker/powers/fortitude.dm | 75 +++
.../antagonists/bloodsucker/powers/gohome.dm | 133 ++++
.../bloodsucker/powers/masquerade.dm | 97 +++
.../bloodsucker/powers/targeted/_targeted.dm | 105 ++++
.../bloodsucker/powers/targeted/brawn.dm | 188 ++++++
.../bloodsucker/powers/targeted/haste.dm | 89 +++
.../bloodsucker/powers/targeted/lunge.dm | 169 +++++
.../bloodsucker/powers/targeted/mesmerize.dm | 140 +++++
.../bloodsucker/powers/targeted/trespass.dm | 106 ++++
.../bloodsucker/powers/tremere/auspex.dm | 118 ++++
.../bloodsucker/powers/tremere/dominate.dm | 193 ++++++
.../powers/tremere/thaumaturgey.dm | 186 ++++++
.../powers/tremere/tremere_base.dm | 28 +
.../bloodsucker/powers/vassal/distress.dm | 22 +
.../bloodsucker/powers/vassal/recuperate.dm | 64 ++
.../bloodsucker/powers/vassal/vassal_fold.dm | 111 ++++
.../antagonists/bloodsucker/powers/veil.dm | 139 +++++
.../bloodsucker/soulstone_bloodsucker.dm | 24 +
.../bloodsucker/vassals/datum_vassal.dm | 152 +++++
.../bloodsucker/vassals/ex_vassal.dm | 82 +++
.../bloodsucker/vassals/favorite_vassal.dm | 23 +
.../bloodsucker/vassals/misc_procs_vassal.dm | 72 +++
.../bloodsucker/vassals/pinpointer_vassal.dm | 31 +
.../bloodsucker/vassals/revenge_vassal.dm | 83 +++
.../role_preference/role_antagonists.dm | 16 +
code/modules/language/language_holder.dm | 6 +
code/modules/language/vampiric.dm | 21 +
code/modules/library/lib_codex_gigas.dm | 94 +++
code/modules/mob/living/carbon/human/death.dm | 2 +-
code/modules/mob/living/living.dm | 24 +
.../reagents/reagent_containers/blood_pack.dm | 46 ++
icons/bloodsuckers/512x512.dmi | Bin 0 -> 460 bytes
icons/bloodsuckers/actions_bloodsucker.dmi | Bin 0 -> 23927 bytes
.../actions_tremere_bloodsucker.dmi | Bin 0 -> 5371 bytes
icons/bloodsuckers/blood_fountain.dmi | Bin 0 -> 1498 bytes
icons/bloodsuckers/bloodsucker_icons.dmi | Bin 0 -> 1342 bytes
icons/bloodsuckers/bs_leftinhand.dmi | Bin 0 -> 6812 bytes
icons/bloodsuckers/bs_rightinhand.dmi | Bin 0 -> 6900 bytes
icons/bloodsuckers/clan_icons.dmi | Bin 0 -> 16483 bytes
icons/bloodsuckers/claw.dmi | Bin 0 -> 2194 bytes
icons/bloodsuckers/rabbit.dmi | Bin 0 -> 1631 bytes
icons/bloodsuckers/red_rabbit.dmi | Bin 0 -> 2843 bytes
icons/bloodsuckers/stakes.dmi | Bin 0 -> 866 bytes
icons/bloodsuckers/timestop_guardian.dmi | Bin 0 -> 318 bytes
icons/bloodsuckers/vamp_obj.dmi | Bin 0 -> 10656 bytes
icons/bloodsuckers/vamp_obj_64.dmi | Bin 0 -> 1109 bytes
icons/bloodsuckers/vampiric.dmi | Bin 0 -> 722 bytes
icons/bloodsuckers/weapons.dmi | Bin 0 -> 7886 bytes
icons/bloodsuckers/weapons_lefthand.dmi | Bin 0 -> 4731 bytes
icons/bloodsuckers/weapons_righthand.dmi | Bin 0 -> 5516 bytes
icons/bloodsuckers/worn_mask.dmi | Bin 0 -> 1095 bytes
icons/effects/phobetor_tear.dmi | Bin 0 -> 1927 bytes
icons/hud/radials/radial_generic.dmi | Bin 22916 -> 24593 bytes
sound/bloodsuckers/BloodsuckerAlert.ogg | Bin 0 -> 213040 bytes
sound/bloodsuckers/blood_vial_slurp.ogg | Bin 0 -> 126695 bytes
sound/bloodsuckers/coffin_close.ogg | Bin 0 -> 55584 bytes
sound/bloodsuckers/coffin_open.ogg | Bin 0 -> 66698 bytes
sound/bloodsuckers/griffin_1.ogg | Bin 0 -> 1964 bytes
sound/bloodsuckers/griffin_10.ogg | Bin 0 -> 5235 bytes
sound/bloodsuckers/griffin_2.ogg | Bin 0 -> 2224 bytes
sound/bloodsuckers/griffin_3.ogg | Bin 0 -> 2828 bytes
sound/bloodsuckers/griffin_4.ogg | Bin 0 -> 3171 bytes
sound/bloodsuckers/griffin_5.ogg | Bin 0 -> 3413 bytes
sound/bloodsuckers/griffin_6.ogg | Bin 0 -> 3830 bytes
sound/bloodsuckers/griffin_7.ogg | Bin 0 -> 4255 bytes
sound/bloodsuckers/griffin_8.ogg | Bin 0 -> 4569 bytes
sound/bloodsuckers/griffin_9.ogg | Bin 0 -> 4881 bytes
sound/bloodsuckers/jackinthebomb.ogg | Bin 0 -> 216230 bytes
sound/bloodsuckers/lunge_warn.ogg | Bin 0 -> 68328 bytes
sound/bloodsuckers/monsterhunterintro.ogg | Bin 0 -> 295376 bytes
sound/bloodsuckers/moonlightbeam.ogg | Bin 0 -> 84253 bytes
sound/bloodsuckers/moonlightsword.ogg | Bin 0 -> 39380 bytes
sound/bloodsuckers/owl_1.ogg | Bin 0 -> 2049 bytes
sound/bloodsuckers/owl_10.oga | Bin 0 -> 7811 bytes
sound/bloodsuckers/owl_2.ogg | Bin 0 -> 2495 bytes
sound/bloodsuckers/owl_3.ogg | Bin 0 -> 2933 bytes
sound/bloodsuckers/owl_5.ogg | Bin 0 -> 3678 bytes
sound/bloodsuckers/owl_6.ogg | Bin 0 -> 4114 bytes
sound/bloodsuckers/owl_7.ogg | Bin 0 -> 4475 bytes
sound/bloodsuckers/owl_8.ogg | Bin 0 -> 4924 bytes
sound/bloodsuckers/owl_9.ogg | Bin 0 -> 5248 bytes
sound/bloodsuckers/paradoxskip.ogg | Bin 0 -> 34772 bytes
sound/bloodsuckers/rabbitlocator.ogg | Bin 0 -> 122575 bytes
sound/bloodsuckers/weaponsmithing.ogg | Bin 0 -> 108986 bytes
sound/bloodsuckers/wonderlandmusic.ogg | Bin 0 -> 381457 bytes
sound/misc/ghosty_wind.ogg | Bin 0 -> 154574 bytes
.../tgui/interfaces/AntagInfoBloodsucker.tsx | 241 ++++++++
.../interfaces/AntagInfoRevengeVassal.tsx | 133 ++++
tgui/packages/tgui/interfaces/KindredBook.tsx | 41 ++
130 files changed, 7934 insertions(+), 19 deletions(-)
create mode 100644 code/__DEFINES/bloodsuckers.dm
create mode 100644 code/_onclick/hud/bloodsucker.dm
create mode 100644 code/controllers/subsystem/sunlight.dm
create mode 100644 code/game/objects/structures/bloodsucker_crypt.dm
create mode 100644 code/game/objects/structures/crates_lockers/crates/coffins.dm
create mode 100644 code/modules/antagonists/bloodsucker/clans/_clan.dm
create mode 100644 code/modules/antagonists/bloodsucker/clans/assignclan.dm
create mode 100644 code/modules/antagonists/bloodsucker/clans/clan_flavortext.dm
create mode 100644 code/modules/antagonists/bloodsucker/clans/malkavian.dm
create mode 100644 code/modules/antagonists/bloodsucker/clans/nosferatu.dm
create mode 100644 code/modules/antagonists/bloodsucker/clans/tremere.dm
create mode 100644 code/modules/antagonists/bloodsucker/clans/venture.dm
create mode 100644 code/modules/antagonists/bloodsucker/conversion_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/datum_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/daylight_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/frenzy_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/life_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/misc_procs_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/moodlets_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/names_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/objectives_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/_power.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/cloak.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/feed.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/fortitude.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/gohome.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/masquerade.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/targeted/_targeted.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/targeted/brawn.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/targeted/haste.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/targeted/lunge.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/targeted/trespass.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgey.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/tremere/tremere_base.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/vassal/distress.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/vassal/vassal_fold.dm
create mode 100644 code/modules/antagonists/bloodsucker/powers/veil.dm
create mode 100644 code/modules/antagonists/bloodsucker/soulstone_bloodsucker.dm
create mode 100644 code/modules/antagonists/bloodsucker/vassals/datum_vassal.dm
create mode 100644 code/modules/antagonists/bloodsucker/vassals/ex_vassal.dm
create mode 100644 code/modules/antagonists/bloodsucker/vassals/favorite_vassal.dm
create mode 100644 code/modules/antagonists/bloodsucker/vassals/misc_procs_vassal.dm
create mode 100644 code/modules/antagonists/bloodsucker/vassals/pinpointer_vassal.dm
create mode 100644 code/modules/antagonists/bloodsucker/vassals/revenge_vassal.dm
create mode 100644 code/modules/language/vampiric.dm
create mode 100644 icons/bloodsuckers/512x512.dmi
create mode 100644 icons/bloodsuckers/actions_bloodsucker.dmi
create mode 100644 icons/bloodsuckers/actions_tremere_bloodsucker.dmi
create mode 100644 icons/bloodsuckers/blood_fountain.dmi
create mode 100644 icons/bloodsuckers/bloodsucker_icons.dmi
create mode 100644 icons/bloodsuckers/bs_leftinhand.dmi
create mode 100644 icons/bloodsuckers/bs_rightinhand.dmi
create mode 100644 icons/bloodsuckers/clan_icons.dmi
create mode 100644 icons/bloodsuckers/claw.dmi
create mode 100644 icons/bloodsuckers/rabbit.dmi
create mode 100644 icons/bloodsuckers/red_rabbit.dmi
create mode 100644 icons/bloodsuckers/stakes.dmi
create mode 100644 icons/bloodsuckers/timestop_guardian.dmi
create mode 100644 icons/bloodsuckers/vamp_obj.dmi
create mode 100644 icons/bloodsuckers/vamp_obj_64.dmi
create mode 100644 icons/bloodsuckers/vampiric.dmi
create mode 100644 icons/bloodsuckers/weapons.dmi
create mode 100644 icons/bloodsuckers/weapons_lefthand.dmi
create mode 100644 icons/bloodsuckers/weapons_righthand.dmi
create mode 100644 icons/bloodsuckers/worn_mask.dmi
create mode 100644 icons/effects/phobetor_tear.dmi
create mode 100644 sound/bloodsuckers/BloodsuckerAlert.ogg
create mode 100644 sound/bloodsuckers/blood_vial_slurp.ogg
create mode 100644 sound/bloodsuckers/coffin_close.ogg
create mode 100644 sound/bloodsuckers/coffin_open.ogg
create mode 100644 sound/bloodsuckers/griffin_1.ogg
create mode 100644 sound/bloodsuckers/griffin_10.ogg
create mode 100644 sound/bloodsuckers/griffin_2.ogg
create mode 100644 sound/bloodsuckers/griffin_3.ogg
create mode 100644 sound/bloodsuckers/griffin_4.ogg
create mode 100644 sound/bloodsuckers/griffin_5.ogg
create mode 100644 sound/bloodsuckers/griffin_6.ogg
create mode 100644 sound/bloodsuckers/griffin_7.ogg
create mode 100644 sound/bloodsuckers/griffin_8.ogg
create mode 100644 sound/bloodsuckers/griffin_9.ogg
create mode 100644 sound/bloodsuckers/jackinthebomb.ogg
create mode 100644 sound/bloodsuckers/lunge_warn.ogg
create mode 100644 sound/bloodsuckers/monsterhunterintro.ogg
create mode 100644 sound/bloodsuckers/moonlightbeam.ogg
create mode 100644 sound/bloodsuckers/moonlightsword.ogg
create mode 100644 sound/bloodsuckers/owl_1.ogg
create mode 100644 sound/bloodsuckers/owl_10.oga
create mode 100644 sound/bloodsuckers/owl_2.ogg
create mode 100644 sound/bloodsuckers/owl_3.ogg
create mode 100644 sound/bloodsuckers/owl_5.ogg
create mode 100644 sound/bloodsuckers/owl_6.ogg
create mode 100644 sound/bloodsuckers/owl_7.ogg
create mode 100644 sound/bloodsuckers/owl_8.ogg
create mode 100644 sound/bloodsuckers/owl_9.ogg
create mode 100644 sound/bloodsuckers/paradoxskip.ogg
create mode 100644 sound/bloodsuckers/rabbitlocator.ogg
create mode 100644 sound/bloodsuckers/weaponsmithing.ogg
create mode 100644 sound/bloodsuckers/wonderlandmusic.ogg
create mode 100644 sound/misc/ghosty_wind.ogg
create mode 100644 tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx
create mode 100644 tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx
create mode 100644 tgui/packages/tgui/interfaces/KindredBook.tsx
diff --git a/beestation.dme b/beestation.dme
index 679f57a38f157..c2e76f7ea3263 100644
--- a/beestation.dme
+++ b/beestation.dme
@@ -51,6 +51,7 @@
#include "code\__DEFINES\basic_mobs.dm"
#include "code\__DEFINES\bitfields.dm"
#include "code\__DEFINES\blood.dm"
+#include "code\__DEFINES\bloodsuckers.dm"
#include "code\__DEFINES\bodyparts.dm"
#include "code\__DEFINES\bot_defines.dm"
#include "code\__DEFINES\callbacks.dm"
@@ -372,6 +373,7 @@
#include "code\_onclick\hud\alien_larva.dm"
#include "code\_onclick\hud\blob_overmind.dm"
#include "code\_onclick\hud\blobbernauthud.dm"
+#include "code\_onclick\hud\bloodsucker.dm"
#include "code\_onclick\hud\constructs.dm"
#include "code\_onclick\hud\credits.dm"
#include "code\_onclick\hud\drones.dm"
@@ -489,6 +491,7 @@
#include "code\controllers\subsystem\stat.dm"
#include "code\controllers\subsystem\stickyban.dm"
#include "code\controllers\subsystem\sun.dm"
+#include "code\controllers\subsystem\sunlight.dm"
#include "code\controllers\subsystem\supply.dm"
#include "code\controllers\subsystem\tgui.dm"
#include "code\controllers\subsystem\throwing.dm"
@@ -1621,6 +1624,7 @@
#include "code\game\objects\structures\artstuff.dm"
#include "code\game\objects\structures\barsigns.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
+#include "code\game\objects\structures\bloodsucker_crypt.dm"
#include "code\game\objects\structures\bot_elevator.dm"
#include "code\game\objects\structures\catwalk.dm"
#include "code\game\objects\structures\crateshelf.dm"
@@ -1712,6 +1716,7 @@
#include "code\game\objects\structures\crates_lockers\closets\secure\secure_closets.dm"
#include "code\game\objects\structures\crates_lockers\closets\secure\security.dm"
#include "code\game\objects\structures\crates_lockers\crates\bins.dm"
+#include "code\game\objects\structures\crates_lockers\crates\coffins.dm"
#include "code\game\objects\structures\crates_lockers\crates\critter.dm"
#include "code\game\objects\structures\crates_lockers\crates\large.dm"
#include "code\game\objects\structures\crates_lockers\crates\secure.dm"
@@ -1953,6 +1958,49 @@
#include "code\modules\antagonists\blob\structures\resource.dm"
#include "code\modules\antagonists\blob\structures\shield.dm"
#include "code\modules\antagonists\blood_contract\blood_contract.dm"
+#include "code\modules\antagonists\bloodsucker\conversion_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\datum_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\daylight_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\frenzy_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\life_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\misc_procs_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\moodlets_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\names_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\objectives_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\soulstone_bloodsucker.dm"
+#include "code\modules\antagonists\bloodsucker\clans\_clan.dm"
+#include "code\modules\antagonists\bloodsucker\clans\assignclan.dm"
+#include "code\modules\antagonists\bloodsucker\clans\clan_flavortext.dm"
+#include "code\modules\antagonists\bloodsucker\clans\malkavian.dm"
+#include "code\modules\antagonists\bloodsucker\clans\nosferatu.dm"
+#include "code\modules\antagonists\bloodsucker\clans\tremere.dm"
+#include "code\modules\antagonists\bloodsucker\clans\venture.dm"
+#include "code\modules\antagonists\bloodsucker\powers\_power.dm"
+#include "code\modules\antagonists\bloodsucker\powers\cloak.dm"
+#include "code\modules\antagonists\bloodsucker\powers\feed.dm"
+#include "code\modules\antagonists\bloodsucker\powers\fortitude.dm"
+#include "code\modules\antagonists\bloodsucker\powers\gohome.dm"
+#include "code\modules\antagonists\bloodsucker\powers\masquerade.dm"
+#include "code\modules\antagonists\bloodsucker\powers\veil.dm"
+#include "code\modules\antagonists\bloodsucker\powers\targeted\_targeted.dm"
+#include "code\modules\antagonists\bloodsucker\powers\targeted\brawn.dm"
+#include "code\modules\antagonists\bloodsucker\powers\targeted\haste.dm"
+#include "code\modules\antagonists\bloodsucker\powers\targeted\lunge.dm"
+#include "code\modules\antagonists\bloodsucker\powers\targeted\mesmerize.dm"
+#include "code\modules\antagonists\bloodsucker\powers\targeted\trespass.dm"
+#include "code\modules\antagonists\bloodsucker\powers\tremere\auspex.dm"
+#include "code\modules\antagonists\bloodsucker\powers\tremere\dominate.dm"
+#include "code\modules\antagonists\bloodsucker\powers\tremere\thaumaturgey.dm"
+#include "code\modules\antagonists\bloodsucker\powers\tremere\tremere_base.dm"
+#include "code\modules\antagonists\bloodsucker\powers\vassal\distress.dm"
+#include "code\modules\antagonists\bloodsucker\powers\vassal\recuperate.dm"
+#include "code\modules\antagonists\bloodsucker\powers\vassal\vassal_fold.dm"
+#include "code\modules\antagonists\bloodsucker\vassals\datum_vassal.dm"
+#include "code\modules\antagonists\bloodsucker\vassals\ex_vassal.dm"
+#include "code\modules\antagonists\bloodsucker\vassals\favorite_vassal.dm"
+#include "code\modules\antagonists\bloodsucker\vassals\misc_procs_vassal.dm"
+#include "code\modules\antagonists\bloodsucker\vassals\pinpointer_vassal.dm"
+#include "code\modules\antagonists\bloodsucker\vassals\revenge_vassal.dm"
#include "code\modules\antagonists\brainwashing\brainwashing.dm"
#include "code\modules\antagonists\brother\brother.dm"
#include "code\modules\antagonists\changeling\cellular_emporium.dm"
@@ -2901,6 +2949,7 @@
#include "code\modules\language\sylvan.dm"
#include "code\modules\language\terrum.dm"
#include "code\modules\language\uncommon.dm"
+#include "code\modules\language\vampiric.dm"
#include "code\modules\language\voltaic.dm"
#include "code\modules\language\xenocommon.dm"
#include "code\modules\library\lib_codex_gigas.dm"
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 46c5b43e3f1ea..e5aab4d8e4701 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -82,6 +82,7 @@
#define FACTION_BLOB "Blob"
#define FACTION_ALIEN "Xenomorph"
#define FACTION_WIZARD "Wizard"
+#define FACTION_BLOODSUCKER "Bloodsucker"
// Heretic path defines.
#define HERETIC_PATH_START "Heretic Start Path"
diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm
index eb666c5277347..b09d4737a1ef7 100644
--- a/code/__DEFINES/atom_hud.dm
+++ b/code/__DEFINES/atom_hud.dm
@@ -75,6 +75,7 @@
#define ANTAG_HUD_VALENTINE 37
#define ANTAG_HUD_HEARTBREAKER 38
#define ANTAG_HUD_PRISONER 39
+#define ANTAG_HUD_BLOODSUCKER 40
// Notification action types
#define NOTIFY_JUMP "jump"
diff --git a/code/__DEFINES/bloodsuckers.dm b/code/__DEFINES/bloodsuckers.dm
new file mode 100644
index 0000000000000..fa919f8eea7b1
--- /dev/null
+++ b/code/__DEFINES/bloodsuckers.dm
@@ -0,0 +1,176 @@
+///Uncomment this to enable testing of Bloodsucker features (such as vassalizing people with a mind instead of a client).
+//#define BLOODSUCKER_TESTING
+
+/**
+ * Blood-level defines
+ */
+/// Determines Bloodsucker regeneration rate
+#define BS_BLOOD_VOLUME_MAX_REGEN 700
+/// Cost to torture someone halfway, in blood. Called twice for full cost
+#define TORTURE_BLOOD_HALF_COST 8
+/// Cost to convert someone after successful torture, in blood
+#define TORTURE_CONVERSION_COST 50
+/// Once blood is this low, will enter Frenzy
+#define FRENZY_THRESHOLD_ENTER 25
+/// Once blood is this high, will exit Frenzyshak
+#define FRENZY_THRESHOLD_EXIT 250
+
+/**
+ * Vassal defines
+ */
+///If someone passes all checks and can be vassalized
+#define VASSALIZATION_ALLOWED 0
+///If someone has to accept vassalization
+#define VASSALIZATION_DISLOYAL 1
+///If someone is not allowed under any circimstances to become a Vassal
+#define VASSALIZATION_BANNED 2
+
+/**
+ * Cooldown defines
+ * Used in Cooldowns Bloodsuckers use to prevent spamming
+ */
+///Spam prevention for healing messages.
+#define BLOODSUCKER_SPAM_HEALING (15 SECONDS)
+///Span prevention for Sol Masquerade messages.
+#define BLOODSUCKER_SPAM_MASQUERADE (60 SECONDS)
+
+///Span prevention for Sol messages.
+#define BLOODSUCKER_SPAM_SOL (30 SECONDS)
+
+/**
+ * Clan defines
+ */
+#define CLAN_NONE "Caitiff"
+#define CLAN_BRUJAH "Brujah Clan"
+#define CLAN_TOREADOR "Toreador Clan"
+#define CLAN_NOSFERATU "Nosferatu Clan"
+#define CLAN_TREMERE "Tremere Clan"
+#define CLAN_GANGREL "Gangrel Clan"
+#define CLAN_VENTRUE "Ventrue Clan"
+#define CLAN_MALKAVIAN "Malkavian Clan"
+#define CLAN_TZIMISCE "Tzimisce Clan"
+
+#define TREMERE_VASSAL "tremere_vassal"
+#define FAVORITE_VASSAL "favorite_vassal"
+#define REVENGE_VASSAL "revenge_vassal"
+
+/**
+ * Power defines
+ */
+/// This Power can't be used in Torpor
+#define BP_CANT_USE_IN_TORPOR (1<<0)
+/// This Power can't be used in Frenzy.
+#define BP_CANT_USE_IN_FRENZY (1<<1)
+/// This Power can't be used with a stake in you
+#define BP_CANT_USE_WHILE_STAKED (1<<2)
+/// This Power can't be used while incapacitated
+#define BP_CANT_USE_WHILE_INCAPACITATED (1<<3)
+/// This Power can't be used while unconscious
+#define BP_CANT_USE_WHILE_UNCONSCIOUS (1<<4)
+
+/// This Power can be purchased by Bloodsuckers
+#define BLOODSUCKER_CAN_BUY (1<<0)
+/// This is a Default Power that all Bloodsuckers get.
+#define BLOODSUCKER_DEFAULT_POWER (1<<1)
+/// This Power can be purchased by Tremere Bloodsuckers
+#define TREMERE_CAN_BUY (1<<2)
+/// This Power can be purchased by Vassals
+#define VASSAL_CAN_BUY (1<<3)
+
+/// This Power is a Toggled Power
+#define BP_AM_TOGGLE (1<<0)
+/// This Power is a Single-Use Power
+#define BP_AM_SINGLEUSE (1<<1)
+/// This Power has a Static cooldown
+#define BP_AM_STATIC_COOLDOWN (1<<2)
+/// This Power doesn't cost bloot to run while unconscious
+#define BP_AM_COSTLESS_UNCONSCIOUS (1<<3)
+
+/**
+ * Bloodsucker Signals
+ */
+///Called when a Bloodsucker ranks up: (datum/bloodsucker_datum, mob/owner, mob/target)
+#define BLOODSUCKER_RANK_UP "bloodsucker_rank_up"
+///Called when a Bloodsucker interacts with a Vassal on their persuasion rack.
+#define BLOODSUCKER_INTERACT_WITH_VASSAL "bloodsucker_interact_with_vassal"
+///Called when a Bloodsucker makes a Vassal into their Favorite Vassal: (datum/vassal_datum, mob/master)
+#define BLOODSUCKER_MAKE_FAVORITE "bloodsucker_make_favorite"
+///Called when a new Vassal is successfully made: (datum/bloodsucker_datum)
+#define BLOODSUCKER_MADE_VASSAL "bloodsucker_made_vassal"
+///Called when a Bloodsucker exits Torpor.
+#define BLOODSUCKER_EXIT_TORPOR "bloodsucker_exit_torpor"
+///Called when a Bloodsucker reaches Final Death.
+#define BLOODSUCKER_FINAL_DEATH "bloodsucker_final_death"
+ ///Whether the Bloodsucker should not be dusted when arriving Final Death
+ #define DONT_DUST (1<<0)
+///Called when a Bloodsucker breaks the Masquerade
+#define COMSIG_BLOODSUCKER_BROKE_MASQUERADE "comsig_bloodsucker_broke_masquerade"
+///Called when a Bloodsucker enters Frenzy
+#define BLOODSUCKER_ENTERS_FRENZY "bloodsucker_enters_frenzy"
+///Called when a Bloodsucker exits Frenzy
+#define BLOODSUCKER_EXITS_FRENZY "bloodsucker_exits_frenzy"
+
+/**
+ * Sol signals & Defines
+ */
+#define COMSIG_SOL_RANKUP_BLOODSUCKERS "comsig_sol_rankup_bloodsuckers"
+#define COMSIG_SOL_RISE_TICK "comsig_sol_rise_tick"
+#define COMSIG_SOL_NEAR_START "comsig_sol_near_start"
+#define COMSIG_SOL_END "comsig_sol_end"
+///Sent when a warning for Sol is meant to go out: (danger_level, vampire_warning_message, vassal_warning_message)
+#define COMSIG_SOL_WARNING_GIVEN "comsig_sol_warning_given"
+///Called on a Bloodsucker's Lifetick.
+#define COMSIG_BLOODSUCKER_ON_LIFETICK "comsig_bloodsucker_on_lifetick"
+
+#define DANGER_LEVEL_FIRST_WARNING 1
+#define DANGER_LEVEL_SECOND_WARNING 2
+#define DANGER_LEVEL_THIRD_WARNING 3
+#define DANGER_LEVEL_SOL_ROSE 4
+#define DANGER_LEVEL_SOL_ENDED 5
+
+/**
+ * Clan defines
+ *
+ * This is stuff that is used solely by Clans for clan-related activity.
+ */
+///Drinks blood the normal Bloodsucker way.
+#define BLOODSUCKER_DRINK_NORMAL "bloodsucker_drink_normal"
+///Drinks blood but is snobby, refusing to drink from mindless
+#define BLOODSUCKER_DRINK_SNOBBY "bloodsucker_drink_snobby"
+///Drinks blood from disgusting creatures without Humanity consequences.
+#define BLOODSUCKER_DRINK_INHUMANELY "bloodsucker_drink_imhumanely"
+
+/**
+ * Traits
+ */
+/// Falsifies Health analyzer blood levels
+#define TRAIT_MASQUERADE "masquerade"
+/// Your body is literal room temperature. Does not make you immune to the temp
+#define TRAIT_COLDBLOODED "coldblooded"
+
+/**
+ * Sources
+ */
+/// Source trait for Bloodsuckers-related traits
+#define BLOODSUCKER_TRAIT "bloodsucker_trait"
+/// Source trait while Feeding
+#define FEED_TRAIT "feed_trait"
+/// Source trait during a Frenzy
+#define FRENZY_TRAIT "frenzy_trait"
+
+/**
+ * Macros
+ */
+///Whether a mob is a Bloodsucker
+#define IS_BLOODSUCKER(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/bloodsucker))
+///Whether a mob is a Vassal
+#define IS_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal))
+///Whether a mob is a Favorite Vassal
+#define IS_FAVORITE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/favorite))
+///Whether a mob is a Revenge Vassal
+#define IS_REVENGE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/revenge))
+///Whether a mob is a curator
+#define IS_CURATOR(mob) (mob?.mind?.assigned_role == JOB_NAME_CURATOR)
+
+//Used in bloodsucker_life.dm
+#define MARTIALART_FRENZYGRAB "frenzy grabbing"
diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm
index 77e8060c06532..ad3727382aea6 100644
--- a/code/__DEFINES/language.dm
+++ b/code/__DEFINES/language.dm
@@ -62,5 +62,6 @@ GLOBAL_LIST_INIT(multilingual_language_list, typecacheof(list(
/datum/language/sylvan,
/datum/language/terrum,
/datum/language/uncommon,
- /datum/language/voltaic
+ /datum/language/voltaic,
+ /datum/language/vampiric
)))
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 1b1783108971e..e635ca6b00e6f 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -53,6 +53,7 @@
#define ROLE_PYRO_SLIME "Pyroclastic Anomaly Slime"
#define ROLE_MONKEY_HELMET "Sentient Monkey"
#define ROLE_PRISONER "Prisoner"
+#define ROLE_BLOODSUCKER "Bloodsucker"
/// Roles that are antagonists, roundstart or not, and have passes to do.. antagonistry
GLOBAL_LIST_INIT(antagonist_bannable_roles, list(
@@ -91,6 +92,7 @@ GLOBAL_LIST_INIT(antagonist_bannable_roles, list(
ROLE_FUGITIVE_HUNTER,
ROLE_SLAUGHTER_DEMON,
ROLE_CONTRACTOR_SUPPORT_UNIT,
+ ROLE_BLOODSUCKER,
))
#define BAN_ROLE_FORCED_ANTAGONISTS "Forced Antagonists"
diff --git a/code/_onclick/hud/bloodsucker.dm b/code/_onclick/hud/bloodsucker.dm
new file mode 100644
index 0000000000000..0980b370529d1
--- /dev/null
+++ b/code/_onclick/hud/bloodsucker.dm
@@ -0,0 +1,89 @@
+/// 1 tile down
+#define UI_BLOOD_DISPLAY "WEST:6,CENTER-1:0"
+/// 2 tiles down
+#define UI_VAMPRANK_DISPLAY "WEST:6,CENTER-2:-5"
+/// 6 pixels to the right, zero tiles & 5 pixels DOWN.
+#define UI_SUNLIGHT_DISPLAY "WEST:6,CENTER-0:0"
+
+///Maptext define for Bloodsucker HUDs
+#define FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, value) MAPTEXT("
[round(value,1)]
")
+///Maptext define for Bloodsucker Sunlight HUDs
+#define FORMAT_BLOODSUCKER_SUNLIGHT_TEXT(valuecolor, value) MAPTEXT("[value]
")
+
+/atom/movable/screen/bloodsucker
+ icon = 'icons/bloodsuckers/actions_bloodsucker.dmi'
+
+/atom/movable/screen/bloodsucker/blood_counter
+ name = "Blood Consumed"
+ icon_state = "blood_display"
+ screen_loc = UI_BLOOD_DISPLAY
+
+/atom/movable/screen/bloodsucker/rank_counter
+ name = "Bloodsucker Rank"
+ icon_state = "rank"
+ screen_loc = UI_VAMPRANK_DISPLAY
+
+/atom/movable/screen/bloodsucker/sunlight_counter
+ name = "Solar Flare Timer"
+ icon_state = "sunlight"
+ screen_loc = UI_SUNLIGHT_DISPLAY
+#ifdef BLOODSUCKER_TESTING
+ var/datum/controller/subsystem/sunlight/sunlight_subsystem
+
+/atom/movable/screen/bloodsucker/sunlight_counter/New(loc, ...)
+ . = ..()
+ sunlight_subsystem = SSsunlight
+#endif
+
+/// Update Blood Counter + Rank Counter
+/datum/antagonist/bloodsucker/proc/update_hud()
+ var/valuecolor
+ if(bloodsucker_blood_volume > BLOOD_VOLUME_SAFE)
+ valuecolor = "#FFDDDD"
+ else if(bloodsucker_blood_volume > BLOOD_VOLUME_BAD)
+ valuecolor = "#FFAAAA"
+
+ if(blood_display)
+ blood_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_blood_volume)
+
+ if(vamprank_display)
+ if(bloodsucker_level_unspent > 0)
+ vamprank_display.icon_state = "[initial(vamprank_display.icon_state)]_up"
+ else
+ vamprank_display.icon_state = initial(vamprank_display.icon_state)
+ vamprank_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_level)
+
+ if(sunlight_display)
+ if(SSsunlight.sunlight_active)
+ valuecolor = "#FF5555"
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_day"
+ else
+ switch(round(SSsunlight.time_til_cycle, 1))
+ if(0 to 30)
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_30"
+ valuecolor = "#FFCCCC"
+ if(31 to 60)
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_60"
+ valuecolor = "#FFE6CC"
+ if(61 to 90)
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_90"
+ valuecolor = "#FFFFCC"
+ else
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_night"
+ valuecolor = "#FFFFFF"
+ sunlight_display.maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT( \
+ valuecolor, \
+ (SSsunlight.time_til_cycle >= 60) ? "[round(SSsunlight.time_til_cycle / 60, 1)] m" : "[round(SSsunlight.time_til_cycle, 1)] s" \
+ )
+
+/// 1 tile down
+#undef UI_BLOOD_DISPLAY
+/// 2 tiles down
+#undef UI_VAMPRANK_DISPLAY
+/// 6 pixels to the right, zero tiles & 5 pixels DOWN.
+#undef UI_SUNLIGHT_DISPLAY
+
+///Maptext define for Bloodsucker HUDs
+#undef FORMAT_BLOODSUCKER_HUD_TEXT
+///Maptext define for Bloodsucker Sunlight HUDs
+#undef FORMAT_BLOODSUCKER_SUNLIGHT_TEXT
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 80a0c4896153b..6d83d0edc5061 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -30,6 +30,10 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
var/atom/movable/screen/ling/chems/lingchemdisplay
var/atom/movable/screen/ling/sting/lingstingdisplay
+ var/atom/movable/screen/bloodsucker/blood_counter/blood_display
+ var/atom/movable/screen/bloodsucker/rank_counter/vamprank_display
+ var/atom/movable/screen/bloodsucker/sunlight_counter/sunlight_display
+
var/atom/movable/screen/blobpwrdisplay
var/atom/movable/screen/alien_plasma_display
diff --git a/code/controllers/subsystem/sunlight.dm b/code/controllers/subsystem/sunlight.dm
new file mode 100644
index 0000000000000..8db02a01e312d
--- /dev/null
+++ b/code/controllers/subsystem/sunlight.dm
@@ -0,0 +1,89 @@
+///How long Sol will last until it's night again.
+#define TIME_BLOODSUCKER_DAY 60
+///Base time nighttime should be in for, until Sol rises.
+#define TIME_BLOODSUCKER_NIGHT 600
+///Time left to send an alert to Bloodsuckers about an incoming Sol.
+#define TIME_BLOODSUCKER_DAY_WARN 90
+///Time left to send an urgent alert to Bloodsuckers about an incoming Sol.
+#define TIME_BLOODSUCKER_DAY_FINAL_WARN 30
+///Time left to alert that Sol is rising.
+#define TIME_BLOODSUCKER_BURN_INTERVAL 5
+
+///How much time Sol can be 'off' by, keeping the time inconsistent.
+#define TIME_BLOODSUCKER_SOL_DELAY 90
+
+SUBSYSTEM_DEF(sunlight)
+ name = "Sol"
+ can_fire = FALSE
+ wait = 2 SECONDS
+ flags = SS_NO_INIT | SS_BACKGROUND | SS_TICKER
+
+ ///If the Sun is currently out our not.
+ var/sunlight_active = FALSE
+ ///The time between the next cycle, randomized every night.
+ var/time_til_cycle = TIME_BLOODSUCKER_NIGHT
+ ///If Bloodsucker levels for the night has been given out yet.
+ var/issued_XP = FALSE
+
+/datum/controller/subsystem/sunlight/fire(resumed = FALSE)
+ time_til_cycle--
+ if(sunlight_active)
+ if(time_til_cycle > 0)
+ SEND_SIGNAL(src, COMSIG_SOL_RISE_TICK)
+ if(!issued_XP && time_til_cycle <= 15)
+ issued_XP = TRUE
+ SEND_SIGNAL(src, COMSIG_SOL_RANKUP_BLOODSUCKERS)
+ if(time_til_cycle <= 1)
+ sunlight_active = FALSE
+ issued_XP = FALSE
+ //randomize the next sol timer
+ time_til_cycle = round(rand((TIME_BLOODSUCKER_NIGHT-TIME_BLOODSUCKER_SOL_DELAY), (TIME_BLOODSUCKER_NIGHT+TIME_BLOODSUCKER_SOL_DELAY)), 1)
+ message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [time_til_cycle / 60] minutes.")
+ SEND_SIGNAL(src, COMSIG_SOL_END)
+ warn_daylight(
+ danger_level = DANGER_LEVEL_SOL_ENDED,
+ vampire_warning_message = "The solar flare has ended, and the daylight danger has passed... for now.",
+ vassal_warning_message = "The solar flare has ended, and the daylight danger has passed... for now.",
+ )
+ return
+
+ switch(time_til_cycle)
+ if(TIME_BLOODSUCKER_DAY_WARN)
+ SEND_SIGNAL(src, COMSIG_SOL_NEAR_START)
+ warn_daylight(
+ danger_level = DANGER_LEVEL_FIRST_WARNING,
+ vampire_warning_message = "Solar Flares will bombard the station with dangerous UV radiation in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. Prepare to seek cover in a coffin or closet."
+ )
+ if(TIME_BLOODSUCKER_DAY_FINAL_WARN)
+ message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.)")
+ warn_daylight(
+ danger_level = DANGER_LEVEL_SECOND_WARNING,
+ vampire_warning_message = "Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!",
+ vassal_warning_message = "In [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds, your master will be at risk of a Solar Flare. Make sure they find cover!",
+ )
+ if(TIME_BLOODSUCKER_BURN_INTERVAL)
+ warn_daylight(
+ danger_level = DANGER_LEVEL_THIRD_WARNING,
+ vampire_warning_message = "Seek cover, for Sol rises!",
+ )
+ if(NONE)
+ sunlight_active = TRUE
+ //set the timer to countdown daytime now.
+ time_til_cycle = TIME_BLOODSUCKER_DAY
+ message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [TIME_BLOODSUCKER_DAY / 60] minutes.)")
+ warn_daylight(
+ danger_level = DANGER_LEVEL_SOL_ROSE,
+ vampire_warning_message = "Solar flares bombard the station with deadly UV light! Stay in cover for the next [TIME_BLOODSUCKER_DAY / 60] minutes or risk Final Death!",
+ vassal_warning_message = "Solar flares bombard the station with UV light!",
+ )
+
+/datum/controller/subsystem/sunlight/proc/warn_daylight(danger_level, vampire_warning_message, vassal_warning_message)
+ SEND_SIGNAL(src, COMSIG_SOL_WARNING_GIVEN, danger_level, vampire_warning_message, vassal_warning_message)
+
+#undef TIME_BLOODSUCKER_SOL_DELAY
+
+#undef TIME_BLOODSUCKER_DAY
+#undef TIME_BLOODSUCKER_NIGHT
+#undef TIME_BLOODSUCKER_DAY_WARN
+#undef TIME_BLOODSUCKER_DAY_FINAL_WARN
+#undef TIME_BLOODSUCKER_BURN_INTERVAL
diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm
index 79ec38905f1f0..8973eec14e88e 100644
--- a/code/datums/brain_damage/special.dm
+++ b/code/datums/brain_damage/special.dm
@@ -255,3 +255,166 @@
victim = null
STOP_PROCESSING(SSfastprocess,src)
return ..()
+
+/datum/brain_trauma/special/bluespace_prophet/phobetor
+ name = "Sleepless Dreamer"
+ desc = "The patient, after undergoing untold psychological hardship, believes they can travel between the dreamscapes of this dimension."
+ scan_desc = "awoken sleeper"
+ gain_text = "Your mind snaps, and you wake up. You really wake up."
+ lose_text = "You succumb once more to the sleepless dream of the unwoken."
+
+ ///Created tears, only checking the FIRST one, not the one it's created to link to.
+ var/list/created_firsts = list()
+ COOLDOWN_DECLARE(portal_cooldown)
+
+///When the trauma is removed from a mob.
+/datum/brain_trauma/special/bluespace_prophet/phobetor/on_lose(silent)
+ for(var/obj/effect/client_image_holder/phobetor/phobetor_tears as anything in created_firsts)
+ qdel(phobetor_tears)
+
+/datum/brain_trauma/special/bluespace_prophet/phobetor/on_life(seconds_per_tick, times_fired)
+ if(!COOLDOWN_FINISHED(src, portal_cooldown))
+ return
+ COOLDOWN_START(src, portal_cooldown, 10 SECONDS)
+ var/list/turf/possible_tears = list()
+ for(var/turf/nearby_turfs as anything in RANGE_TURFS(8, owner))
+ if(nearby_turfs.density)
+ continue
+ possible_tears += nearby_turfs
+ if(!LAZYLEN(possible_tears))
+ return
+
+ var/turf/first_tear
+ var/turf/second_tear
+ first_tear = return_valid_floor_in_range(owner, 6, 0, TRUE)
+ if(!first_tear)
+ return
+ second_tear = return_valid_floor_in_range(first_tear, 20, 6, TRUE)
+ if(!second_tear)
+ return
+
+ var/obj/effect/client_image_holder/phobetor/first = new(first_tear, owner)
+ var/obj/effect/client_image_holder/phobetor/second = new(second_tear, owner)
+
+ first.linked_to = second
+ first.seer = owner
+ first.desc += " This one leads to [get_area(second)]."
+ first.name += " ([get_area(second)])"
+ created_firsts += first
+
+ second.linked_to = first
+ second.seer = owner
+ second.desc += " This one leads to [get_area(first)]."
+ second.name += " ([get_area(first)])"
+
+ // Delete Next Portal if it's time (it will remove its partner)
+ var/obj/effect/client_image_holder/phobetor/first_on_the_stack = created_firsts[1]
+ if(created_firsts.len && world.time >= first_on_the_stack.created_on + first_on_the_stack.exist_length)
+ var/targetGate = first_on_the_stack
+ created_firsts -= targetGate
+ qdel(targetGate)
+
+/datum/brain_trauma/special/bluespace_prophet/phobetor/proc/return_valid_floor_in_range(atom/targeted_atom, checkRange = 8, minRange = 0, check_floor = TRUE)
+ // FAIL: Atom doesn't exist. Aren't you real?
+ if(!istype(targeted_atom))
+ return FALSE
+ var/delta_x = rand(minRange,checkRange)*pick(-1,1)
+ var/delta_y = rand(minRange,checkRange)*pick(-1,1)
+ var/turf/center = get_turf(targeted_atom)
+
+ var/target = locate((center.x + delta_x),(center.y + delta_y), center.z)
+ if(check_turf_is_valid(target, check_floor))
+ return target
+ return FALSE
+
+/**
+ * Used as a helper that checks if you can successfully teleport to a turf.
+ * Returns a boolean, and checks for if the turf has density, if the turf's area has the NOTELEPORT flag,
+ * and if the objects in the turf have density.
+ * If check_floor is TRUE in the argument, it will return FALSE if it's not a type of [/turf/open/floor].
+ * Arguments:
+ * * turf/open_turf - The turf being checked for validity.
+ * * check_floor - Checks if it's a type of [/turf/open/floor]. If this is FALSE, lava/chasms will be able to be selected.
+ */
+/datum/brain_trauma/special/bluespace_prophet/phobetor/proc/check_turf_is_valid(turf/open_turf, check_floor = TRUE)
+ if(check_floor && !istype(open_turf, /turf/open/floor))
+ return FALSE
+ if(open_turf.density)
+ return FALSE
+ var/area/turf_area = get_area(open_turf)
+ if(turf_area.area_flags & HIDDEN_AREA)
+ return FALSE
+ // Checking for Objects...
+ for(var/obj/object in open_turf)
+ if(object.density)
+ return FALSE
+ return TRUE
+
+/**
+ * # Phobetor Tears
+ *
+ * The phobetor tears created by the Brain trauma.
+ */
+
+/obj/effect/client_image_holder/phobetor
+ name = "phobetor tear"
+ desc = "A subdimensional rip in reality, which gives extra-spacial passage to those who have woken from the sleepless dream."
+ icon = 'icons/effects/phobetor_tear.dmi'
+ icon_state = "phobetor_tear"
+
+ /// How long this will exist for
+ var/exist_length = 50 SECONDS
+ /// The time of this tear's creation
+ var/created_on
+ /// The phobetor tear this is linked to
+ var/obj/effect/client_image_holder/phobetor/linked_to
+ /// The person able to see this tear.
+ var/mob/living/carbon/seer
+
+/obj/effect/client_image_holder/phobetor/Initialize()
+ . = ..()
+ created_on = world.time
+
+/obj/effect/client_image_holder/phobetor/Destroy()
+ if(linked_to)
+ linked_to.linked_to = null
+ QDEL_NULL(linked_to)
+ return ..()
+
+/obj/effect/client_image_holder/phobetor/proc/check_location_seen(atom/subject, turf/target_turf)
+ if(!target_turf)
+ return FALSE
+ if(!isturf(target_turf))
+ return FALSE
+ if(!target_turf.lighting_object || !target_turf.get_lumcount() >= 0.1)
+ return FALSE
+ for(var/mob/living/nearby_viewers in viewers(target_turf))
+ if(nearby_viewers == subject)
+ continue
+ if(!isliving(nearby_viewers) || !nearby_viewers.mind)
+ continue
+ if(nearby_viewers.has_unlimited_silicon_privilege || nearby_viewers.is_blind())
+ continue
+ return TRUE
+ return FALSE
+
+/obj/effect/client_image_holder/phobetor/attack_hand(mob/living/user, list/modifiers)
+ if(user != seer || !linked_to)
+ return
+ if(user.loc != src.loc)
+ to_chat(user, "Step into the Tear before using it.")
+ return
+ for(var/obj/item/implant/tracking/imp in user.implants)
+ if(imp)
+ to_chat(user, "[imp] gives you the sense that you're being watched.")
+ return
+ // Is this, or linked, stream being watched?
+ if(check_location_seen(user, get_turf(user)))
+ to_chat(user, "Not while you're being watched.")
+ return
+ if(check_location_seen(user, get_turf(linked_to)))
+ to_chat(user, "Your destination is being watched.")
+ return
+ to_chat(user, "You slip unseen through [src].")
+ user.playsound_local(null, 'sound/magic/wand_teleport.ogg', 30, FALSE, pressure_affected = FALSE)
+ user.forceMove(get_turf(linked_to))
diff --git a/code/datums/components/crafting/crafting_lists/structures.dm b/code/datums/components/crafting/crafting_lists/structures.dm
index d9d38f67d0ab7..9916a1e00871f 100644
--- a/code/datums/components/crafting/crafting_lists/structures.dm
+++ b/code/datums/components/crafting/crafting_lists/structures.dm
@@ -120,3 +120,88 @@
category = CAT_STRUCTURE
one_per_turf = TRUE
+/datum/crafting_recipe/blackcoffin
+ name = "Black Coffin"
+ result = /obj/structure/closet/crate/coffin/blackcoffin
+ tools = list(TOOL_WELDER, TOOL_SCREWDRIVER)
+ reqs = list(
+ /obj/item/stack/sheet/cotton/cloth = 1,
+ /obj/item/stack/sheet/mineral/wood = 5,
+ /obj/item/stack/sheet/iron = 1,
+ )
+ time = 15 SECONDS
+ category = CAT_STRUCTURE
+
+/datum/crafting_recipe/securecoffin
+ name = "Secure Coffin"
+ result = /obj/structure/closet/crate/coffin/securecoffin
+ tools = list(TOOL_WELDER, TOOL_SCREWDRIVER)
+ reqs = list(
+ /obj/item/stack/rods = 1,
+ /obj/item/stack/sheet/plasteel = 5,
+ /obj/item/stack/sheet/iron = 5,
+ )
+ time = 15 SECONDS
+ category = CAT_STRUCTURE
+
+/datum/crafting_recipe/meatcoffin
+ name = "Meat Coffin"
+ result = /obj/structure/closet/crate/coffin/meatcoffin
+ tools = list(TOOL_KNIFE, TOOL_ROLLINGPIN)
+ reqs = list(
+ /obj/item/food/meat/slab = 5,
+ /obj/item/restraints/handcuffs/cable = 1,
+ )
+ time = 15 SECONDS
+ category = CAT_STRUCTURE
+ always_available = FALSE //only for the elite vampires
+
+/datum/crafting_recipe/metalcoffin
+ name = "Metal Coffin"
+ result = /obj/structure/closet/crate/coffin/metalcoffin
+ tools = list(TOOL_WRENCH, TOOL_SCREWDRIVER)
+ reqs = list(
+ /obj/item/stack/sheet/iron = 6,
+ /obj/item/stack/rods = 2,
+ )
+ time = 10 SECONDS
+ category = CAT_STRUCTURE
+
+/datum/crafting_recipe/vassalrack
+ name = "Persuasion Rack"
+ result = /obj/structure/bloodsucker/vassalrack
+ tools = list(TOOL_WELDER, TOOL_WRENCH)
+ reqs = list(
+ /obj/item/stack/sheet/mineral/wood = 3,
+ /obj/item/stack/sheet/iron = 2,
+ /obj/item/restraints/handcuffs/cable = 2,
+ )
+ time = 15 SECONDS
+ category = CAT_STRUCTURE
+ always_available = FALSE
+
+/datum/crafting_recipe/candelabrum
+ name = "Candelabrum"
+ result = /obj/structure/bloodsucker/candelabrum
+ tools = list(TOOL_WELDER, TOOL_WRENCH)
+ reqs = list(
+ /obj/item/stack/sheet/iron = 3,
+ /obj/item/stack/rods = 1,
+ /obj/item/candle = 1,
+ )
+ time = 10 SECONDS
+ category = CAT_STRUCTURE
+ always_available = FALSE
+
+/datum/crafting_recipe/bloodthrone
+ name = "Blood Throne"
+ result = /obj/structure/bloodsucker/bloodthrone
+ tools = list(TOOL_WRENCH)
+ reqs = list(
+ /obj/item/stack/sheet/cotton/cloth = 3,
+ /obj/item/stack/sheet/iron = 5,
+ /obj/item/stack/sheet/mineral/wood = 1,
+ )
+ time = 5 SECONDS
+ category = CAT_STRUCTURE
+ always_available = FALSE
diff --git a/code/datums/components/crafting/crafting_lists/weaponry/weapons.dm b/code/datums/components/crafting/crafting_lists/weaponry/weapons.dm
index af1eefd2cd594..19177bd47089f 100644
--- a/code/datums/components/crafting/crafting_lists/weaponry/weapons.dm
+++ b/code/datums/components/crafting/crafting_lists/weaponry/weapons.dm
@@ -396,3 +396,37 @@
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
dangerous_craft = TRUE
+
+/datum/crafting_recipe/stake
+ name = "Stake"
+ result = /obj/item/stake
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 3)
+ time = 8 SECONDS
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+ dangerous_craft = TRUE
+
+/datum/crafting_recipe/hardened_stake
+ name = "Hardened Stake"
+ result = /obj/item/stake/hardened
+ tools = list(TOOL_WELDER)
+ reqs = list(/obj/item/stack/rods = 1)
+ time = 6 SECONDS
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+ dangerous_craft = TRUE
+ always_available = FALSE
+
+/datum/crafting_recipe/silver_stake
+ name = "Silver Stake"
+ result = /obj/item/stake/hardened/silver
+ tools = list(TOOL_WELDER)
+ reqs = list(
+ /obj/item/stack/sheet/mineral/silver = 1,
+ /obj/item/stake/hardened = 1,
+ )
+ time = 8 SECONDS
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+ dangerous_craft = TRUE
+ always_available = FALSE
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index 5b02717818f96..8b219c32a9810 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -43,6 +43,7 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_VALENTINE = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_HEARTBREAKER = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_PRISONER = new/datum/atom_hud/antag/hidden(),
+ ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag()
))
/datum/atom_hud
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index 0a21008e6b14c..0fd511dfba17d 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -117,3 +117,36 @@
new_heretic.knowledge_points = min(new_heretic.knowledge_points, 5)
return DYNAMIC_EXECUTE_SUCCESS
+
+//////////////////////////////////////////////
+// //
+// LATEJOIN BLOODSUCKER //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/latejoin/bloodsucker
+ name = "Bloodsucker Breakout"
+ antag_datum = /datum/antagonist/bloodsucker
+ role_preference = /datum/role_preference/antagonist/bloodsucker
+ protected_roles = list(JOB_NAME_CAPTAIN, JOB_NAME_HEADOFPERSONNEL, JOB_NAME_HEADOFSECURITY, JOB_NAME_WARDEN, JOB_NAME_SECURITYOFFICER, JOB_NAME_DETECTIVE, JOB_NAME_CURATOR)
+ restricted_roles = list(JOB_NAME_AI, JOB_NAME_CYBORG)
+ required_candidates = 1
+ weight = 5
+ cost = 10
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ repeatable = FALSE
+
+/datum/dynamic_ruleset/latejoin/bloodsucker/execute()
+ var/mob/latejoiner = pick(candidates) // This should contain a single player, but in case.
+ assigned += latejoiner.mind
+
+ for(var/datum/mind/candidate_mind as anything in assigned)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = candidate_mind.make_bloodsucker()
+ if(!bloodsuckerdatum)
+ assigned -= candidate_mind
+ message_admins("[ADMIN_LOOKUPFLW(candidate_mind)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.")
+ continue
+ bloodsuckerdatum.bloodsucker_level_unspent = rand(2,3)
+ message_admins("[ADMIN_LOOKUPFLW(candidate_mind)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.")
+ log_game("DYNAMIC: [key_name(candidate_mind)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.")
+ return TRUE
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 3217233bbeda9..1a0813f8633ea 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -280,6 +280,48 @@
M.add_ion_law(generate_ion_law())
return DYNAMIC_EXECUTE_SUCCESS
+//////////////////////////////////////////////
+// //
+// MIDROUND BLOODSUCKER //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/bloodsucker
+ name = "Vampiric Accident"
+ midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
+ antag_datum = /datum/antagonist/bloodsucker
+ role_preference = /datum/role_preference/midround_living/bloodsucker
+ protected_roles = list(JOB_NAME_CAPTAIN, JOB_NAME_HEADOFPERSONNEL, JOB_NAME_HEADOFSECURITY, JOB_NAME_WARDEN, JOB_NAME_SECURITYOFFICER, JOB_NAME_DETECTIVE, JOB_NAME_CURATOR)
+ restricted_roles = list(JOB_NAME_AI, JOB_NAME_CYBORG, "Positronic Brain")
+ required_candidates = 1
+ weight = 5
+ cost = 10
+ requirements = list(40,30,20,10,10,10,10,10,10,10)
+ repeatable = FALSE
+
+/datum/dynamic_ruleset/midround/bloodsucker/trim_candidates()
+ candidates = living_players
+ for(var/mob/living/player in candidates)
+ if(!is_station_level(player.z))
+ candidates.Remove(player)
+ else if(player.mind && (player.mind.special_role || length(player.mind.antag_datums) > 0))
+ candidates.Remove(player)
+
+/datum/dynamic_ruleset/midround/bloodsucker/execute()
+ var/mob/selected_mobs = pick(living_players)
+ assigned += selected_mobs.mind
+ living_players -= selected_mobs
+ var/datum/mind/candidate_mind = selected_mobs.mind
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = candidate_mind.make_bloodsucker()
+ if(!bloodsuckerdatum)
+ assigned -= selected_mobs.mind
+ message_admins("[ADMIN_LOOKUPFLW(selected_mobs)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.")
+ return FALSE
+ bloodsuckerdatum.bloodsucker_level_unspent = rand(2,3)
+ message_admins("[ADMIN_LOOKUPFLW(selected_mobs)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.")
+ log_game("DYNAMIC: [key_name(selected_mobs)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.")
+ return TRUE
+
//////////////////////////////////////////////
// //
// WIZARD (GHOST) //
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index ebe48dbb4474a..dcaef4d48caf3 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -630,3 +630,45 @@
SSticker.mode_result = "win - incursion win"
else
SSticker.mode_result = "loss - staff stopped the incursion"
+
+//////////////////////////////////////////////
+// //
+// ROUNDSTART BLOODSUCKER //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/bloodsucker
+ name = "Bloodsuckers"
+ role_preference = /datum/role_preference/antagonist/bloodsucker
+ antag_datum = /datum/antagonist/bloodsucker
+ protected_roles = list(JOB_NAME_CAPTAIN, JOB_NAME_HEADOFPERSONNEL, JOB_NAME_HEADOFSECURITY, JOB_NAME_WARDEN, JOB_NAME_SECURITYOFFICER, JOB_NAME_DETECTIVE, JOB_NAME_CURATOR)
+ restricted_roles = list(JOB_NAME_AI, JOB_NAME_CYBORG)
+ required_candidates = 1
+ weight = 3
+ cost = 10
+ scaling_cost = 9
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ antag_cap = list("denominator" = 24)
+
+/datum/dynamic_ruleset/roundstart/bloodsucker/pre_execute(population)
+ . = ..()
+ var/num_bloodsuckers = get_antag_cap(population) * (scaled_times + 1)
+
+ for(var/i = 1 to num_bloodsuckers)
+ if(candidates.len <= 0)
+ break
+ var/mob/selected_mobs = pick_n_take(candidates)
+ assigned += selected_mobs.mind
+ selected_mobs.mind.restricted_roles = restricted_roles
+ GLOB.pre_setup_antags += selected_mobs.mind
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/bloodsucker/execute()
+ for(var/datum/mind/candidate_minds as anything in assigned)
+ if(!candidate_minds.make_bloodsucker())
+ message_admins("[ADMIN_LOOKUPFLW(candidate_minds)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.")
+ assigned -= candidate_minds
+ continue
+ GLOB.pre_setup_antags -= candidate_minds
+ candidate_minds.special_role = ROLE_BLOODSUCKER
+ return TRUE
diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm
index f990ca463f537..b28eec2c93343 100644
--- a/code/game/objects/items/implants/implant_mindshield.dm
+++ b/code/game/objects/items/implants/implant_mindshield.dm
@@ -42,6 +42,15 @@
to_chat(target, "You feel something interfering with your mental conditioning, but you resist it!")
else
to_chat(target, "You feel a sense of peace and security. You are now protected from brainwashing.")
+
+ var/datum/antagonist/vassal/vassal = IS_VASSAL(target)
+ if(vassal)
+ if(IS_FAVORITE_VASSAL(target))
+ if(!silent)
+ target.visible_message("[target] seems to resist the implant!", "You feel something interfering with your mental conditioning, but you resist it!")
+ return FALSE
+ target.mind.remove_antag_datum(/datum/antagonist/vassal)
+
ADD_TRAIT(target, TRAIT_MINDSHIELD, "implant")
target.sec_hud_set_implants()
return TRUE
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index b401ff08a3600..3a2ac03064145 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -1002,3 +1002,97 @@
target.apply_damage(stamina_force, STAMINA, target_zone, armour_level)
return ..()
+
+/obj/item/stake
+ name = "wooden stake"
+ desc = "A simple wooden stake carved to a sharp point."
+ icon = 'icons/bloodsuckers/stakes.dmi'
+ icon_state = "wood"
+ item_state = "wood"
+ lefthand_file = 'icons/bloodsuckers/bs_leftinhand.dmi'
+ righthand_file = 'icons/bloodsuckers/bs_rightinhand.dmi'
+ slot_flags = ITEM_SLOT_POCKETS
+ w_class = WEIGHT_CLASS_SMALL
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ attack_verb_continuous = list("staked", "stabbed", "tore into")
+ attack_verb_simple = list("staked", "stabbed", "tore into")
+ sharpness = IS_SHARP
+ embedding = list("embed_chance" = 20)
+ force = 6
+ throwforce = 10
+ max_integrity = 30
+
+ ///Time it takes to embed the stake into someone's chest.
+ var/staketime = 12 SECONDS
+
+/obj/item/stake/attack(mob/living/target, mob/living/user, params)
+ . = ..()
+ if(.)
+ return
+ // Invalid Target, or not targetting the chest?
+ if(check_zone(user.zone_selected) != BODY_ZONE_CHEST)
+ return
+ if(target == user)
+ return
+ if(!target.can_be_staked()) // Oops! Can't.
+ to_chat(user, "You can't stake [target] when they are moving about! They have to be laying down or grabbed by the neck!")
+ return
+ if(HAS_TRAIT(target, TRAIT_PIERCEIMMUNE))
+ to_chat(user, "[target]'s chest resists the stake. It won't go in.")
+ return
+
+ to_chat(user, "You put all your weight into embedding the stake into [target]'s chest...")
+ playsound(user, 'sound/magic/Demon_consume.ogg', 50, 1)
+ if(!do_after(user, staketime, target, extra_checks = CALLBACK(target, TYPE_PROC_REF(/mob/living/carbon, can_be_staked)))) // user / target / time / uninterruptable / show progress bar / extra checks
+ return
+ // Drop & Embed Stake
+ user.visible_message(
+ "[user.name] drives the [src] into [target]'s chest!",
+ "You drive the [src] into [target]'s chest!",
+ )
+ playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1)
+ if(tryEmbed(target.get_bodypart(BODY_ZONE_CHEST), TRUE, TRUE)) //and if it embeds successfully in their chest, cause a lot of pain
+ target.apply_damage(max(10, force * 1.2), BRUTE, BODY_ZONE_CHEST, wound_bonus = 0, sharpness = TRUE)
+ if(QDELETED(src)) // in case trying to embed it caused its deletion (say, if it's DROPDEL)
+ return
+ if(!target.mind)
+ return
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = target.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum)
+ // If DEAD or TORPID... Kill Bloodsucker!
+ if(target.StakeCanKillMe())
+ bloodsuckerdatum.FinalDeath()
+ else
+ to_chat(target, "You have been staked! Your powers are useless, your death forever, while it remains in place.")
+ target.balloon_alert(target, "you have been staked!")
+
+///Can this target be staked? If someone stands up before this is complete, it fails. Best used on someone stationary.
+/mob/living/proc/can_be_staked()
+ return FALSE
+
+/mob/living/carbon/can_be_staked()
+ if(!(mobility_flags & MOBILITY_MOVE))
+ return TRUE
+ return FALSE
+
+/// Created by welding and acid-treating a simple stake.
+/obj/item/stake/hardened
+ name = "hardened stake"
+ desc = "A wooden stake carved to a sharp point and hardened by fire."
+ icon_state = "hardened"
+ force = 8
+ throwforce = 12
+ armour_penetration = 10
+ embedding = list("embed_chance" = 35)
+ staketime = 80
+
+/obj/item/stake/hardened/silver
+ name = "silver stake"
+ desc = "Polished and sharp at the end. For when some mofo is always trying to iceskate uphill."
+ icon_state = "silver"
+ item_state = "silver"
+ siemens_coefficient = 1
+ force = 9
+ armour_penetration = 25
+ embedding = list("embed_chance" = 65)
+ staketime = 60
diff --git a/code/game/objects/items/stacks/sheets/organic/wood.dm b/code/game/objects/items/stacks/sheets/organic/wood.dm
index 35a9daba19827..b752ccebdfb5c 100644
--- a/code/game/objects/items/stacks/sheets/organic/wood.dm
+++ b/code/game/objects/items/stacks/sheets/organic/wood.dm
@@ -32,6 +32,32 @@ Woods Sheets
/obj/item/stack/sheet/wood/get_recipes()
return GLOB.wood_recipes
+/obj/item/stack/sheet/mineral/wood/attackby(obj/item/item, mob/user, params)
+ if(!item.is_sharp())
+ return ..()
+ user.visible_message(
+ "[user] begins whittling [src] into a pointy object.",
+ "You begin whittling [src] into a sharp point at one end.",
+ "You hear wood carving.",
+ )
+ // 5 Second Timer
+ if(!do_after(user, 5 SECONDS, src, NONE, TRUE))
+ return
+ // Make Stake
+ var/obj/item/stake/new_item = new(user.loc)
+ user.visible_message(
+ "[user] finishes carving a stake out of [src].",
+ "You finish carving a stake out of [src].",
+ )
+ // Prepare to Put in Hands (if holding wood)
+ var/obj/item/stack/sheet/mineral/wood/wood_stack = src
+ var/replace = (user.get_inactive_held_item() == wood_stack)
+ // Use Wood
+ wood_stack.use(1)
+ // If stack depleted, put item in that hand (if it had one)
+ if(!wood_stack && replace)
+ user.put_in_hands(new_item)
+
/* Bamboo */
/obj/item/stack/sheet/bamboo
diff --git a/code/game/objects/structures/bloodsucker_crypt.dm b/code/game/objects/structures/bloodsucker_crypt.dm
new file mode 100644
index 0000000000000..94d50f4f903d3
--- /dev/null
+++ b/code/game/objects/structures/bloodsucker_crypt.dm
@@ -0,0 +1,576 @@
+/obj/structure/bloodsucker
+ ///Who owns this structure?
+ var/mob/living/owner
+ /*
+ * We use vars to add descriptions to items.
+ * This way we don't have to make a new /examine for each structure
+ * And it's easier to edit.
+ */
+ var/ghost_desc
+ var/bloodsucker_desc
+ var/vassal_desc
+ var/curator_desc
+
+/obj/structure/bloodsucker/examine(mob/user)
+ . = ..()
+ if(!user.mind && ghost_desc != "")
+ . += "[ghost_desc]"
+ if(IS_BLOODSUCKER(user) && bloodsucker_desc)
+ if(!owner)
+ . += "It is unsecured. Click on [src] while in your lair to secure it in place to get its full potential"
+ return
+ . += "[bloodsucker_desc]"
+ if(IS_VASSAL(user) && vassal_desc)
+ . += "[vassal_desc]"
+ if(IS_CURATOR(user) && curator_desc)
+ . += "You have secured [src] in place.")
+ to_chat(user, "* Bloodsucker Tip: Examine [src] to understand how it functions!")
+ owner = user
+
+/// This handles unbolting of the structure.
+/obj/structure/bloodsucker/proc/unbolt(mob/user)
+ to_chat(user, "You have unsecured [src].")
+ owner = null
+
+/obj/structure/bloodsucker/attackby(obj/item/item, mob/living/user, params)
+ /// If a Bloodsucker tries to wrench it in place, yell at them.
+ if(item.tool_behaviour == TOOL_WRENCH && !anchored && IS_BLOODSUCKER(user))
+ user.playsound_local(null, 'sound/machines/buzz-sigh.ogg', 40, FALSE, pressure_affected = FALSE)
+ to_chat(user, "* Bloodsucker Tip: Examine Bloodsucker structures to understand how they function!")
+ return
+ return ..()
+
+/obj/structure/bloodsucker/attack_hand(mob/user, list/modifiers)
+// . = ..() // Don't call parent, else they will handle unbuckling.
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ /// Claiming the Rack instead of using it?
+ if(istype(bloodsuckerdatum) && !owner)
+ if(!bloodsuckerdatum.bloodsucker_lair_area)
+ to_chat(user, "You don't have a lair. Claim a coffin to make that location your lair.")
+ return FALSE
+ if(bloodsuckerdatum.bloodsucker_lair_area != get_area(src))
+ to_chat(user, "You may only activate this structure in your lair: [bloodsuckerdatum.bloodsucker_lair_area].")
+ return FALSE
+
+ /// Radial menu for securing your Persuasion rack in place.
+ to_chat(user, "Do you wish to secure [src] here?")
+ var/static/list/secure_options = list(
+ "Yes" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_no"))
+ var/secure_response = show_radial_menu(user, src, secure_options, radius = 36, require_near = TRUE)
+ if(!secure_response)
+ return FALSE
+ switch(secure_response)
+ if("Yes")
+ user.playsound_local(null, 'sound/items/ratchet.ogg', 70, FALSE, pressure_affected = FALSE)
+ bolt(user)
+ return FALSE
+ return FALSE
+ return TRUE
+
+/obj/structure/bloodsucker/AltClick(mob/user)
+ . = ..()
+ if(user == owner && user.Adjacent(src))
+ balloon_alert(user, "unbolt [src]?")
+ var/static/list/unclaim_options = list(
+ "Yes" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_no"),
+ )
+ var/unclaim_response = show_radial_menu(user, src, unclaim_options, radius = 36, require_near = TRUE)
+ switch(unclaim_response)
+ if("Yes")
+ unbolt(user)
+
+/obj/structure/bloodsucker/vassalrack
+ name = "persuasion rack"
+ desc = "If this wasn't meant for torture, then someone has some fairly horrifying hobbies."
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ icon_state = "vassalrack"
+ anchored = FALSE
+ density = TRUE
+ can_buckle = TRUE
+ buckle_lying = 180
+ ghost_desc = "This is a Vassal rack, which allows Bloodsuckers to thrall crewmembers into loyal minions."
+ bloodsucker_desc = "This is the Vassal rack, which allows you to thrall crewmembers into loyal minions in your service.\n\
+ Simply click and hold on a victim, and then drag their sprite on the vassal rack. Right-click on the vassal rack to unbuckle them.\n\
+ To convert into a Vassal, repeatedly click on the persuasion rack. The time required scales with the tool in your off hand. This costs Blood to do.\n\
+ Vassals can be turned into special ones by continuing to torture them once converted."
+ vassal_desc = "This is the vassal rack, which allows your master to thrall crewmembers into their minions.\n\
+ Aid your master in bringing their victims here and keeping them secure.\n\
+ You can secure victims to the vassal rack by click dragging the victim onto the rack while it is secured."
+ curator_desc = "This is the vassal rack, which monsters use to brainwash crewmembers into their loyal slaves.\n\
+ They usually ensure that victims are handcuffed, to prevent them from running away.\n\
+ Their rituals take time, allowing us to disrupt it."
+
+ /// Resets on each new character to be added to the chair. Some effects should lower it...
+ var/convert_progress = 3
+ /// Mindshielded and Antagonists willingly have to accept you as their Master.
+ var/disloyalty_confirm = FALSE
+ /// Prevents popup spam.
+ var/disloyalty_offered = FALSE
+
+/obj/structure/bloodsucker/vassalrack/deconstruct(disassembled = TRUE)
+ . = ..()
+ new /obj/item/stack/sheet/iron(src.loc, 4)
+ new /obj/item/stack/rods(loc, 4)
+ qdel(src)
+
+/obj/structure/bloodsucker/vassalrack/bolt()
+ . = ..()
+ density = FALSE
+ anchored = TRUE
+
+/obj/structure/bloodsucker/vassalrack/unbolt()
+ . = ..()
+ density = TRUE
+ anchored = FALSE
+
+/obj/structure/bloodsucker/vassalrack/MouseDrop_T(atom/movable/movable_atom, mob/user)
+ var/mob/living/living_target = movable_atom
+ if(!anchored && IS_BLOODSUCKER(user))
+ to_chat(user, "Until this rack is secured in place, it cannot serve its purpose.")
+ to_chat(user, "* Bloodsucker Tip: Examine the Persuasion Rack to understand how it functions!")
+ return
+ // Default checks
+ if(!isliving(movable_atom) || !living_target.Adjacent(src) || living_target == user || !isliving(user) || has_buckled_mobs() || user.incapacitated() || living_target.buckled)
+ return
+ // Don't buckle Silicon to it please.
+ if(issilicon(living_target))
+ to_chat(user, "You realize that this machine cannot be vassalized, therefore it is useless to buckle them.")
+ return
+ if(do_after(user, 5 SECONDS, living_target))
+ attach_victim(living_target, user)
+
+/// Attempt Release (Owner vs Non Owner)
+/obj/structure/bloodsucker/vassalrack/attack_hand(mob/user, modifiers)
+ . = ..()
+ if(. == COMPONENT_CANCEL_ATTACK_CHAIN)
+ return
+ if(!has_buckled_mobs() || !isliving(user))
+ return
+ var/mob/living/carbon/buckled_carbons = pick(buckled_mobs)
+ if(buckled_carbons)
+ if(user == owner)
+ unbuckle_mob(buckled_carbons)
+ else
+ user_unbuckle_mob(buckled_carbons, user)
+
+/**
+ * Attempts to buckle target into the vassalrack
+ */
+/obj/structure/bloodsucker/vassalrack/proc/attach_victim(mob/living/target, mob/living/user)
+ if(!buckle_mob(target))
+ return
+ user.visible_message(
+ "[user] straps [target] into the rack, immobilizing them.",
+ "You secure [target] tightly in place. They won't escape you now.",
+ )
+
+ playsound(loc, 'sound/effects/pop_expl.ogg', 25, 1)
+ update_appearance(UPDATE_ICON)
+ density = TRUE
+
+ // Set up Torture stuff now
+ convert_progress = 3
+ disloyalty_confirm = FALSE
+ disloyalty_offered = FALSE
+
+/// Attempt Unbuckle
+/obj/structure/bloodsucker/vassalrack/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
+ if(IS_BLOODSUCKER(user) || IS_VASSAL(user))
+ return ..()
+
+ if(buckled_mob == user)
+ buckled_mob.visible_message(
+ "[user] tries to release themself from the rack!",
+ "You attempt to release yourself from the rack!",
+ "You hear a squishy wet noise.",
+ )
+ if(!do_after(user, 20 SECONDS, buckled_mob))
+ return
+ else
+ buckled_mob.visible_message(
+ "[user] tries to pull [buckled_mob] from the rack!",
+ "You attempt to release [buckled_mob] from the rack!",
+ "You hear a squishy wet noise.",
+ )
+ if(!do_after(user, 10 SECONDS, buckled_mob))
+ return
+
+ return ..()
+
+/obj/structure/bloodsucker/vassalrack/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
+ . = ..()
+ if(!.)
+ return FALSE
+ visible_message("[buckled_mob][buckled_mob.stat == DEAD ? "'s corpse" : ""] slides off of the rack.")
+ density = FALSE
+ buckled_mob.Paralyze(2 SECONDS)
+ update_appearance(UPDATE_ICON)
+ return TRUE
+
+/obj/structure/bloodsucker/vassalrack/attack_hand(mob/user, list/modifiers)
+ . = ..()
+ if(!.)
+ return FALSE
+ // Is there anyone on the rack & If so, are they being tortured?
+ if(!has_buckled_mobs())
+ return FALSE
+
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ var/mob/living/carbon/buckled_carbons = pick(buckled_mobs)
+ // If I'm not a Bloodsucker, try to unbuckle them.
+ if(!istype(bloodsuckerdatum))
+ user_unbuckle_mob(buckled_carbons, user)
+ return
+ if(!bloodsuckerdatum.my_clan)
+ to_chat(user, "You can't vassalize people until you enter a Clan (Through your Antagonist UI button)")
+ user.balloon_alert(user, "join a clan first!")
+ return
+
+ var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(buckled_carbons)
+ // Are they our Vassal?
+ if(vassaldatum && (vassaldatum in bloodsuckerdatum.vassals))
+ SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_INTERACT_WITH_VASSAL, vassaldatum)
+ return
+
+ // Not our Vassal, but Alive & We're a Bloodsucker, good to torture!
+ torture_victim(user, buckled_carbons)
+
+/**
+ * Torture steps:
+ *
+ * * Tick Down Conversion from 3 to 0
+ * * Break mindshielding/antag (on approve)
+ * * Vassalize target
+ */
+/obj/structure/bloodsucker/vassalrack/proc/torture_victim(mob/living/user, mob/living/target)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(IS_VASSAL(target))
+ var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal)
+ if(!vassaldatum.master.broke_masquerade)
+ balloon_alert(user, "someone else's vassal!")
+ return FALSE
+
+ var/disloyalty_requires = RequireDisloyalty(user, target)
+
+ if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
+ balloon_alert(user, "its pointless to try and change their loyalties!")
+ return FALSE
+
+ if(disloyalty_requires == VASSALIZATION_BANNED)
+ balloon_alert(user, "can't be vassalized!")
+ return FALSE
+
+ // Conversion Process
+ if(convert_progress)
+ balloon_alert(user, "spilling blood...")
+ bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST)
+ if(!do_torture(user, target))
+ return FALSE
+ bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST)
+ // Prevent them from unbuckling themselves as long as we're torturing.
+ target.Paralyze(1 SECONDS)
+ convert_progress--
+
+ // We're done? Let's see if they can be Vassal.
+ if(convert_progress)
+ balloon_alert(user, "needs more persuasion...")
+ return
+
+ if(disloyalty_requires)
+ balloon_alert(user, "has external loyalties! more persuasion required!")
+ else
+ balloon_alert(user, "ready for communion!")
+ return
+
+ if(!disloyalty_confirm && disloyalty_requires)
+ if(!do_disloyalty(user, target))
+ return
+ if(!disloyalty_confirm)
+ balloon_alert(user, "refused persuasion!")
+ else
+ balloon_alert(user, "ready for communion!")
+ return
+
+ user.balloon_alert_to_viewers("smears blood...", "painting bloody marks...")
+ if(!do_after(user, 5 SECONDS, target))
+ balloon_alert(user, "interrupted!")
+ return
+ // Convert to Vassal!
+ bloodsuckerdatum.AddBloodVolume(-TORTURE_CONVERSION_COST)
+ if(bloodsuckerdatum.make_vassal(target))
+ remove_loyalties(target)
+ SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, user, target)
+
+/obj/structure/bloodsucker/vassalrack/proc/do_torture(mob/living/user, mob/living/carbon/target, mult = 1)
+ // Fifteen seconds if you aren't using anything. Shorter with weapons and such.
+ var/torture_time = 15
+ var/torture_dmg_brute = 2
+ var/torture_dmg_burn = 0
+ var/obj/item/bodypart/selected_bodypart = pick(target.bodyparts)
+ // Get Weapon
+ var/obj/item/held_item = user.get_inactive_held_item()
+ /// Weapon Bonus
+ if(held_item)
+ torture_time -= held_item.force / 4
+ if(!held_item.use_tool(src, user, 0, volume = 5))
+ return
+ switch(held_item.damtype)
+ if(BRUTE)
+ torture_dmg_brute = held_item.force / 4
+ torture_dmg_burn = 0
+ if(BURN)
+ torture_dmg_brute = 0
+ torture_dmg_burn = held_item.force / 4
+ switch(held_item.sharpness)
+ if(IS_SHARP)
+ torture_time -= 2
+ if(IS_SHARP_ACCURATE)
+ torture_time -= 3
+
+ // Minimum 5 seconds.
+ torture_time = max(5 SECONDS, torture_time * 10)
+ // Now run process.
+ if(!do_after(user, (torture_time * mult), target))
+ return FALSE
+
+ if(held_item)
+ held_item.play_tool_sound(target)
+ target.visible_message(
+ "[user] performs a ritual, spilling some of [target]'s blood from their [selected_bodypart.name] and shaking them up!",
+ "[user] performs a ritual, spilling some blood from your [selected_bodypart.name], shaking you up!")
+
+ INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, emote), "scream")
+ target.Jitter(5 SECONDS)
+ target.apply_damages(brute = torture_dmg_brute, burn = torture_dmg_burn, def_zone = selected_bodypart.body_zone)
+ return TRUE
+
+/// Offer them the oppertunity to join now.
+/obj/structure/bloodsucker/vassalrack/proc/do_disloyalty(mob/living/user, mob/living/target)
+ if(disloyalty_offered)
+ return FALSE
+
+ disloyalty_offered = TRUE
+ to_chat(user, "[target] has been given the opportunity for servitude. You await their decision...")
+ var/alert_response = tgui_alert(
+ user = target, \
+ message = "You are being tortured! Do you want to give in and pledge your undying loyalty to [user]? \n\
+ You will not lose your current objectives, but they come second to the will of your new master!", \
+ title = "THE HORRIBLE PAIN! WHEN WILL IT END?!",
+ buttons = list("Accept", "Refuse"),
+ timeout = 10 SECONDS, \
+ autofocus = TRUE, \
+ )
+ switch(alert_response)
+ if("Accept")
+ disloyalty_confirm = TRUE
+ else
+ target.balloon_alert_to_viewers("stares defiantly", "refused vassalization!")
+ disloyalty_offered = FALSE
+ return TRUE
+
+/obj/structure/bloodsucker/vassalrack/proc/RequireDisloyalty(mob/living/user, mob/living/target)
+#ifdef BLOODSUCKER_TESTING
+ if(!target || !target.mind)
+#else
+ if(!target || !target.client)
+#endif
+ balloon_alert(user, "target has no mind!")
+ return VASSALIZATION_BANNED
+
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user)
+ return bloodsuckerdatum.AmValidAntag(target)
+
+/obj/structure/bloodsucker/vassalrack/proc/remove_loyalties(mob/living/target)
+ // Find Mind Implant & Destroy
+ for(var/obj/item/implant/all_implants as anything in target.implants)
+ if(all_implants.type == /obj/item/implant/mindshield)
+ all_implants.removed(target, silent = TRUE)
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/obj/structure/bloodsucker/candelabrum
+ name = "candelabrum"
+ desc = "It burns slowly, but doesn't radiate any heat."
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ icon_state = "candelabrum"
+ light_color = "#66FFFF"//LIGHT_COLOR_BLUEGREEN // lighting.dm
+ light_power = 3
+ density = FALSE
+ can_buckle = TRUE
+ anchored = FALSE
+ ghost_desc = "This is a magical candle which drains at the sanity of non Bloodsuckers and Vassals.\n\
+ Vassals can turn the candle on manually, while Bloodsuckers can do it from a distance."
+ bloodsucker_desc = "This is a magical candle which drains at the sanity of mortals who are not under your command while it is active.\n\
+ You can right-click on it from any range to turn it on remotely, or simply be next to it and click on it to turn it on and off normally."
+ vassal_desc = "This is a magical candle which drains at the sanity of the fools who havent yet accepted your master, as long as it is active.\n\
+ You can turn it on and off by clicking on it while you are next to it.\n\
+ If your Master is part of the Ventrue Clan, they utilize this to upgrade their Favorite Vassal."
+ curator_desc = "This is a blue Candelabrum, which causes insanity to those near it while active."
+ var/lit = FALSE
+
+/obj/structure/bloodsucker/candelabrum/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/structure/bloodsucker/candelabrum/update_icon_state()
+ icon_state = "candelabrum[lit ? "_lit" : ""]"
+ return ..()
+
+/obj/structure/bloodsucker/candelabrum/bolt()
+ . = ..()
+ set_anchored(TRUE)
+ density = TRUE
+
+/obj/structure/bloodsucker/candelabrum/unbolt()
+ . = ..()
+ set_anchored(FALSE)
+ density = FALSE
+
+/obj/structure/bloodsucker/candelabrum/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(!.)
+ return
+ if(anchored && (IS_VASSAL(user) || IS_BLOODSUCKER(user)))
+ toggle()
+ return ..()
+
+/obj/structure/bloodsucker/candelabrum/proc/toggle(mob/user)
+ lit = !lit
+ if(lit)
+ desc = initial(desc)
+ set_light(l_outer_range = 2, l_power = 3, l_color = "#66FFFF")
+ START_PROCESSING(SSobj, src)
+ else
+ desc = "Despite not being lit, it makes your skin crawl."
+ set_light(0)
+ STOP_PROCESSING(SSobj, src)
+ update_icon()
+
+/obj/structure/bloodsucker/candelabrum/process()
+ if(!lit)
+ return
+ for(var/mob/living/carbon/nearby_people in viewers(7, src))
+ /// We dont want Bloodsuckers or Vassals affected by this
+ if(IS_VASSAL(nearby_people) || IS_BLOODSUCKER(nearby_people) || IS_CURATOR(nearby_people))
+ continue
+ nearby_people.hallucination += 5 SECONDS
+ SEND_SIGNAL(nearby_people, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle)
+
+/// Blood Throne - Allows Bloodsuckers to remotely speak with their Vassals. - Code (Mostly) stolen from comfy chairs (armrests) and chairs (layers)
+/obj/structure/bloodsucker/bloodthrone
+ name = "wicked throne"
+ desc = "Twisted metal shards jut from the arm rests. Very uncomfortable looking. It would take a masochistic sort to sit on this jagged piece of furniture."
+ icon = 'icons/bloodsuckers/vamp_obj_64.dmi'
+ icon_state = "throne"
+ buckle_lying = 0
+ anchored = FALSE
+ density = TRUE
+ can_buckle = TRUE
+ ghost_desc = "This is a Bloodsucker throne, any Bloodsucker sitting on it can remotely speak to their Vassals by attempting to speak aloud."
+ bloodsucker_desc = "This is a blood throne, sitting on it will allow you to telepathically speak to your vassals by simply speaking."
+ vassal_desc = "This is a blood throne, it allows your Master to telepathically speak to you and others like you."
+ curator_desc = "This is a chair that hurts those that try to buckle themselves onto it, though the Undead have no problem latching on.\n\
+ While buckled, Monsters can use this to telepathically communicate with eachother."
+ var/mutable_appearance/armrest
+
+// Add rotating and armrest
+/obj/structure/bloodsucker/bloodthrone/Initialize()
+ AddComponent(/datum/component/simple_rotation)
+ armrest = GetArmrest()
+ armrest.layer = ABOVE_MOB_LAYER
+ return ..()
+
+/obj/structure/bloodsucker/bloodthrone/Destroy()
+ QDEL_NULL(armrest)
+ return ..()
+
+/obj/structure/bloodsucker/bloodthrone/bolt()
+ . = ..()
+ anchored = TRUE
+
+/obj/structure/bloodsucker/bloodthrone/unbolt()
+ . = ..()
+ anchored = FALSE
+
+// Armrests
+/obj/structure/bloodsucker/bloodthrone/proc/GetArmrest()
+ return mutable_appearance('icons/bloodsuckers/vamp_obj_64.dmi', "thronearm")
+
+/obj/structure/bloodsucker/bloodthrone/proc/update_armrest()
+ if(has_buckled_mobs())
+ add_overlay(armrest)
+ else
+ cut_overlay(armrest)
+
+// Rotating
+/obj/structure/bloodsucker/bloodthrone/setDir(newdir)
+ . = ..()
+ if(has_buckled_mobs())
+ for(var/m in buckled_mobs)
+ var/mob/living/buckled_mob = m
+ buckled_mob.setDir(newdir)
+
+ if(has_buckled_mobs() && dir == NORTH)
+ layer = ABOVE_MOB_LAYER
+ else
+ layer = OBJ_LAYER
+
+// Buckling
+/obj/structure/bloodsucker/bloodthrone/buckle_mob(mob/living/user, force = FALSE, check_loc = TRUE)
+ if(!anchored)
+ to_chat(user, "[src] is not bolted to the ground!")
+ return
+ . = ..()
+ user.visible_message(
+ "[user] sits down on [src].",
+ "You sit down onto [src].",
+ )
+ if(IS_BLOODSUCKER(user))
+ RegisterSignal(user, COMSIG_MOB_SAY, PROC_REF(handle_speech))
+ else
+ unbuckle_mob(user)
+ user.Paralyze(10 SECONDS)
+ to_chat(user, "The power of the blood throne overwhelms you!")
+
+/obj/structure/bloodsucker/bloodthrone/post_buckle_mob(mob/living/target)
+ . = ..()
+ update_armrest()
+ target.pixel_y += 2
+
+// Unbuckling
+/obj/structure/bloodsucker/bloodthrone/unbuckle_mob(mob/living/user, force = FALSE, can_fall = TRUE)
+ src.visible_message("[user] unbuckles themselves from [src].")
+ if(IS_BLOODSUCKER(user))
+ UnregisterSignal(user, COMSIG_MOB_SAY)
+ . = ..()
+
+/obj/structure/bloodsucker/bloodthrone/post_unbuckle_mob(mob/living/target)
+ target.pixel_y -= 2
+
+// The speech itself
+/obj/structure/bloodsucker/bloodthrone/proc/handle_speech(datum/source, mob/speech_args)
+ SIGNAL_HANDLER
+
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/mob/living/carbon/human/user = source
+ var/rendered = "[user.real_name]: [message]"
+ user.log_talk(message, LOG_SAY, tag=ROLE_BLOODSUCKER)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ for(var/datum/antagonist/vassal/receiver as anything in bloodsuckerdatum.vassals)
+ if(!receiver.owner.current)
+ continue
+ var/mob/receiver_mob = receiver.owner.current
+ to_chat(receiver_mob, rendered)
+ to_chat(user, rendered) // tell yourself, too.
+
+ for(var/mob/dead_mob in GLOB.dead_mob_list)
+ var/link = FOLLOW_LINK(dead_mob, user)
+ to_chat(dead_mob, "[link] [rendered]")
+
+ speech_args[SPEECH_MESSAGE] = ""
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index d552cbd2a255e..1e47b5b5242be 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -24,6 +24,9 @@
close_sound_volume = 50
drag_slowdown = 0
imacrate = TRUE
+ breakout_time = 20 SECONDS
+ var/mob/living/resident //The bloodsucker owner of this crate (or coffin)
+ var/pry_lid_timer = 25 SECONDS //The time it takes to pry this open with a crowbar
var/crate_climb_time = 20
var/azimuth_angle_2 = 180 //in this context the azimuth angle for over 90 degree
var/radius_2 = 1.35
@@ -153,23 +156,6 @@
manifest = null
update_icon()
-/obj/structure/closet/crate/coffin
- name = "coffin"
- desc = "It's a burial receptacle for the dearly departed."
- icon_state = "coffin"
- resistance_flags = FLAMMABLE
- max_integrity = 70
- material_drop = /obj/item/stack/sheet/wood
- material_drop_amount = 5
- open_sound = 'sound/machines/wooden_closet_open.ogg'
- close_sound = 'sound/machines/wooden_closet_close.ogg'
- open_sound_volume = 25
- close_sound_volume = 50
- door_anim_angle = 140
- azimuth_angle_2 = 180
- door_anim_time = 5
- door_hinge = 5
-
/obj/structure/closet/crate/internals
desc = "An internals crate."
name = "internals crate"
diff --git a/code/game/objects/structures/crates_lockers/crates/coffins.dm b/code/game/objects/structures/crates_lockers/crates/coffins.dm
new file mode 100644
index 0000000000000..b88441da106ee
--- /dev/null
+++ b/code/game/objects/structures/crates_lockers/crates/coffins.dm
@@ -0,0 +1,271 @@
+/obj/structure/closet/crate/coffin
+ name = "coffin"
+ desc = "It's a burial receptacle for the dearly departed."
+ icon_state = "coffin"
+ resistance_flags = FLAMMABLE
+ max_integrity = 70
+ material_drop = /obj/item/stack/sheet/wood
+ material_drop_amount = 5
+ open_sound = 'sound/machines/wooden_closet_open.ogg'
+ close_sound = 'sound/machines/wooden_closet_close.ogg'
+ open_sound_volume = 25
+ close_sound_volume = 50
+ door_anim_angle = 140
+ azimuth_angle_2 = 180
+ door_anim_time = 5
+ door_hinge = 5
+/obj/structure/closet/crate/coffin/examine(mob/user)
+ . = ..()
+ if(user == resident)
+ . += "This is your Claimed Coffin."
+ . += "Rest in it while injured to enter Torpor. Entering it with unspent Ranks will allow you to spend one."
+ . += "Alt-Click while inside the Coffin to Lock/Unlock."
+ . += "Alt-Click while outside of your Coffin to Unclaim it, unwrenching it and all your other structures as a result."
+
+/obj/structure/closet/crate/coffin/blackcoffin
+ name = "black coffin"
+ desc = "For those departed who are not so dear."
+ icon_state = "coffin"
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ open_sound = 'sound/bloodsuckers/coffin_open.ogg'
+ close_sound = 'sound/bloodsuckers/coffin_close.ogg'
+ breakout_time = 30 SECONDS
+ pry_lid_timer = 20 SECONDS
+ resistance_flags = NONE
+ material_drop = /obj/item/stack/sheet/iron
+ material_drop_amount = 2
+ armor_type = /datum/armor/blackcoffin
+
+/datum/armor/blackcoffin
+ melee = 50
+ bullet = 20
+ laser = 30
+ bomb = 50
+ fire = 70
+ acid = 60
+
+/obj/structure/closet/crate/coffin/securecoffin
+ name = "secure coffin"
+ desc = "For those too scared of having their place of rest disturbed."
+ icon_state = "securecoffin"
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ open_sound = 'sound/bloodsuckers/coffin_open.ogg'
+ close_sound = 'sound/bloodsuckers/coffin_close.ogg'
+ breakout_time = 35 SECONDS
+ pry_lid_timer = 35 SECONDS
+ resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF
+ material_drop = /obj/item/stack/sheet/iron
+ material_drop_amount = 2
+ armor_type = /datum/armor/securecoffin
+
+/datum/armor/securecoffin
+ melee = 35
+ bullet = 20
+ laser = 20
+ bomb = 100
+ fire = 100
+ acid = 100
+
+/obj/structure/closet/crate/coffin/meatcoffin
+ name = "meat coffin"
+ desc = "When you're ready to meat your maker, the steaks can never be too high."
+ icon_state = "meatcoffin"
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ resistance_flags = FIRE_PROOF
+ open_sound = 'sound/effects/footstep/slime1.ogg'
+ close_sound = 'sound/effects/footstep/slime1.ogg'
+ breakout_time = 25 SECONDS
+ pry_lid_timer = 20 SECONDS
+ material_drop = /obj/item/food/meat/slab/human
+ material_drop_amount = 3
+ armor_type = /datum/armor/meatcoffin
+
+/datum/armor/meatcoffin
+ melee = 70
+ bullet = 10
+ laser = 10
+ bomb = 70
+ fire = 70
+ acid = 60
+
+/obj/structure/closet/crate/coffin/metalcoffin
+ name = "metal coffin"
+ desc = "A big metal sardine can inside of another big metal sardine can, in space."
+ icon_state = "metalcoffin"
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ resistance_flags = FIRE_PROOF | LAVA_PROOF
+ open_sound = 'sound/effects/pressureplate.ogg'
+ close_sound = 'sound/effects/pressureplate.ogg'
+ breakout_time = 25 SECONDS
+ pry_lid_timer = 30 SECONDS
+ material_drop = /obj/item/stack/sheet/iron
+ material_drop_amount = 5
+ armor_type = /datum/armor/metalcoffin
+
+/datum/armor/metalcoffin
+ melee = 40
+ bullet = 15
+ laser = 50
+ bomb = 10
+ fire = 70
+ acid = 60
+
+/// NOTE: This can be any coffin that you are resting AND inside of.
+/obj/structure/closet/crate/coffin/proc/claim_coffin(mob/living/claimant, area/current_area)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = claimant.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ // Successfully claimed?
+ if(bloodsuckerdatum.claim_coffin(src, current_area))
+ resident = claimant
+ anchored = TRUE
+ START_PROCESSING(SSprocessing, src)
+
+/obj/structure/closet/crate/coffin/Destroy()
+ unclaim_coffin()
+ STOP_PROCESSING(SSprocessing, src)
+ return ..()
+
+/obj/structure/closet/crate/coffin/process(mob/living/user)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(user in src)
+ var/list/turf/area_turfs = get_area_turfs(get_area(src))
+ // Create Dirt etc.
+ var/turf/T_Dirty = pick(area_turfs)
+ if(T_Dirty && !T_Dirty.density)
+ // Default: Dirt
+ // STEP ONE: COBWEBS
+ // CHECK: Wall to North?
+ var/turf/check_N = get_step(T_Dirty, NORTH)
+ if(istype(check_N, /turf/closed/wall))
+ // CHECK: Wall to West?
+ var/turf/check_W = get_step(T_Dirty, WEST)
+ if(istype(check_W, /turf/closed/wall))
+ new /obj/effect/decal/cleanable/cobweb(T_Dirty)
+ // CHECK: Wall to East?
+ var/turf/check_E = get_step(T_Dirty, EAST)
+ if(istype(check_E, /turf/closed/wall))
+ new /obj/effect/decal/cleanable/cobweb/cobweb2(T_Dirty)
+ new /obj/effect/decal/cleanable/dirt(T_Dirty)
+
+/obj/structure/closet/crate/proc/unclaim_coffin(manual = FALSE)
+ // Unanchor it (If it hasn't been broken, anyway)
+ anchored = FALSE
+ if(!resident || !resident.mind)
+ return
+ // Unclaiming
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = resident.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum && bloodsuckerdatum.coffin == src)
+ bloodsuckerdatum.coffin = null
+ bloodsuckerdatum.bloodsucker_lair_area = null
+ for(var/obj/structure/bloodsucker/bloodsucker_structure in get_area(src))
+ if(bloodsucker_structure.owner == resident)
+ bloodsucker_structure.unbolt()
+ if(manual)
+ to_chat(resident, "You have unclaimed your coffin! This also unclaims all your other Bloodsucker structures!")
+ else
+ to_chat(resident, "You sense that the link with your coffin and your sacred lair has been broken! You will need to seek another.")
+ // Remove resident. Because this objec (GC?) we need to give them a way to see they don't have a home anymore.
+ resident = null
+
+/// You cannot lock in/out a coffin's owner. SORRY.
+/obj/structure/closet/crate/coffin/can_open(mob/living/user)
+ if(!locked)
+ return ..()
+ if(user == resident)
+ if(welded)
+ welded = FALSE
+ update_icon()
+ locked = FALSE
+ return TRUE
+ playsound(get_turf(src), 'sound/machines/door_locked.ogg', 20, 1)
+ to_chat(user, "[src] appears to be locked tight from the inside.")
+
+/obj/structure/closet/crate/coffin/close(mob/living/user)
+ . = ..()
+ if(!.)
+ return FALSE
+ // Only the User can put themself into Torpor. If already in it, you'll start to heal.
+ if(user in src)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(!bloodsuckerdatum)
+ return FALSE
+ var/area/current_area = get_area(src)
+ if(!bloodsuckerdatum.coffin && !resident)
+ switch(tgui_alert(user, "Do you wish to claim this as your coffin? [current_area] will be your lair.", "Claim Lair", list("Yes", "No")))
+ if("Yes")
+ claim_coffin(user, current_area)
+ if("No")
+ return
+ LockMe(user)
+ //Level up if possible.
+ if(!bloodsuckerdatum.my_clan)
+ to_chat(user, "You must enter a Clan to rank up.")
+ else
+ bloodsuckerdatum.SpendRank()
+ // You're in a Coffin, everything else is done, you're likely here to heal. Let's offer them the oppertunity to do so.
+ bloodsuckerdatum.check_begin_torpor()
+ return TRUE
+
+/// You cannot weld or deconstruct an owned coffin. Only the owner can destroy their own coffin.
+/obj/structure/closet/crate/coffin/attackby(obj/item/item, mob/user, params)
+ if(!resident)
+ return ..()
+ if(user != resident)
+ if(istype(item, cutting_tool))
+ to_chat(user, "This is a much more complex mechanical structure than you thought. You don't know where to begin cutting [src].")
+ return
+ if(anchored && (item.tool_behaviour == TOOL_WRENCH))
+ to_chat(user, "The coffin won't come unanchored from the floor.[user == resident ? " You can Alt-Click to unclaim and unwrench your Coffin." : ""]")
+ return
+
+ if(locked && (item.tool_behaviour == TOOL_CROWBAR))
+ var/pry_time = pry_lid_timer * item.toolspeed // Pry speed must be affected by the speed of the tool.
+ user.visible_message(
+ "[user] tries to pry the lid off of [src] with [item].",
+ "You begin prying the lid off of [src] with [item]. This should take about [DisplayTimeText(pry_time)].",
+ )
+ if(!do_after(user, pry_time, src))
+ return
+ bust_open()
+ user.visible_message(
+ "[user] snaps the door of [src] wide open.",
+ "The door of [src] snaps open.",
+ )
+ return
+ return ..()
+
+/// Distance Check (Inside Of)
+/obj/structure/closet/crate/coffin/AltClick(mob/user)
+ . = ..()
+ if(user in src)
+ LockMe(user, !locked)
+ return
+
+ if(user == resident && user.Adjacent(src))
+ balloon_alert(user, "unclaim coffin?")
+ var/static/list/unclaim_options = list(
+ "Yes" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_no"))
+ var/unclaim_response = show_radial_menu(user, src, unclaim_options, radius = 36, require_near = TRUE)
+ switch(unclaim_response)
+ if("Yes")
+ unclaim_coffin(TRUE)
+
+/obj/structure/closet/crate/proc/LockMe(mob/user, inLocked = TRUE)
+ if(user == resident)
+ if(!broken)
+ locked = inLocked
+ if(locked)
+ to_chat(user, "You flip a secret latch and lock yourself inside [src].")
+ else
+ to_chat(user, "You flip a secret latch and unlock [src].")
+ return
+ // Broken? Let's fix it.
+ to_chat(resident, "The secret latch that would lock [src] from the inside is broken. You set it back into place...")
+ if(!do_after(resident, 5 SECONDS, src))
+ to_chat(resident, "You fail to fix [src]'s mechanism.")
+ return
+ to_chat(resident, "You fix the mechanism and lock it.")
+ broken = FALSE
+ locked = TRUE
diff --git a/code/modules/antagonists/bloodsucker/clans/_clan.dm b/code/modules/antagonists/bloodsucker/clans/_clan.dm
new file mode 100644
index 0000000000000..37279d7aa87fd
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/clans/_clan.dm
@@ -0,0 +1,260 @@
+/**
+ * Bloodsucker clans
+ *
+ * Handles everything related to clans.
+ * the entire idea of datumizing this came to me in a dream.
+ */
+/datum/bloodsucker_clan
+ ///The bloodsucker datum that owns this clan. Use this over 'source', because while it's the same thing, this is more consistent (and used for deletion).
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum
+ ///The name of the clan we're in.
+ var/name = CLAN_NONE
+ ///Description of what the clan is, given when joining and through your antag UI.
+ var/description = "The Caitiff is as basic as you can get with Bloodsuckers. \n\
+ Entirely Clan-less, they are blissfully unaware of who they really are. \n\
+ No additional abilities is gained, nothing is lost, if you want a plain Bloodsucker, this is it. \n\
+ The Favorite Vassal will gain the Brawn ability, to help in combat."
+ ///The clan objective that is required to greentext.
+ var/datum/objective/bloodsucker/clan_objective
+ ///The icon of the radial icon to join this clan.
+ var/join_icon = 'icons/bloodsuckers/clan_icons.dmi'
+ ///Same as join_icon, but the state
+ var/join_icon_state = "caitiff"
+ ///Description shown when trying to join the clan.
+ var/join_description = "The default, Classic Bloodsucker."
+ ///Whether the clan can be joined by players. FALSE for flavortext-only clans.
+ var/joinable_clan = TRUE
+
+ ///How we will drink blood using Feed.
+ var/blood_drink_type = BLOODSUCKER_DRINK_NORMAL
+
+/datum/bloodsucker_clan/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ src.bloodsuckerdatum = owner_datum
+
+ RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_ON_LIFETICK, PROC_REF(handle_clan_life))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_RANK_UP, PROC_REF(on_spend_rank))
+
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_INTERACT_WITH_VASSAL, PROC_REF(on_interact_with_vassal))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MAKE_FAVORITE, PROC_REF(on_favorite_vassal))
+
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, PROC_REF(on_vassal_made))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_EXIT_TORPOR, PROC_REF(on_exit_torpor))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_FINAL_DEATH, PROC_REF(on_final_death))
+
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_ENTERS_FRENZY, PROC_REF(on_enter_frenzy))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_EXITS_FRENZY, PROC_REF(on_exit_frenzy))
+
+ give_clan_objective()
+
+/datum/bloodsucker_clan/Destroy(force)
+ UnregisterSignal(bloodsuckerdatum, list(
+ COMSIG_BLOODSUCKER_ON_LIFETICK,
+ BLOODSUCKER_RANK_UP,
+ BLOODSUCKER_INTERACT_WITH_VASSAL,
+ BLOODSUCKER_MAKE_FAVORITE,
+ BLOODSUCKER_MADE_VASSAL,
+ BLOODSUCKER_EXIT_TORPOR,
+ BLOODSUCKER_FINAL_DEATH,
+ BLOODSUCKER_ENTERS_FRENZY,
+ BLOODSUCKER_EXITS_FRENZY,
+ ))
+ remove_clan_objective()
+ bloodsuckerdatum = null
+ return ..()
+
+/datum/bloodsucker_clan/proc/on_enter_frenzy(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+ var/mob/living/carbon/human/human_bloodsucker = bloodsuckerdatum.owner.current
+ if(!istype(human_bloodsucker))
+ return
+ human_bloodsucker.physiology.stamina_mod *= 0.4
+
+/datum/bloodsucker_clan/proc/on_exit_frenzy(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+ var/mob/living/carbon/human/human_bloodsucker = bloodsuckerdatum.owner.current
+ if(!istype(human_bloodsucker))
+ return
+ human_bloodsucker.set_dizziness(3 SECONDS)
+ human_bloodsucker.Paralyze(2 SECONDS)
+ human_bloodsucker.physiology.stamina_mod /= 0.4
+
+/datum/bloodsucker_clan/proc/give_clan_objective()
+ if(isnull(clan_objective))
+ return
+ clan_objective = new clan_objective()
+ clan_objective.name = "Clan Objective"
+ clan_objective.owner = bloodsuckerdatum.owner
+ bloodsuckerdatum.objectives += clan_objective
+ bloodsuckerdatum.owner.announce_objectives()
+
+/datum/bloodsucker_clan/proc/remove_clan_objective()
+ bloodsuckerdatum.objectives -= clan_objective
+ QDEL_NULL(clan_objective)
+ bloodsuckerdatum.owner.announce_objectives()
+
+/**
+ * Called when a Bloodsucker exits Torpor
+ * args:
+ * source - the Bloodsucker exiting Torpor
+ */
+/datum/bloodsucker_clan/proc/on_exit_torpor(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+
+/**
+ * Called when a Bloodsucker enters Final Death
+ * args:
+ * source - the Bloodsucker exiting Torpor
+ */
+/datum/bloodsucker_clan/proc/on_final_death(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+ return FALSE
+
+/**
+ * Called during Bloodsucker's LifeTick
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this.
+ */
+/datum/bloodsucker_clan/proc/handle_clan_life(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+
+/**
+ * Called when a Bloodsucker successfully Vassalizes someone.
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this.
+ */
+/datum/bloodsucker_clan/proc/on_vassal_made(datum/antagonist/bloodsucker/source, mob/living/user, mob/living/target)
+ SIGNAL_HANDLER
+ user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
+ target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
+ target.Jitter(15 SECONDS)
+ INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, emote), "laugh")
+
+/**
+ * Called when a Bloodsucker successfully starts spending their Rank
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this.
+ * target - The Vassal (if any) we are upgrading.
+ * cost_rank - TRUE/FALSE on whether this will cost us a rank when we go through with it.
+ * blood_cost - A number saying how much it costs to rank up.
+ */
+/datum/bloodsucker_clan/proc/on_spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(spend_rank), bloodsuckerdatum, target, cost_rank, blood_cost)
+
+/datum/bloodsucker_clan/proc/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
+ // Purchase Power Prompt
+ var/list/options = list()
+ for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
+ if(initial(power.purchase_flags) & BLOODSUCKER_CAN_BUY && !(locate(power) in bloodsuckerdatum.powers))
+ options[initial(power.name)] = power
+
+ if(options.len < 1)
+ to_chat(bloodsuckerdatum.owner.current, "You grow more ancient by the night!")
+ else
+ // Give them the UI to purchase a power.
+ var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Select a power to advance your Rank.", "Your Blood Thickens...", options)
+ // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
+ if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0)
+ return
+ // Did you choose a power?
+ if(!choice || !options[choice])
+ to_chat(bloodsuckerdatum.owner.current, "You prevent your blood from thickening just yet, but you may try again later.")
+ return
+ // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
+ if(locate(options[choice]) in bloodsuckerdatum.powers)
+ to_chat(bloodsuckerdatum.owner.current, "You prevent your blood from thickening just yet, but you may try again later.")
+ return
+ // Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
+ if(!istype(bloodsuckerdatum.owner.current.loc, /obj/structure/closet/crate/coffin))
+ to_chat(bloodsuckerdatum.owner.current, "You must be in your Coffin to purchase Powers.")
+ return
+
+ // Good to go - Buy Power!
+ var/datum/action/cooldown/bloodsucker/purchased_power = options[choice]
+ bloodsuckerdatum.BuyPower(new purchased_power)
+ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "learned [choice]!")
+ to_chat(bloodsuckerdatum.owner.current, "You have learned how to use [choice]!")
+
+ finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
+
+/datum/bloodsucker_clan/proc/finalize_spend_rank(datum/antagonist/bloodsucker/source, cost_rank = TRUE, blood_cost)
+ bloodsuckerdatum.LevelUpPowers()
+ bloodsuckerdatum.bloodsucker_regen_rate += 0.05
+ bloodsuckerdatum.max_blood_volume += 100
+
+ var/mob/living/carbon/human/bloodsucker_human = bloodsuckerdatum.owner.current
+ if(ishuman(bloodsucker_human))
+ bloodsucker_human.dna.species.punchdamage += 0.5
+ // We're almost done - Spend your Rank now.
+ bloodsuckerdatum.bloodsucker_level++
+ if(cost_rank)
+ bloodsuckerdatum.bloodsucker_level_unspent--
+ if(blood_cost)
+ bloodsuckerdatum.AddBloodVolume(-blood_cost)
+
+ // Ranked up enough to get your true Reputation?
+ if(bloodsuckerdatum.bloodsucker_level == 4)
+ bloodsuckerdatum.SelectReputation(am_fledgling = FALSE, forced = TRUE)
+
+ to_chat(bloodsuckerdatum.owner.current, "You are now a rank [bloodsuckerdatum.bloodsucker_level] Bloodsucker. \
+ Your strength, health, feed rate, regen rate, and maximum blood capacity have all increased! \n\
+ * Your existing powers have all ranked up as well!")
+ bloodsuckerdatum.owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, TRUE, pressure_affected = FALSE)
+ bloodsuckerdatum.update_hud()
+
+/**
+ * Called when we are trying to turn someone into a Favorite Vassal
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker performing this.
+ * vassaldatum - the antagonist datum of the Vassal being offered up.
+ */
+/datum/bloodsucker_clan/proc/on_interact_with_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(interact_with_vassal), bloodsuckerdatum, vassaldatum)
+
+/datum/bloodsucker_clan/proc/interact_with_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ if(vassaldatum.special_type)
+ to_chat(bloodsuckerdatum.owner.current, "This Vassal was already assigned a special position.")
+ return FALSE
+ if(!vassaldatum.owner.can_make_special(creator = bloodsuckerdatum.owner))
+ to_chat(bloodsuckerdatum.owner.current, "This Vassal is unable to gain a Special rank due to innate features.")
+ return FALSE
+
+ var/list/options = list()
+ var/list/radial_display = list()
+ for(var/datum/antagonist/vassal/vassaldatums as anything in subtypesof(/datum/antagonist/vassal))
+ if(bloodsuckerdatum.special_vassals[initial(vassaldatums.special_type)])
+ continue
+ options[initial(vassaldatums.name)] = vassaldatums
+
+ var/datum/radial_menu_choice/option = new
+ option.image = image(icon = 'icons/bloodsuckers/bloodsucker_icons.dmi', icon_state = initial(vassaldatums.vassal_hud_name))
+ option.info = "[initial(vassaldatums.name)] - ["[initial(vassaldatums.vassal_description)]"]"
+ radial_display[initial(vassaldatums.name)] = option
+
+ if(!options.len)
+ return
+
+ to_chat(bloodsuckerdatum.owner.current, "You can change who this Vassal is, who are they to you?")
+ var/vassal_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, radial_display)
+ if(!vassal_response)
+ return
+ vassal_response = options[vassal_response]
+ if(QDELETED(src) || QDELETED(bloodsuckerdatum.owner.current) || QDELETED(vassaldatum.owner.current))
+ return FALSE
+ vassaldatum.make_special(vassal_response)
+ bloodsuckerdatum.bloodsucker_blood_volume -= 150
+ return TRUE
+
+/**
+ * Called when we are successfully turn a Vassal into a Favorite Vassal
+ * args:
+ * bloodsuckerdatum - antagonist datum of the Bloodsucker who turned them into a Vassal.
+ * vassaldatum - the antagonist datum of the Vassal being offered up.
+ */
+/datum/bloodsucker_clan/proc/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ SIGNAL_HANDLER
+ vassaldatum.BuyPower(new /datum/action/cooldown/bloodsucker/targeted/brawn)
diff --git a/code/modules/antagonists/bloodsucker/clans/assignclan.dm b/code/modules/antagonists/bloodsucker/clans/assignclan.dm
new file mode 100644
index 0000000000000..ec29309beb100
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/clans/assignclan.dm
@@ -0,0 +1,47 @@
+/**
+ * Gives Bloodsuckers the ability to choose a Clan.
+ * If they are already in a Clan, or is in a Frenzy, they will not be able to do so.
+ * The arg is optional and should really only be an Admin setting a Clan for a player.
+ * If set however, it will give them the control of their Clan instead of the Bloodsucker.
+ * This is selected through a radial menu over the player's body, even when an Admin is setting it.
+ * Args:
+ * person_selecting - Mob override for stuff like Admins selecting someone's clan.
+ */
+/datum/antagonist/bloodsucker/proc/assign_clan_and_bane(mob/person_selecting)
+ if(my_clan)
+ return
+ if(owner.current.has_status_effect(/datum/status_effect/frenzy))
+ return
+ if(!person_selecting)
+ person_selecting = owner.current
+
+ var/list/options = list()
+ var/list/radial_display = list()
+ for(var/datum/bloodsucker_clan/all_clans as anything in typesof(/datum/bloodsucker_clan))
+ if(!initial(all_clans.joinable_clan)) //flavortext only
+ continue
+ options[initial(all_clans.name)] = all_clans
+
+ var/datum/radial_menu_choice/option = new
+ option.image = image(icon = initial(all_clans.join_icon), icon_state = initial(all_clans.join_icon_state))
+ option.info = "[initial(all_clans.name)] - ["[initial(all_clans.join_description)]"]"
+ radial_display[initial(all_clans.name)] = option
+
+ var/chosen_clan = show_radial_menu(person_selecting, owner.current, radial_display)
+ chosen_clan = options[chosen_clan]
+ if(QDELETED(src) || QDELETED(owner.current))
+ return FALSE
+ if(!chosen_clan)
+ to_chat(person_selecting, "You choose to remain ignorant, for now.")
+ return
+ my_clan = new chosen_clan(src)
+
+/datum/antagonist/bloodsucker/proc/remove_clan(mob/admin)
+ if(owner.current.has_status_effect(/datum/status_effect/frenzy))
+ to_chat(admin, "Removing a Bloodsucker from a Clan while they are in a Frenzy will break stuff, this action has been blocked.")
+ return
+ QDEL_NULL(my_clan)
+ to_chat(owner.current, "You have been forced out of your clan! You can re-enter one by regular means.")
+
+/datum/antagonist/bloodsucker/proc/admin_set_clan(mob/admin)
+ assign_clan_and_bane(admin)
diff --git a/code/modules/antagonists/bloodsucker/clans/clan_flavortext.dm b/code/modules/antagonists/bloodsucker/clans/clan_flavortext.dm
new file mode 100644
index 0000000000000..16f9c75f7eda6
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/clans/clan_flavortext.dm
@@ -0,0 +1,48 @@
+// These have no functionality. They're just flavortext for the Archive of the Kindred
+/datum/bloodsucker_clan/gangrel
+ name = CLAN_GANGREL
+ description = "Closer to Animals than Bloodsuckers, known as Werewolves waiting to happen, \n\
+ these are the most fearful of True Faith, being the most lethal thing they would ever see the night of. \n\
+ Full Moons do not seem to have an effect, despite common-told stories. \n\
+ The Favorite Vassal turns into a Werewolf whenever their Master does."
+ joinable_clan = FALSE
+ blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY
+
+/datum/bloodsucker_clan/gangrel/on_enter_frenzy(datum/antagonist/bloodsucker/source)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_STUNIMMUNE, FRENZY_TRAIT)
+
+/datum/bloodsucker_clan/gangrel/on_exit_frenzy(datum/antagonist/bloodsucker/source)
+ REMOVE_TRAIT(bloodsuckerdatum.owner.current, TRAIT_STUNIMMUNE, FRENZY_TRAIT)
+
+/datum/bloodsucker_clan/gangrel/handle_clan_life(datum/antagonist/bloodsucker/source)
+ . = ..()
+ var/area/current_area = get_area(bloodsuckerdatum.owner.current)
+ if(istype(current_area, /area/chapel))
+ to_chat(bloodsuckerdatum.owner.current, "You don't belong in holy areas! The Faith burns you!")
+ bloodsuckerdatum.owner.current.adjustFireLoss(20)
+ bloodsuckerdatum.owner.current.adjust_fire_stacks(2)
+ bloodsuckerdatum.owner.current.IgniteMob()
+
+/datum/bloodsucker_clan/toreador
+ name = CLAN_TOREADOR
+ description = "The most charming Clan of them all, allowing them to very easily disguise among the crew. \n\
+ More in touch with their morals, they suffer and benefit more strongly from humanity cost or gain of their actions. \n\
+ Known as 'The most humane kind of vampire', they have an obsession with perfectionism and beauty \n\
+ The Favorite Vassal gains the Mesmerize ability."
+ joinable_clan = FALSE
+ blood_drink_type = BLOODSUCKER_DRINK_SNOBBY
+
+/datum/bloodsucker_clan/brujah
+ name = CLAN_BRUJAH
+ description = "The Brujah Clan has proven to be the strongest in melee combat, boasting a powerful punch. \n\
+ They also appear to be more calm than the others, entering their 'frenzies' whenever they want, but dont seem affected much by them. \n\
+ Be wary, as they are fearsome warriors, rebels and anarchists, with an inclination towards Frenzy. \n\
+ The Favorite Vassal gains brawn and a massive increase in brute damage from punching."
+ joinable_clan = FALSE
+
+/datum/bloodsucker_clan/tzimisce
+ name = CLAN_TZIMISCE
+ description = "The Tzimisce Clan has no knowledge about it. \n\
+ If you see one, you should probably run away.\n\
+ *the rest of the page is full of undecipherable scribbles...*"
+ joinable_clan = FALSE
diff --git a/code/modules/antagonists/bloodsucker/clans/malkavian.dm b/code/modules/antagonists/bloodsucker/clans/malkavian.dm
new file mode 100644
index 0000000000000..58040e368a4af
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/clans/malkavian.dm
@@ -0,0 +1,72 @@
+/datum/bloodsucker_clan/malkavian
+ name = CLAN_MALKAVIAN
+ description = "Little is documented about Malkavians. Complete insanity is the most common theme. \n\
+ The Favorite Vassal will suffer the same fate as the Master."
+ join_icon_state = "malkavian"
+ join_description = "Completely insane. You gain constant hallucinations, become a prophet with unintelligable rambling, \
+ and become the enforcer of the Masquerade code."
+ blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY
+
+/datum/bloodsucker_clan/malkavian/on_enter_frenzy(datum/antagonist/bloodsucker/source)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_STUNIMMUNE, FRENZY_TRAIT)
+
+/datum/bloodsucker_clan/malkavian/on_exit_frenzy(datum/antagonist/bloodsucker/source)
+ REMOVE_TRAIT(bloodsuckerdatum.owner.current, TRAIT_STUNIMMUNE, FRENZY_TRAIT)
+
+/datum/bloodsucker_clan/malkavian/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ RegisterSignal(SSdcs, COMSIG_BLOODSUCKER_BROKE_MASQUERADE, PROC_REF(on_bloodsucker_broke_masquerade))
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_XRAY_VISION, BLOODSUCKER_TRAIT)
+ var/mob/living/carbon/carbon_owner = bloodsuckerdatum.owner.current
+ if(istype(carbon_owner))
+ carbon_owner.gain_trauma(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE)
+ carbon_owner.gain_trauma(/datum/brain_trauma/special/bluespace_prophet, TRAUMA_RESILIENCE_ABSOLUTE)
+ owner_datum.owner.current.update_sight()
+
+ bloodsuckerdatum.owner.current.playsound_local(get_turf(bloodsuckerdatum.owner.current), 'sound/ambience/antag/creepalert.ogg', 80, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
+ to_chat(bloodsuckerdatum.owner.current, "Welcome to the Malkavian...")
+
+/datum/bloodsucker_clan/malkavian/Destroy(force)
+ UnregisterSignal(SSdcs, COMSIG_BLOODSUCKER_BROKE_MASQUERADE)
+ REMOVE_TRAIT(bloodsuckerdatum.owner.current, TRAIT_XRAY_VISION, BLOODSUCKER_TRAIT)
+ var/mob/living/carbon/carbon_owner = bloodsuckerdatum.owner.current
+ if(istype(carbon_owner))
+ carbon_owner.cure_trauma_type(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE)
+ carbon_owner.cure_trauma_type(/datum/brain_trauma/special/bluespace_prophet, TRAUMA_RESILIENCE_ABSOLUTE)
+ bloodsuckerdatum.owner.current.update_sight()
+ return ..()
+
+/datum/bloodsucker_clan/malkavian/handle_clan_life(datum/antagonist/bloodsucker/source)
+ . = ..()
+ if(prob(85) || bloodsuckerdatum.owner.current.stat != CONSCIOUS || HAS_TRAIT(bloodsuckerdatum.owner.current, TRAIT_MASQUERADE))
+ return
+ var/message = pick(strings("malkavian_revelations.json", "revelations", "fulp_modules/strings/bloodsuckers"))
+ INVOKE_ASYNC(bloodsuckerdatum.owner.current, /atom/movable/proc/say, message, , , , , , CLAN_MALKAVIAN)
+
+/datum/bloodsucker_clan/malkavian/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ var/mob/living/carbon/carbonowner = vassaldatum.owner.current
+ if(istype(carbonowner))
+ carbonowner.gain_trauma(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE)
+ carbonowner.gain_trauma(/datum/brain_trauma/special/bluespace_prophet/phobetor, TRAUMA_RESILIENCE_ABSOLUTE)
+ to_chat(vassaldatum.owner.current, "Additionally, you now suffer the same fate as your Master.")
+
+/datum/bloodsucker_clan/malkavian/on_exit_torpor(datum/antagonist/bloodsucker/source)
+ var/mob/living/carbon/carbonowner = bloodsuckerdatum.owner.current
+ if(istype(carbonowner))
+ carbonowner.gain_trauma(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE)
+ carbonowner.gain_trauma(/datum/brain_trauma/special/bluespace_prophet, TRAUMA_RESILIENCE_ABSOLUTE)
+
+/datum/bloodsucker_clan/malkavian/on_final_death(datum/antagonist/bloodsucker/source)
+ var/obj/item/soulstone/bloodsucker/stone = new /obj/item/soulstone/bloodsucker(get_turf(bloodsuckerdatum.owner.current))
+ stone.transfer_soul("FORCE", bloodsuckerdatum.owner.current, bloodsuckerdatum = bloodsuckerdatum)
+ return DONT_DUST
+
+/datum/bloodsucker_clan/malkavian/proc/on_bloodsucker_broke_masquerade(datum/antagonist/bloodsucker/masquerade_breaker)
+ SIGNAL_HANDLER
+ to_chat(bloodsuckerdatum.owner.current, "[masquerade_breaker.owner.current] has broken the Masquerade! Ensure [masquerade_breaker.owner.current.p_they()] [masquerade_breaker.owner.current.p_are()] eliminated at all costs!")
+ var/datum/objective/assassinate/masquerade_objective = new()
+ masquerade_objective.target = masquerade_breaker.owner.current
+ masquerade_objective.name = "Clan Objective"
+ masquerade_objective.explanation_text = "Ensure [masquerade_breaker.owner.current], who has broken the Masquerade, succumbs to Final Death."
+ bloodsuckerdatum.objectives += masquerade_objective
+ bloodsuckerdatum.owner.announce_objectives()
diff --git a/code/modules/antagonists/bloodsucker/clans/nosferatu.dm b/code/modules/antagonists/bloodsucker/clans/nosferatu.dm
new file mode 100644
index 0000000000000..e9d56478d5be1
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/clans/nosferatu.dm
@@ -0,0 +1,45 @@
+/datum/bloodsucker_clan/nosferatu
+ name = CLAN_NOSFERATU
+ description = "The Nosferatu Clan is unable to blend in with the crew, with no abilities such as Masquerade and Veil. \n\
+ Additionally, has a permanent bad back and looks like a Bloodsucker upon a simple examine, and is entirely unidentifiable, \n\
+ they can fit in the vents regardless of their form and equipment. \n\
+ The Favorite Vassal is permanetly disfigured, and can also ventcrawl, but only while entirely nude."
+ clan_objective = /datum/objective/bloodsucker/kindred
+ join_icon_state = "nosferatu"
+ join_description = "You are permanetly disfigured, look like a Bloodsucker to all who examine you, \
+ lose your Masquerade ability, but gain the ability to Ventcrawl even while clothed."
+ blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY
+
+/datum/bloodsucker_clan/nosferatu/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.powers)
+ if(istype(power, /datum/action/cooldown/bloodsucker/masquerade) || istype(power, /datum/action/cooldown/bloodsucker/veil))
+ bloodsuckerdatum.RemovePower(power)
+
+ //if(!bloodsuckerdatum.owner.current.has_quirk(/datum/quirk/badback))
+ // var/datum/quirk/badback/q = new /datum/quirk/badback
+ // q.transfer_mob(bloodsuckerdatum.owner.current)
+
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_DISFIGURED, BLOODSUCKER_TRAIT)
+ bloodsuckerdatum.owner.current.ventcrawler = VENTCRAWLER_ALWAYS
+
+/datum/bloodsucker_clan/nosferatu/Destroy(force)
+ for(var/datum/action/cooldown/bloodsucker/power in bloodsuckerdatum.powers)
+ bloodsuckerdatum.RemovePower(power)
+ bloodsuckerdatum.give_starting_powers()
+ //bloodsuckerdatum.owner.current.remove_quirk(/datum/quirk/badback)
+
+ REMOVE_TRAIT(bloodsuckerdatum.owner.current, TRAIT_DISFIGURED, BLOODSUCKER_TRAIT)
+ bloodsuckerdatum.owner.current.ventcrawler = VENTCRAWLER_NONE
+ return ..()
+
+/datum/bloodsucker_clan/nosferatu/handle_clan_life(datum/antagonist/bloodsucker/source)
+ . = ..()
+ if(!HAS_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NO_BLOOD))
+ bloodsuckerdatum.owner.current.blood_volume = BLOOD_VOLUME_SURVIVE
+
+/datum/bloodsucker_clan/nosferatu/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ vassaldatum.owner.current.ventcrawler = VENTCRAWLER_NUDE
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_DISFIGURED, BLOODSUCKER_TRAIT)
+
+ to_chat(vassaldatum.owner.current, "Additionally, you can now ventcrawl while naked, and are permanently disfigured.")
diff --git a/code/modules/antagonists/bloodsucker/clans/tremere.dm b/code/modules/antagonists/bloodsucker/clans/tremere.dm
new file mode 100644
index 0000000000000..c552a4e464f0a
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/clans/tremere.dm
@@ -0,0 +1,82 @@
+/datum/bloodsucker_clan/tremere
+ name = CLAN_TREMERE
+ description = "The Tremere Clan is extremely weak to True Faith, and will burn when entering areas considered such, like the Chapel. \n\
+ Additionally, a whole new moveset is learned, built on Blood magic rather than Blood abilities, which are upgraded overtime. \n\
+ More ranks can be gained by Vassalizing crewmembers. \n\
+ The Favorite Vassal gains the Batform spell, being able to morph themselves at will."
+ clan_objective = /datum/objective/bloodsucker/tremere_power
+ join_icon_state = "tremere"
+ join_description = "You will burn if you enter the Chapel, lose all default powers, \
+ but gain Blood Magic instead, powers you level up overtime."
+
+/datum/bloodsucker_clan/tremere/New(mob/living/carbon/user)
+ . = ..()
+ bloodsuckerdatum.remove_nondefault_powers(return_levels = TRUE)
+ for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
+ if((initial(power.purchase_flags) & TREMERE_CAN_BUY) && initial(power.level_current) == 1)
+ bloodsuckerdatum.BuyPower(new power)
+
+/datum/bloodsucker_clan/tremere/Destroy(force)
+ for(var/datum/action/cooldown/bloodsucker/power in bloodsuckerdatum.powers)
+ if(power.purchase_flags & TREMERE_CAN_BUY)
+ bloodsuckerdatum.RemovePower(power)
+ return ..()
+
+/datum/bloodsucker_clan/tremere/handle_clan_life(datum/antagonist/bloodsucker/source)
+ . = ..()
+ var/area/current_area = get_area(bloodsuckerdatum.owner.current)
+ if(istype(current_area, /area/chapel))
+ to_chat(bloodsuckerdatum.owner.current, "You don't belong in holy areas! The Faith burns you!")
+ bloodsuckerdatum.owner.current.adjustFireLoss(10)
+ bloodsuckerdatum.owner.current.adjust_fire_stacks(2)
+ bloodsuckerdatum.owner.current.IgniteMob()
+
+/datum/bloodsucker_clan/tremere/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
+ // Purchase Power Prompt
+ var/list/options = list()
+ for(var/datum/action/cooldown/bloodsucker/targeted/tremere/power as anything in bloodsuckerdatum.powers)
+ if(!(power.purchase_flags & TREMERE_CAN_BUY))
+ continue
+ if(isnull(power.upgraded_power))
+ continue
+ options[initial(power.name)] = power
+
+ if(options.len < 1)
+ to_chat(bloodsuckerdatum.owner.current, "You grow more ancient by the night!")
+ else
+ // Give them the UI to purchase a power.
+ var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Select a power you wish to upgrade.", "Your Blood Thickens...", options)
+ // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
+ if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0)
+ return
+ // Did you choose a power?
+ if(!choice || !options[choice])
+ to_chat(bloodsuckerdatum.owner.current, "You prevent your blood from thickening just yet, but you may try again later.")
+ return
+ // Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
+ if(!istype(bloodsuckerdatum.owner.current.loc, /obj/structure/closet/crate/coffin))
+ to_chat(bloodsuckerdatum.owner.current, "You must be in your Coffin to purchase Powers.")
+ return
+
+ // Good to go - Buy Power!
+ var/datum/action/cooldown/bloodsucker/purchased_power = options[choice]
+ var/datum/action/cooldown/bloodsucker/targeted/tremere/tremere_power = purchased_power
+ if(isnull(tremere_power.upgraded_power))
+ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "cannot upgrade [choice]!")
+ to_chat(bloodsuckerdatum.owner.current, "[choice] is already at max level!")
+ return
+ bloodsuckerdatum.BuyPower(new tremere_power.upgraded_power)
+ bloodsuckerdatum.RemovePower(tremere_power)
+ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "upgraded [choice]!")
+ to_chat(bloodsuckerdatum.owner.current, "You have upgraded [choice]!")
+
+ finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
+
+/datum/bloodsucker_clan/tremere/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/batform
+ vassaldatum.owner.current.AddSpell(batform)
+
+/datum/bloodsucker_clan/tremere/on_vassal_made(datum/antagonist/bloodsucker/source, mob/living/user, mob/living/target)
+ . = ..()
+ to_chat(bloodsuckerdatum.owner.current, "You have now gained an additional Rank to spend!")
+ bloodsuckerdatum.bloodsucker_level_unspent++
diff --git a/code/modules/antagonists/bloodsucker/clans/venture.dm b/code/modules/antagonists/bloodsucker/clans/venture.dm
new file mode 100644
index 0000000000000..e705ff3014460
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/clans/venture.dm
@@ -0,0 +1,119 @@
+///The maximum level a Ventrue Bloodsucker can be, before they have to level up their vassal instead.
+#define VENTRUE_MAX_LEVEL 3
+///How much it costs for a Ventrue to rank up without a spare rank to spend.
+#define BLOODSUCKER_BLOOD_RANKUP_COST (550)
+
+/datum/bloodsucker_clan/ventrue
+ name = CLAN_VENTRUE
+ description = "The Ventrue Clan is extremely snobby with their meals, and refuse to drink blood from people without a mind. \n\
+ You may only level yourself up to Level %MAX_LEVEL%, anything further will be ranks to spend on their Favorite Vassal through a Persuasion Rack. \n\
+ The Favorite Vassal will slowly turn more Vampiric this way, until they finally lose their last bits of Humanity."
+ clan_objective = /datum/objective/bloodsucker/embrace
+ join_icon_state = "ventrue"
+ join_description = "Lose the ability to drink from mindless mobs, can't level up or gain new powers, \
+ instead you raise a vassal into a Bloodsucker."
+ blood_drink_type = BLOODSUCKER_DRINK_SNOBBY
+
+/datum/bloodsucker_clan/ventrue/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ description = replacetext(description, "%MAX_LEVEL%", VENTRUE_MAX_LEVEL)
+
+/datum/bloodsucker_clan/ventrue/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
+ if(!target)
+ if(bloodsuckerdatum.bloodsucker_level < VENTRUE_MAX_LEVEL)
+ return ..()
+ return FALSE
+ var/datum/antagonist/vassal/favorite/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal/favorite)
+ if(!vassaldatum)
+ return FALSE
+ // Purchase Power Prompt
+ var/list/options = list()
+ for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
+ if(initial(power.purchase_flags) & VASSAL_CAN_BUY && !(locate(power) in vassaldatum.powers))
+ options[initial(power.name)] = power
+
+ if(options.len < 1)
+ to_chat(bloodsuckerdatum.owner.current, "You grow more ancient by the night!")
+ else
+ // Give them the UI to purchase a power.
+ var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to level up your Favorite Vassal. Select a power you wish them to recieve.", "Your Blood Thickens...", options)
+ // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
+ if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0)
+ return
+ // Did you choose a power?
+ if(!choice || !options[choice])
+ to_chat(bloodsuckerdatum.owner.current, "You prevent your blood from thickening just yet, but you may try again later.")
+ return
+ // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
+ if((locate(options[choice]) in vassaldatum.powers))
+ to_chat(bloodsuckerdatum.owner.current, "You prevent your blood from thickening just yet, but you may try again later.")
+ return
+
+ // Good to go - Buy Power!
+ var/datum/action/cooldown/bloodsucker/purchased_power = options[choice]
+ vassaldatum.BuyPower(new purchased_power)
+ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "taught [choice]!")
+ to_chat(bloodsuckerdatum.owner.current, "You taught [target] how to use [choice]!")
+ target.balloon_alert(target, "learned [choice]!")
+ to_chat(target, "Your master taught you how to use [choice]!")
+
+ vassaldatum.vassal_level++
+ switch(vassaldatum.vassal_level)
+ if(2)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NOBREATH, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_AGEUSIA, BLOODSUCKER_TRAIT)
+ to_chat(target, "Your blood begins to feel cold, and as a mote of ash lands upon your tongue, you stop breathing...")
+ if(3)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NOCRITDAMAGE, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NOSOFTCRIT, BLOODSUCKER_TRAIT)
+ to_chat(target, "You feel your Master's blood reinforce you, strengthening you up.")
+ if(4)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_VIRUSIMMUNE, BLOODSUCKER_TRAIT)
+ to_chat(target, "You feel your Master's blood begin to protect you from bacteria.")
+ if(ishuman(target))
+ var/mob/living/carbon/human/human_target = target
+ human_target.skin_tone = "albino"
+ if(5)
+ ADD_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NOHARDCRIT, BLOODSUCKER_TRAIT)
+ to_chat(target, "You feel yourself able to take cuts and stabbings like it's nothing.")
+ if(6 to INFINITY)
+ if(!target.mind.has_antag_datum(/datum/antagonist/bloodsucker))
+ to_chat(target, "You feel your heart stop pumping for the last time as you begin to thirst for blood, you feel... dead.")
+ target.mind.add_antag_datum(/datum/antagonist/bloodsucker)
+ SEND_SIGNAL(bloodsuckerdatum.owner, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle)
+ vassaldatum.set_vassal_level(target)
+
+ finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
+ vassaldatum.LevelUpPowers()
+
+/datum/bloodsucker_clan/ventrue/interact_with_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/favorite/vassaldatum)
+ . = ..()
+ if(.)
+ return TRUE
+ if(!istype(vassaldatum))
+ return FALSE
+ if(!bloodsuckerdatum.bloodsucker_level_unspent <= 0)
+ bloodsuckerdatum.SpendRank(vassaldatum.owner.current)
+ return TRUE
+ if(bloodsuckerdatum.bloodsucker_blood_volume >= BLOODSUCKER_BLOOD_RANKUP_COST)
+ // We don't have any ranks to spare? Let them upgrade... with enough Blood.
+ to_chat(bloodsuckerdatum.owner.current, "Do you wish to spend [BLOODSUCKER_BLOOD_RANKUP_COST] Blood to Rank [vassaldatum.owner.current] up?")
+ var/static/list/rank_options = list(
+ "Yes" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/hud/radials/radial_generic.dmi', icon_state = "radial_no"),
+ )
+ var/rank_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, rank_options, radius = 36, require_near = TRUE)
+ if(rank_response == "Yes")
+ bloodsuckerdatum.SpendRank(vassaldatum.owner.current, cost_rank = FALSE, blood_cost = BLOODSUCKER_BLOOD_RANKUP_COST)
+ return TRUE
+ to_chat(bloodsuckerdatum.owner.current, "You don't have any levels or enough Blood to Rank [vassaldatum.owner.current] up with.")
+ return TRUE
+
+/datum/bloodsucker_clan/ventrue/on_favorite_vassal(datum/source, datum/antagonist/vassal/vassaldatum, mob/living/bloodsucker)
+ to_chat(bloodsucker, "* Bloodsucker Tip: You can now upgrade your Favorite Vassal by buckling them onto a Candelabrum!")
+ vassaldatum.BuyPower(new /datum/action/cooldown/bloodsucker/distress)
+
+#undef BLOODSUCKER_BLOOD_RANKUP_COST
+#undef VENTRUE_MAX_LEVEL
diff --git a/code/modules/antagonists/bloodsucker/conversion_bloodsucker.dm b/code/modules/antagonists/bloodsucker/conversion_bloodsucker.dm
new file mode 100644
index 0000000000000..9141a3d2a6829
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/conversion_bloodsucker.dm
@@ -0,0 +1,121 @@
+/**
+ * Checks if the target has antag datums and, if so,
+ * are they allowed to be Vassalized, or not, or banned.
+ * Args:
+ * target - The person we check for antag datums.
+ */
+/datum/antagonist/bloodsucker/proc/AmValidAntag(mob/target)
+ if(!target.mind || target.mind.unconvertable)
+ return VASSALIZATION_BANNED
+
+ var/vassalization_status = VASSALIZATION_ALLOWED
+ for(var/datum/antagonist/antag_datum as anything in target.mind.antag_datums)
+ if(antag_datum.type in vassal_banned_antags)
+ return VASSALIZATION_BANNED
+ vassalization_status = VASSALIZATION_DISLOYAL
+ return vassalization_status
+
+/**
+ * # can_make_vassal
+ * Checks if the person is allowed to turn into the Bloodsucker's
+ * Vassal, ensuring they are a player and valid.
+ * If they are a Vassal themselves, will check if their master
+ * has broken the Masquerade, to steal them.
+ * Args:
+ * conversion_target - Person being vassalized
+ */
+/datum/antagonist/bloodsucker/proc/can_make_vassal(mob/living/conversion_target)
+ if(!iscarbon(conversion_target) || conversion_target.stat > UNCONSCIOUS)
+ return FALSE
+ if(length(vassals) == return_current_max_vassals())
+ to_chat(owner.current, "You find that your powers run thin and are unable to dominate their mind with your blood!")
+ return FALSE
+ // No Mind!
+ if(!conversion_target.mind)
+ to_chat(owner.current, "[conversion_target] isn't self-aware enough to be made into a Vassal.")
+ return FALSE
+ if(AmValidAntag(conversion_target) == VASSALIZATION_BANNED)
+ to_chat(owner.current, "[conversion_target] resists the power of your blood to dominate their mind!")
+ return FALSE
+ var/mob/living/master = conversion_target.mind.enslaved_to
+ if(!master || (master == owner.current))
+ return TRUE
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum && bloodsuckerdatum.broke_masquerade)
+ //vassal stealing
+ return TRUE
+ to_chat(owner.current, "[conversion_target]'s mind is overwhelmed with too much external force to put your own!")
+ return FALSE
+
+/**
+ * This proc is responsible for calculating how many vassals you can have at any given
+ * time, ranges from 1 at 20 pop to 4 at 40 pop
+ */
+/datum/antagonist/bloodsucker/proc/return_current_max_vassals()
+ var/total_players = GLOB.joined_player_list.len
+ switch(total_players)
+ if(1 to 20)
+ return 1
+ if(21 to 30)
+ return 3
+ else
+ return 4
+
+/**
+ * First will check if the target can be turned into a Vassal, if so then it will
+ * turn them into one, log it, sync their minds, then updates the Rank
+ * Args:
+ * conversion_target - The person converted.
+ */
+/datum/antagonist/bloodsucker/proc/make_vassal(mob/living/conversion_target)
+ if(!can_make_vassal(conversion_target))
+ return FALSE
+
+ //Check if they used to be a Vassal and was stolen.
+ var/datum/antagonist/vassal/old_vassal = conversion_target.mind.has_antag_datum(/datum/antagonist/vassal)
+ if(old_vassal)
+ conversion_target.mind.remove_antag_datum(/datum/antagonist/vassal)
+
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
+ bloodsuckerdatum.SelectTitle(am_fledgling = FALSE)
+
+ //set the master, then give the datum.
+ var/datum/antagonist/vassal/vassaldatum = new(conversion_target.mind)
+ vassaldatum.master = bloodsuckerdatum
+ conversion_target.mind.add_antag_datum(vassaldatum)
+
+ message_admins("[conversion_target] has become a Vassal, and is enslaved to [owner.current].")
+ log_admin("[conversion_target] has become a Vassal, and is enslaved to [owner.current].")
+ return TRUE
+
+/*
+ * # can_make_special
+ *
+ * MIND Helper proc that ensures the person can be a Special Vassal,
+ * without actually giving the antag datum to them.
+ * This is because Special Vassals get special abilities, without the unique Bloodsucker blood tracking,
+ * and we don't want this to be infinite.
+ * Args:
+ * creator - Person attempting to convert them.
+ */
+/datum/mind/proc/can_make_special(datum/mind/creator)
+ var/mob/living/user = current
+ if(!(user.mob_biotypes & MOB_ORGANIC))
+ if(creator)
+ to_chat(creator, "[user]'s DNA isn't compatible!")
+ return FALSE
+ return TRUE
+
+/*
+ * # make_bloodsucker
+ *
+ * MIND Helper proc that turns the person into a Bloodsucker
+ * Args:
+ * creator - Person attempting to convert them.
+ */
+/datum/mind/proc/make_bloodsucker(datum/mind/creator)
+ var/datum/antagonist/bloodsuckerdatum = add_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum && creator)
+ message_admins("[src] has become a Bloodsucker, and was created by [creator].")
+ log_admin("[src] has become a Bloodsucker, and was created by [creator].")
+ return bloodsuckerdatum
diff --git a/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm
new file mode 100644
index 0000000000000..4d8ca2f325603
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm
@@ -0,0 +1,505 @@
+/datum/antagonist/bloodsucker
+ name = "\improper Bloodsucker"
+ roundend_category = "bloodsuckers"
+ antagpanel_category = "Bloodsucker"
+ banning_key = ROLE_BLOODSUCKER
+ required_living_playtime = 4
+ ui_name = "AntagInfoBloodsucker"
+ antag_moodlet = /datum/mood_event/focused
+ hijack_speed = 0.5
+ var/antag_hud_name = "bloodsucker"
+ /// How much blood we have, starting off at default blood levels.
+ var/bloodsucker_blood_volume = BLOOD_VOLUME_NORMAL
+ /// How much blood we can have at once, increases per level.
+ var/max_blood_volume = 600
+
+ // Only created if bloodsucker makes vassals
+ var/datum/team/bloodsucker/vamp_team
+
+ var/datum/bloodsucker_clan/my_clan
+
+ // TIMERS //
+ ///Timer between alerts for Burn messages
+ COOLDOWN_DECLARE(bloodsucker_spam_sol_burn)
+ ///Timer between alerts for Healing messages
+ COOLDOWN_DECLARE(bloodsucker_spam_healing)
+
+ ///Used for assigning your name
+ var/bloodsucker_name
+ ///Used for assigning your title
+ var/bloodsucker_title
+ ///Used for assigning your reputation
+ var/bloodsucker_reputation
+
+ ///Amount of Humanity lost
+ var/humanity_lost = 0
+ ///Have we been broken the Masquerade?
+ var/broke_masquerade = FALSE
+ ///How many Masquerade Infractions do we have?
+ var/masquerade_infractions = 0
+ ///Blood required to enter Frenzy
+ var/frenzy_threshold = FRENZY_THRESHOLD_ENTER
+ ///If we are currently in a Frenzy
+ var/frenzied = FALSE
+
+ ///ALL Powers currently owned
+ var/list/datum/action/cooldown/bloodsucker/powers = list()
+ ///Frenzy Grab Martial art given to Bloodsuckers in a Frenzy
+ var/datum/martial_art/frenzygrab/frenzygrab = new
+
+ ///Vassals under my control. Periodically remove the dead ones.
+ var/list/datum/antagonist/vassal/vassals = list()
+ ///Special vassals I own, to not have double of the same type.
+ var/list/datum/antagonist/vassal/special_vassals = list()
+
+ var/bloodsucker_level = 0
+ var/bloodsucker_level_unspent = 1
+ var/additional_regen
+ var/bloodsucker_regen_rate = 0.3
+
+ // Used for Bloodsucker Objectives
+ var/area/bloodsucker_lair_area
+ var/obj/structure/closet/crate/coffin
+ var/total_blood_drank = 0
+
+ ///Blood display HUD
+ var/atom/movable/screen/bloodsucker/blood_counter/blood_display
+ ///Vampire level display HUD
+ var/atom/movable/screen/bloodsucker/rank_counter/vamprank_display
+ ///Sunlight timer HUD
+ var/atom/movable/screen/bloodsucker/sunlight_counter/sunlight_display
+
+ /// Static typecache of all bloodsucker powers.
+ var/static/list/all_bloodsucker_powers = typecacheof(/datum/action/cooldown/bloodsucker, ignore_root_path = TRUE)
+ /// Antagonists that cannot be Vassalized no matter what
+ var/static/list/vassal_banned_antags = list(
+ /datum/antagonist/bloodsucker,
+ /datum/antagonist/changeling,
+ /datum/antagonist/cult,
+ /datum/antagonist/servant_of_ratvar,
+ )
+ ///Default Bloodsucker traits
+ var/static/list/bloodsucker_traits = list(
+ TRAIT_NOBREATH,
+ TRAIT_SLEEPIMMUNE,
+ TRAIT_NOCRITDAMAGE,
+ TRAIT_RESISTCOLD,
+ TRAIT_RADIMMUNE,
+ TRAIT_STABLEHEART,
+ TRAIT_NOSOFTCRIT,
+ TRAIT_NOHARDCRIT,
+ TRAIT_AGEUSIA,
+ TRAIT_COLDBLOODED,
+ TRAIT_VIRUSIMMUNE,
+ TRAIT_TOXIMMUNE,
+ )
+
+// Taken from wizard.dm
+/datum/antagonist/bloodsucker/proc/create_vamp_team()
+ var/static/count = 0
+ vamp_team = new(owner)
+ vamp_team.name = "Vamp team No.[++count]" // only displayed to admins
+ vamp_team.master_bloodsucker = src
+ add_antag_hud(ANTAG_HUD_BLOODSUCKER, antag_hud_name, owner.current)
+
+/datum/team/bloodsucker
+ name = "bloodsucker team"
+ var/datum/antagonist/bloodsucker/master_bloodsucker
+/**
+ * Apply innate effects is everything given to the mob
+ * When a body is tranferred, this is called on the new mob
+ * while on_gain is called ONCE per ANTAG, this is called ONCE per BODY.
+ */
+/datum/antagonist/bloodsucker/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ RegisterSignal(current_mob, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(current_mob, COMSIG_LIVING_LIFE, PROC_REF(LifeTick))
+ RegisterSignal(current_mob, COMSIG_LIVING_DEATH, PROC_REF(on_death))
+ handle_clown_mutation(current_mob, mob_override ? null : "As a vampiric clown, you are no longer a danger to yourself. Your clownish nature has been subdued by your thirst for blood.")
+
+ add_antag_hud(ANTAG_HUD_BLOODSUCKER, antag_hud_name, current_mob)
+ current_mob.faction |= FACTION_BLOODSUCKER
+
+ if(current_mob.hud_used)
+ on_hud_created()
+ else
+ RegisterSignal(current_mob, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
+#ifdef BLOODSUCKER_TESTING
+ var/turf/user_loc = get_turf(current_mob)
+ new /obj/structure/closet/crate/coffin(user_loc)
+ new /obj/structure/bloodsucker/vassalrack(user_loc)
+#endif
+
+/**
+ * Remove innate effects is everything given to the mob
+ * When a body is tranferred, this is called on the old mob.
+ * while on_removal is called ONCE per ANTAG, this is called ONCE per BODY.
+ */
+/datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_PARENT_EXAMINE, COMSIG_LIVING_DEATH))
+ handle_clown_mutation(current_mob, removing = FALSE)
+
+ if(current_mob.hud_used)
+ var/datum/hud/hud_used = current_mob.hud_used
+ hud_used.infodisplay -= blood_display
+ hud_used.infodisplay -= vamprank_display
+ hud_used.infodisplay -= sunlight_display
+ QDEL_NULL(blood_display)
+ QDEL_NULL(vamprank_display)
+ QDEL_NULL(sunlight_display)
+
+ remove_antag_hud(ANTAG_HUD_BLOODSUCKER, current_mob)
+ current_mob.faction -= FACTION_BLOODSUCKER
+
+/datum/antagonist/bloodsucker/proc/on_hud_created(datum/source)
+ SIGNAL_HANDLER
+ var/datum/hud/bloodsucker_hud = owner.current.hud_used
+
+ blood_display = new /atom/movable/screen/bloodsucker/blood_counter()
+ blood_display.hud = bloodsucker_hud
+ bloodsucker_hud.infodisplay += blood_display
+
+ vamprank_display = new /atom/movable/screen/bloodsucker/rank_counter()
+ vamprank_display.hud = bloodsucker_hud
+ bloodsucker_hud.infodisplay += vamprank_display
+
+ sunlight_display = new /atom/movable/screen/bloodsucker/sunlight_counter()
+ sunlight_display.hud = bloodsucker_hud
+ bloodsucker_hud.infodisplay += sunlight_display
+
+ bloodsucker_hud.show_hud(bloodsucker_hud.hud_version)
+ UnregisterSignal(owner.current, COMSIG_MOB_HUD_CREATED)
+
+/datum/antagonist/bloodsucker/get_admin_commands()
+ . = ..()
+ .["Give Level"] = CALLBACK(src, PROC_REF(RankUp))
+ if(bloodsucker_level_unspent >= 1)
+ .["Remove Level"] = CALLBACK(src, PROC_REF(RankDown))
+
+ if(broke_masquerade)
+ .["Fix Masquerade"] = CALLBACK(src, PROC_REF(fix_masquerade))
+ else
+ .["Break Masquerade"] = CALLBACK(src, PROC_REF(break_masquerade))
+
+ if(my_clan)
+ .["Remove Clan"] = CALLBACK(src, PROC_REF(remove_clan))
+ else
+ .["Add Clan"] = CALLBACK(src, PROC_REF(admin_set_clan))
+
+///Called when you get the antag datum, called only ONCE per antagonist.
+/datum/antagonist/bloodsucker/on_gain()
+ RegisterSignal(SSsunlight, COMSIG_SOL_RANKUP_BLOODSUCKERS, PROC_REF(sol_rank_up))
+ RegisterSignal(SSsunlight, COMSIG_SOL_NEAR_START, PROC_REF(sol_near_start))
+ RegisterSignal(SSsunlight, COMSIG_SOL_END, PROC_REF(on_sol_end))
+ RegisterSignal(SSsunlight, COMSIG_SOL_RISE_TICK, PROC_REF(handle_sol))
+ RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(give_warning))
+
+ if(IS_FAVORITE_VASSAL(owner.current)) // Vassals shouldnt be getting the same benefits as Bloodsuckers.
+ bloodsucker_level_unspent = 0
+ show_in_roundend = FALSE
+ else
+ // Start Sunlight if first Bloodsucker
+ check_start_sunlight()
+ // Name and Titles
+ SelectFirstName()
+ SelectTitle(am_fledgling = TRUE)
+ SelectReputation(am_fledgling = TRUE)
+ // Objectives
+ forge_bloodsucker_objectives()
+
+ . = ..()
+ // Assign Powers
+ give_starting_powers()
+ assign_starting_stats()
+
+/// Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion.
+/datum/antagonist/bloodsucker/on_removal()
+ UnregisterSignal(SSsunlight, list(COMSIG_SOL_RANKUP_BLOODSUCKERS, COMSIG_SOL_NEAR_START, COMSIG_SOL_END, COMSIG_SOL_RISE_TICK, COMSIG_SOL_WARNING_GIVEN))
+ clear_powers_and_stats()
+ check_cancel_sunlight() //check if sunlight should end
+ //Remove Language
+ owner.current.remove_language(/datum/language/vampiric)
+ return ..()
+
+/datum/antagonist/bloodsucker/on_body_transfer(mob/living/old_body, mob/living/new_body)
+ . = ..()
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
+ if(old_body)
+ all_powers.Remove(old_body)
+ all_powers.Grant(new_body)
+
+ var/mob/living/carbon/human/old_body_human = old_body
+ if(ishuman(old_body_human))
+ old_body_human.dna.species.punchdamage -= 2
+
+ var/mob/living/carbon/human/user = owner.current
+ if(ishuman(owner.current))
+ var/datum/species/user_species = user.dna.species
+ user_species.species_traits += TRAIT_DRINKSBLOOD
+ user_species.punchdamage += 2
+
+ //Give Bloodsucker Traits
+ if(old_body)
+ for(var/all_traits in bloodsucker_traits)
+ REMOVE_TRAIT(new_body, all_traits, BLOODSUCKER_TRAIT)
+
+ for(var/all_traits in bloodsucker_traits)
+ ADD_TRAIT(new_body, all_traits, BLOODSUCKER_TRAIT)
+
+/datum/antagonist/bloodsucker/greet()
+ . = ..()
+ var/fullname = return_full_name()
+ to_chat(owner, "You are [fullname], a strain of vampire known as a Bloodsucker!")
+ owner.announce_objectives()
+ if(bloodsucker_level_unspent >= 2)
+ to_chat(owner, "As a latejoin, you have [bloodsucker_level_unspent] bonus Ranks, entering your claimed coffin allows you to spend a Rank.")
+ owner.current.playsound_local(null, 'sound/bloodsuckers/BloodsuckerAlert.ogg', 100, FALSE, pressure_affected = FALSE)
+ antag_memory += "Although you were born a mortal, in undeath you earned the name [fullname].
"
+
+/datum/antagonist/bloodsucker/farewell()
+ to_chat(owner.current, "With a snap, your curse has ended. You are no longer a Bloodsucker. You live once more!")
+ // Refill with Blood so they don't instantly die.
+ if(!HAS_TRAIT(owner.current, TRAIT_NO_BLOOD))
+ owner.current.blood_volume = max(owner.current.blood_volume, BLOOD_VOLUME_NORMAL)
+
+// Called when using admin tools to give antag status
+/datum/antagonist/bloodsucker/admin_add(datum/mind/new_owner, mob/admin)
+ var/levels = input("How many unspent Ranks would you like [new_owner] to have?","Bloodsucker Rank", bloodsucker_level_unspent) as null | num
+ var/msg = " made [key_name_admin(new_owner)] into \a [name]"
+ if(levels > 1)
+ bloodsucker_level_unspent = levels
+ msg += " with [levels] extra unspent Ranks."
+ message_admins("[key_name_admin(usr)][msg]")
+ log_admin("[key_name(usr)][msg]")
+ new_owner.add_antag_datum(src)
+
+/datum/antagonist/bloodsucker/ui_static_data(mob/user)
+ var/list/data = list()
+ //we don't need to update this that much.
+ data["in_clan"] = !!my_clan
+ var/list/clan_data = list()
+ if(my_clan)
+ clan_data["clan_name"] = my_clan.name
+ clan_data["clan_description"] = my_clan.description
+ clan_data["clan_icon"] = my_clan.join_icon_state
+
+ data["clan"] += list(clan_data)
+
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ var/list/power_data = list()
+
+ power_data["power_name"] = power.name
+ power_data["power_explanation"] = power.power_explanation
+ power_data["power_icon"] = power.button_icon_state
+
+ data["power"] += list(power_data)
+
+ return data + ..()
+
+/datum/antagonist/bloodsucker/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("join_clan")
+ if(my_clan)
+ return
+ assign_clan_and_bane()
+ ui.send_full_update(force = TRUE)
+ return
+
+/datum/antagonist/bloodsucker/roundend_report()
+ var/list/report = list()
+
+ // Vamp name
+ report += "
"
+ report += printplayer(owner)
+ if(my_clan)
+ report += "They were part of the [my_clan.name]!"
+
+ // Default Report
+ var/objectives_complete = TRUE
+ if(objectives.len)
+ report += printobjectives(objectives)
+ for(var/datum/objective/objective in objectives)
+ if(objective.name == "Optional Objective")
+ continue
+ if(!objective.check_completion())
+ objectives_complete = FALSE
+ break
+
+ // Now list their vassals
+ if(vassals.len)
+ report += ""
+ for(var/datum/antagonist/vassal/all_vassals as anything in vassals)
+ if(!all_vassals.owner)
+ continue
+ var/list/vassal_report = list()
+ vassal_report += "[all_vassals.owner.name]"
+
+ if(all_vassals.owner.assigned_role)
+ vassal_report += " the [all_vassals.owner.assigned_role]"
+ if(IS_FAVORITE_VASSAL(all_vassals.owner.current))
+ vassal_report += " and was the Favorite Vassal"
+ else if(IS_REVENGE_VASSAL(all_vassals.owner.current))
+ vassal_report += " and was the Revenge Vassal"
+ report += vassal_report.Join()
+
+ if(objectives.len == 0 || objectives_complete)
+ report += "The [name] was successful!"
+ else
+ report += "The [name] has failed!"
+
+ return report.Join("
")
+
+/datum/antagonist/bloodsucker/proc/give_starting_powers()
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in all_bloodsucker_powers)
+ if(!(initial(all_powers.purchase_flags) & BLOODSUCKER_DEFAULT_POWER))
+ continue
+ BuyPower(new all_powers)
+
+/datum/antagonist/bloodsucker/proc/assign_starting_stats()
+ //Traits: Species
+ var/mob/living/carbon/human/user = owner.current
+ if(ishuman(owner.current))
+ var/datum/species/user_species = user.dna.species
+ user_species.species_traits += TRAIT_DRINKSBLOOD
+ user_species.punchdamage += 2
+ user.dna?.remove_all_mutations()
+ //Give Bloodsucker Traits
+ for(var/all_traits in bloodsucker_traits)
+ ADD_TRAIT(owner.current, all_traits, BLOODSUCKER_TRAIT)
+ //No Skittish "People" allowed
+ if(HAS_TRAIT(owner.current, TRAIT_SKITTISH))
+ REMOVE_TRAIT(owner.current, TRAIT_SKITTISH, ROUNDSTART_TRAIT)
+ // Tongue & Language
+ owner.current.grant_all_languages(FALSE, FALSE, TRUE)
+ owner.current.grant_language(/datum/language/vampiric)
+ /// Clear Disabilities & Organs
+ heal_vampire_organs()
+
+/**
+ * ##clear_power_and_stats()
+ *
+ * Removes all Bloodsucker related Powers/Stats changes, setting them back to pre-Bloodsucker
+ * Order of steps and reason why:
+ * Remove clan - Clans like Nosferatu give Powers on removal, we have to make sure this is given before removing Powers.
+ * Powers - Remove all Powers, so things like Masquerade are off.
+ * Species traits, Traits, MaxHealth, Language - Misc stuff, has no priority.
+ * Organs - At the bottom to ensure everything that changes them has reverted themselves already.
+ * Update Sight - Done after Eyes are regenerated.
+ */
+/datum/antagonist/bloodsucker/proc/clear_powers_and_stats()
+ // Remove clan first
+ if(my_clan)
+ QDEL_NULL(my_clan)
+ // Powers
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
+ RemovePower(all_powers)
+ /// Stats
+ if(ishuman(owner.current))
+ var/mob/living/carbon/human/user = owner.current
+ var/datum/species/user_species = user.dna.species
+ user_species.species_traits -= TRAIT_DRINKSBLOOD
+ // Remove all bloodsucker traits
+ for(var/all_traits in bloodsucker_traits)
+ REMOVE_TRAIT(owner.current, all_traits, BLOODSUCKER_TRAIT)
+ // Update Health
+ owner.current.setMaxHealth(initial(owner.current.maxHealth))
+ // Language
+ owner.current.remove_language(/datum/language/vampiric)
+ // Heart & Eyes
+ var/mob/living/carbon/user = owner.current
+ var/obj/item/organ/heart/newheart = owner.current.getorganslot(ORGAN_SLOT_HEART)
+ if(newheart)
+ newheart.beating = initial(newheart.beating)
+ var/obj/item/organ/eyes/user_eyes = user.getorganslot(ORGAN_SLOT_EYES)
+ if(user_eyes)
+ user_eyes.flash_protect = initial(user_eyes.flash_protect)
+ user_eyes.sight_flags = initial(user_eyes.sight_flags)
+ user.update_sight()
+
+/datum/antagonist/bloodsucker/proc/claim_coffin(obj/structure/closet/crate/claimed, area/current_area)
+ // ALREADY CLAIMED
+ if(claimed.resident)
+ if(claimed.resident == owner.current)
+ to_chat(owner, "This is your [src].")
+ else
+ to_chat(owner, "This [src] has already been claimed by another.")
+ return FALSE
+ if(!(GLOB.the_station_areas.Find(current_area.type)))
+ claimed.balloon_alert(owner.current, "not part of station!")
+ return
+ // This is my Lair
+ coffin = claimed
+ bloodsucker_lair_area = current_area
+ if(!(/datum/crafting_recipe/vassalrack in owner?.learned_recipes))
+ owner.teach_crafting_recipe(/datum/crafting_recipe/vassalrack)
+ owner.teach_crafting_recipe(/datum/crafting_recipe/candelabrum)
+ owner.teach_crafting_recipe(/datum/crafting_recipe/bloodthrone)
+ owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin)
+ owner.current.balloon_alert(owner.current, "new recipes learned!")
+ to_chat(owner, "You have claimed the [claimed] as your place of immortal rest! Your lair is now [bloodsucker_lair_area].")
+ to_chat(owner, "Bloodsucker Tip: Find new lair recipes in the Structures tab of the Crafting Menu, including the Persuasion Rack for converting crew into Vassals.")
+ return TRUE
+
+/// Name shown on antag list
+/datum/antagonist/bloodsucker/antag_listing_name()
+ return ..() + "([return_full_name()])"
+
+/// Whatever interesting things happened to the antag admins should know about
+/// Include additional information about antag in this part
+/datum/antagonist/bloodsucker/antag_listing_status()
+ if(owner && !considered_alive(owner))
+ return "Final Death"
+ return ..()
+
+/datum/antagonist/bloodsucker/proc/forge_bloodsucker_objectives()
+ // Claim a Lair Objective
+ var/datum/objective/bloodsucker/lair/lair_objective = new
+ lair_objective.owner = owner
+ objectives += lair_objective
+ // Survive Objective
+ var/datum/objective/survive/bloodsucker/survive_objective = new
+ survive_objective.owner = owner
+ objectives += survive_objective
+
+ // Objective 1: Vassalize a Head/Command, or a specific target
+ switch(rand(1, 3))
+ if(1) // Conversion Objective
+ var/datum/objective/bloodsucker/conversion/chosen_subtype = pick(subtypesof(/datum/objective/bloodsucker/conversion))
+ var/datum/objective/bloodsucker/conversion/conversion_objective = new chosen_subtype
+ conversion_objective.owner = owner
+ conversion_objective.name = "Optional Objective"
+ objectives += conversion_objective
+ if(2) // Heart Thief Objective
+ var/datum/objective/bloodsucker/heartthief/heartthief_objective = new
+ heartthief_objective.owner = owner
+ heartthief_objective.name = "Optional Objective"
+ objectives += heartthief_objective
+ if(3) // Drink Blood Objective
+ var/datum/objective/bloodsucker/gourmand/gourmand_objective = new
+ gourmand_objective.owner = owner
+ gourmand_objective.name = "Optional Objective"
+ objectives += gourmand_objective
+
+// For the malkavian clan final death
+/datum/antagonist/shaded_bloodsucker
+ name = "\improper Shaded Bloodsucker"
+ antagpanel_category = "Bloodsucker"
+ show_in_roundend = FALSE
+ banning_key = ROLE_BLOODSUCKER
+
+/datum/antagonist/shaded_bloodsucker/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ add_antag_hud(ANTAG_HUD_BLOODSUCKER, "bloodsucker", current_mob)
+
+/datum/antagonist/shaded_bloodsucker/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ remove_antag_hud(ANTAG_HUD_BLOODSUCKER, current_mob)
diff --git a/code/modules/antagonists/bloodsucker/daylight_bloodsucker.dm b/code/modules/antagonists/bloodsucker/daylight_bloodsucker.dm
new file mode 100644
index 0000000000000..d6538973eb3b0
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/daylight_bloodsucker.dm
@@ -0,0 +1,171 @@
+/**
+ * # Assigning Sol
+ *
+ * Sol is the sunlight, during this period, all Bloodsuckers must be in their coffin, else they burn.
+ */
+
+/// Start Sol, called when someone is assigned Bloodsucker
+/datum/antagonist/bloodsucker/proc/check_start_sunlight()
+ var/list/existing_suckers = get_antag_minds(/datum/antagonist/bloodsucker) - owner
+ if(!length(existing_suckers))
+ message_admins("New Sol has been created due to Bloodsucker assignment.")
+ SSsunlight.can_fire = TRUE
+
+/// End Sol, if you're the last Bloodsucker
+/datum/antagonist/bloodsucker/proc/check_cancel_sunlight()
+ var/list/existing_suckers = get_antag_minds(/datum/antagonist/bloodsucker) - owner
+ if(!length(existing_suckers))
+ message_admins("Sol has been deleted due to the lack of Bloodsuckers")
+ SSsunlight.can_fire = FALSE
+
+///Ranks the Bloodsucker up, called by Sol.
+/datum/antagonist/bloodsucker/proc/sol_rank_up(atom/source)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(RankUp))
+
+///Called when Sol is near starting.
+/datum/antagonist/bloodsucker/proc/sol_near_start(atom/source)
+ SIGNAL_HANDLER
+ if(bloodsucker_lair_area && !(locate(/datum/action/cooldown/bloodsucker/gohome) in powers))
+ BuyPower(new /datum/action/cooldown/bloodsucker/gohome)
+
+///Called when Sol first ends.
+/datum/antagonist/bloodsucker/proc/on_sol_end(atom/source)
+ SIGNAL_HANDLER
+ check_end_torpor()
+ for(var/datum/action/cooldown/bloodsucker/power in powers)
+ if(istype(power, /datum/action/cooldown/bloodsucker/gohome))
+ RemovePower(power)
+
+/// Cycle through all vamp antags and check if they're inside a closet.
+/datum/antagonist/bloodsucker/proc/handle_sol()
+ SIGNAL_HANDLER
+ if(!owner || !owner.current)
+ return
+
+ if(!istype(owner.current.loc, /obj/structure))
+ if(COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn))
+ if(bloodsucker_level > 0)
+ to_chat(owner, "The solar flare sets your skin ablaze!")
+ else
+ to_chat(owner, "The solar flare scalds your neophyte skin!")
+ COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
+
+ if(owner.current.fire_stacks <= 0)
+ owner.current.fire_stacks = 0
+ if(bloodsucker_level > 0)
+ owner.current.adjust_fire_stacks(0.2 + bloodsucker_level / 10)
+ owner.current.IgniteMob()
+ owner.current.adjustFireLoss(2 + (bloodsucker_level / 2))
+ owner.current.updatehealth()
+ SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_2)
+ return
+
+ if(istype(owner.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection
+ if(owner.current.am_staked() && COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn))
+ to_chat(owner.current, "You are staked! Remove the offending weapon from your heart before sleeping.")
+ COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
+ if(!HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ check_begin_torpor(TRUE)
+ SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/coffinsleep)
+ return
+
+ if(COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn)) // Closets offer SOME protection
+ to_chat(owner, "Your skin sizzles. [owner.current.loc] doesn't protect well against UV bombardment.")
+ COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
+ owner.current.adjustFireLoss(0.5 + (bloodsucker_level / 4))
+ owner.current.updatehealth()
+ SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_1)
+
+/datum/antagonist/bloodsucker/proc/give_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message)
+ SIGNAL_HANDLER
+ if(!owner)
+ return
+ to_chat(owner, vampire_warning_message)
+
+ switch(danger_level)
+ if(DANGER_LEVEL_FIRST_WARNING)
+ owner.current.playsound_local(null, 'sound/bloodsuckers/griffin_3.ogg', 50, 1)
+ if(DANGER_LEVEL_SECOND_WARNING)
+ owner.current.playsound_local(null, 'sound/bloodsuckers/griffin_5.ogg', 50, 1)
+ if(DANGER_LEVEL_THIRD_WARNING)
+ owner.current.playsound_local(null, 'sound/effects/alert.ogg', 75, 1)
+ if(DANGER_LEVEL_SOL_ROSE)
+ owner.current.playsound_local(null, 'sound/ambience/ambimystery.ogg', 75, 1)
+ if(DANGER_LEVEL_SOL_ENDED)
+ owner.current.playsound_local(null, 'sound/misc/ghosty_wind.ogg', 90, 1)
+
+/**
+ * # Torpor
+ *
+ * Torpor is what deals with the Bloodsucker falling asleep, their healing, the effects, ect.
+ * This is basically what Sol is meant to do to them, but they can also trigger it manually if they wish to heal, as Burn is only healed through Torpor.
+ * You cannot manually exit Torpor, it is instead entered/exited by:
+ *
+ * Torpor is triggered by:
+ * - Being in a Coffin while Sol is on, dealt with by Sol
+ * - Entering a Coffin with more than 10 combined Brute/Burn damage, dealt with by /closet/crate/coffin/close() [bloodsucker_coffin.dm]
+ * - Death, dealt with by /HandleDeath()
+ * Torpor is ended by:
+ * - Having less than 10 Brute damage while OUTSIDE of your Coffin while it isnt Sol.
+ * - Having less than 10 Brute & Burn Combined while INSIDE of your Coffin while it isnt Sol.
+ * - Sol being over, dealt with by /sunlight/process() [bloodsucker_daylight.dm]
+*/
+/datum/antagonist/bloodsucker/proc/check_begin_torpor(SkipChecks = FALSE)
+ /// Are we entering Torpor via Sol/Death? Then entering it isnt optional!
+ if(SkipChecks)
+ torpor_begin()
+ return
+ var/mob/living/carbon/user = owner.current
+ var/total_brute = user.getBruteLoss_nonProsthetic()
+ var/total_burn = user.getFireLoss_nonProsthetic()
+ var/total_damage = total_brute + total_burn
+ /// Checks - Not daylight & Has more than 10 Brute/Burn & not already in Torpor
+ if(!SSsunlight.sunlight_active && total_damage >= 10 && !HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ torpor_begin()
+
+/datum/antagonist/bloodsucker/proc/check_end_torpor()
+ var/mob/living/carbon/user = owner.current
+ var/total_brute = user.getBruteLoss_nonProsthetic()
+ var/total_burn = user.getFireLoss_nonProsthetic()
+ var/total_damage = total_brute + total_burn
+ if(total_burn >= 199)
+ return FALSE
+ if(SSsunlight.sunlight_active)
+ return FALSE
+ // You are in a Coffin, so instead we'll check TOTAL damage, here.
+ if(istype(user.loc, /obj/structure/closet/crate/coffin))
+ if(total_damage <= 10)
+ torpor_end()
+ else
+ if(total_brute <= 10)
+ torpor_end()
+
+/datum/antagonist/bloodsucker/proc/torpor_begin()
+ to_chat(owner.current, "You enter the horrible slumber of deathless Torpor. You will heal until you are renewed.")
+ // Force them to go to sleep
+ REMOVE_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT)
+ // Without this, you'll just keep dying while you recover.
+ ADD_TRAIT(owner, TRAIT_NODEATH, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(owner, TRAIT_FAKEDEATH, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(owner, TRAIT_DEATHCOMA, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(owner, TRAIT_RESISTHIGHPRESSURE, BLOODSUCKER_TRAIT)
+ owner.current.jitteriness = 0
+ // Disable ALL Powers
+ DisableAllPowers()
+
+/datum/antagonist/bloodsucker/proc/torpor_end()
+ owner.current.grab_ghost()
+ to_chat(owner.current, "You have recovered from Torpor.")
+
+ REMOVE_TRAIT(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(owner.current, TRAIT_FAKEDEATH, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(owner.current, TRAIT_DEATHCOMA, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(owner.current, TRAIT_RESISTLOWPRESSURE, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(owner.current, TRAIT_RESISTHIGHPRESSURE, BLOODSUCKER_TRAIT)
+ if(!HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
+ ADD_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT)
+ heal_vampire_organs()
+ SEND_SIGNAL(src, BLOODSUCKER_EXIT_TORPOR)
diff --git a/code/modules/antagonists/bloodsucker/frenzy_bloodsucker.dm b/code/modules/antagonists/bloodsucker/frenzy_bloodsucker.dm
new file mode 100644
index 0000000000000..927d11f57b198
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/frenzy_bloodsucker.dm
@@ -0,0 +1,97 @@
+/**
+ * # FrenzyGrab
+ *
+ * The martial art given to Bloodsuckers so they can instantly aggressively grab people.
+ */
+/datum/martial_art/frenzygrab
+ name = "Frenzy Grab"
+ id = MARTIALART_FRENZYGRAB
+
+/datum/martial_art/frenzygrab/grab_act(mob/living/user, mob/living/target)
+ if(user != target)
+ target.grabbedby(user)
+ target.grippedby(user, instant = TRUE)
+ return TRUE
+ return ..()
+
+/**
+ * # Status effect
+ *
+ * This is the status effect given to Bloodsuckers in a Frenzy
+ * This deals with everything entering/exiting Frenzy is meant to deal with.
+ */
+
+/atom/movable/screen/alert/status_effect/frenzy
+ name = "Frenzy"
+ desc = "You are in a Frenzy! You are entirely Feral and, depending on your Clan, fighting for your life!"
+ icon = 'icons/bloodsuckers/actions_bloodsucker.dmi'
+ icon_state = "power_recover"
+ alerttooltipstyle = "cult"
+
+/datum/status_effect/frenzy
+ id = "Frenzy"
+ status_type = STATUS_EFFECT_UNIQUE
+ duration = -1
+ tick_interval = 10
+ alert_type = /atom/movable/screen/alert/status_effect/frenzy
+ ///Boolean on whether they were an AdvancedToolUser, to give the trait back upon exiting.
+ var/was_tooluser = FALSE
+ /// The stored Bloodsucker antag datum
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum
+
+/datum/status_effect/frenzy/get_examine_text()
+ return "They seem... inhumane, and feral!"
+
+/atom/movable/screen/alert/status_effect/masquerade/MouseEntered(location,control,params)
+ desc = initial(desc)
+ return ..()
+
+/datum/status_effect/frenzy/on_apply()
+ var/mob/living/carbon/human/user = owner
+ bloodsuckerdatum = IS_BLOODSUCKER(user)
+
+ // Disable ALL Powers and notify their entry
+ bloodsuckerdatum.DisableAllPowers(forced = TRUE)
+ to_chat(owner, "Blood! You need Blood, now! You enter a total Frenzy!")
+ to_chat(owner, "* Bloodsucker Tip: While in Frenzy, you instantly Aggresively grab, have stun resistance, cannot speak, hear, or use any powers outside of Feed and Trespass (If you have it).")
+ owner.balloon_alert(owner, "you enter a frenzy!")
+ SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_ENTERS_FRENZY)
+
+ // Give the other Frenzy effects
+ ADD_TRAIT(owner, TRAIT_MUTE, FRENZY_TRAIT)
+ ADD_TRAIT(owner, TRAIT_DEAF, FRENZY_TRAIT)
+ if(!HAS_TRAIT(owner, TRAIT_DISCOORDINATED))
+ was_tooluser = TRUE
+ ADD_TRAIT(owner, TRAIT_DISCOORDINATED, FRENZY_TRAIT)
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/dna_vault_speedup)
+ bloodsuckerdatum.frenzygrab.teach(user, TRUE)
+ owner.add_client_colour(/datum/client_colour/cursed_heart_blood)
+ var/obj/cuffs = user.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
+ var/obj/legcuffs = user.get_item_by_slot(ITEM_SLOT_LEGCUFFED)
+ if(user.handcuffed || user.legcuffed)
+ user.clear_cuffs(cuffs, TRUE)
+ user.clear_cuffs(legcuffs, TRUE)
+ bloodsuckerdatum.frenzied = TRUE
+ return ..()
+
+/datum/status_effect/frenzy/on_remove()
+ var/mob/living/carbon/human/user = owner
+ owner.balloon_alert(owner, "you come back to your senses.")
+ REMOVE_TRAIT(owner, TRAIT_MUTE, FRENZY_TRAIT)
+ REMOVE_TRAIT(owner, TRAIT_DEAF, FRENZY_TRAIT)
+ if(was_tooluser)
+ REMOVE_TRAIT(owner, TRAIT_DISCOORDINATED, FRENZY_TRAIT)
+ was_tooluser = FALSE
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/dna_vault_speedup)
+ bloodsuckerdatum.frenzygrab.remove(user)
+ owner.remove_client_colour(/datum/client_colour/cursed_heart_blood)
+
+ SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_EXITS_FRENZY)
+ bloodsuckerdatum.frenzied = FALSE
+ return ..()
+
+/datum/status_effect/frenzy/tick()
+ var/mob/living/carbon/human/user = owner
+ if(!bloodsuckerdatum.frenzied)
+ return
+ user.adjustFireLoss(1.5 + (bloodsuckerdatum.humanity_lost / 10))
diff --git a/code/modules/antagonists/bloodsucker/life_bloodsucker.dm b/code/modules/antagonists/bloodsucker/life_bloodsucker.dm
new file mode 100644
index 0000000000000..ff57934f3d05f
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/life_bloodsucker.dm
@@ -0,0 +1,302 @@
+///How much Blood it costs to live.
+#define BLOODSUCKER_PASSIVE_BLOOD_DRAIN 0.1
+
+/// Runs from COMSIG_LIVING_LIFE, handles Bloodsucker constant proccesses.
+/datum/antagonist/bloodsucker/proc/LifeTick(mob/living/source, seconds_per_tick, times_fired)
+ SIGNAL_HANDLER
+
+ if(isbrain(owner.current))
+ return
+ if(!owner)
+ INVOKE_ASYNC(src, PROC_REF(HandleDeath))
+ return
+ if(HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ check_end_torpor()
+ // Deduct Blood
+ if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_IMMOBILIZED) && !HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ INVOKE_ASYNC(src, PROC_REF(AddBloodVolume), -BLOODSUCKER_PASSIVE_BLOOD_DRAIN)
+ if(HandleHealing())
+ if((COOLDOWN_FINISHED(src, bloodsucker_spam_healing)) && bloodsucker_blood_volume > 0)
+ to_chat(owner.current, "The power of your blood begins knitting your wounds...")
+ COOLDOWN_START(src, bloodsucker_spam_healing, BLOODSUCKER_SPAM_HEALING)
+ // Standard Updates
+ SEND_SIGNAL(src, COMSIG_BLOODSUCKER_ON_LIFETICK)
+ INVOKE_ASYNC(src, PROC_REF(HandleStarving))
+ INVOKE_ASYNC(src, PROC_REF(update_blood))
+
+ INVOKE_ASYNC(src, PROC_REF(update_hud))
+
+/datum/antagonist/bloodsucker/proc/on_death(mob/living/source, gibbed)
+ SIGNAL_HANDLER
+ RegisterSignal(owner.current, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
+ RegisterSignal(src, COMSIG_BLOODSUCKER_ON_LIFETICK, PROC_REF(HandleDeath))
+
+/datum/antagonist/bloodsucker/proc/on_revive(mob/living/source)
+ UnregisterSignal(owner.current, COMSIG_LIVING_REVIVE)
+ UnregisterSignal(src, COMSIG_BLOODSUCKER_ON_LIFETICK)
+
+/**
+ * ## BLOOD STUFF
+ */
+/datum/antagonist/bloodsucker/proc/AddBloodVolume(value)
+ bloodsucker_blood_volume = clamp(bloodsucker_blood_volume + value, 0, max_blood_volume)
+
+/datum/antagonist/bloodsucker/proc/AddHumanityLost(value)
+ if(humanity_lost >= 500)
+ to_chat(owner.current, "You hit the maximum amount of lost Humanty, you are far from Human.")
+ return
+ humanity_lost += value
+ to_chat(owner.current, "You feel as if you lost some of your humanity, you will now enter Frenzy at [FRENZY_THRESHOLD_ENTER + (humanity_lost * 5)] Blood.")
+
+/// mult: SILENT feed is 1/3 the amount
+/datum/antagonist/bloodsucker/proc/handle_feeding(mob/living/carbon/target, mult=1, power_level)
+ // Starts at 15 (now 8 since we doubled the Feed time)
+ var/feed_amount = 15 + (power_level * 2)
+ var/blood_taken = min(feed_amount, target.blood_volume) * mult
+ target.blood_volume -= blood_taken
+
+ ///////////
+ // Shift Body Temp (toward Target's temp, by volume taken)
+ owner.current.bodytemperature = ((bloodsucker_blood_volume * owner.current.bodytemperature) + (blood_taken * target.bodytemperature)) / (bloodsucker_blood_volume + blood_taken)
+ // our volume * temp, + their volume * temp, / total volume
+ ///////////
+ // Reduce Value Quantity
+ if(target.stat == DEAD) // Penalty for Dead Blood
+ blood_taken /= 3
+ if(!ishuman(target)) // Penalty for Non-Human Blood
+ blood_taken /= 2
+ //if (!iscarbon(target)) // Penalty for Animals (they're junk food)
+ // Apply to Volume
+ AddBloodVolume(blood_taken)
+ // Reagents (NOT Blood!)
+ if(target.reagents && target.reagents.total_volume)
+ target.reagents.trans_to(owner.current, INGEST, 1) // Run transfer of 1 unit of reagent from them to me.
+ owner.current.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
+ total_blood_drank += blood_taken
+ return blood_taken
+
+/**
+ * ## HEALING
+ */
+
+/// Constantly runs on Bloodsucker's LifeTick, and is increased by being in Torpor/Coffins
+/datum/antagonist/bloodsucker/proc/HandleHealing(mult = 1)
+ var/actual_regen = bloodsucker_regen_rate + additional_regen
+ // Don't heal if I'm staked or on Masquerade (+ not in a Coffin). Masqueraded Bloodsuckers in a Coffin however, will heal.
+ if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT(owner.current, TRAIT_NODEATH)))
+ return FALSE
+ owner.current.adjustCloneLoss(-1 * (actual_regen * 4) * mult, 0)
+ owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (actual_regen * 4) * mult) //adjustBrainLoss(-1 * (actual_regen * 4) * mult, 0)
+ if(!iscarbon(owner.current)) // Damage Heal: Do I have damage to ANY bodypart?
+ return
+ var/mob/living/carbon/user = owner.current
+ var/costMult = 1 // Coffin makes it cheaper
+ var/bruteheal = min(user.getBruteLoss_nonProsthetic(), actual_regen) // BRUTE: Always Heal
+ var/fireheal = 0 // BURN: Heal in Coffin while Fakedeath, or when damage above maxhealth (you can never fully heal fire)
+ // Checks if you're in a coffin here, additionally checks for Torpor right below it.
+ var/amInCoffin = istype(user.loc, /obj/structure/closet/crate/coffin)
+ if(amInCoffin && HAS_TRAIT(user, TRAIT_NODEATH))
+ if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && (COOLDOWN_FINISHED(src, bloodsucker_spam_healing)))
+ to_chat(user, "You do not heal while your Masquerade ability is active.")
+ COOLDOWN_START(src, bloodsucker_spam_healing, BLOODSUCKER_SPAM_MASQUERADE)
+ return
+ fireheal = min(user.getFireLoss_nonProsthetic(), actual_regen)
+ mult *= 5 // Increase multiplier if we're sleeping in a coffin.
+ costMult /= 2 // Decrease cost if we're sleeping in a coffin.
+ user.ExtinguishMob()
+ user.remove_all_embedded_objects() // Remove Embedded!
+ if(check_limbs(costMult))
+ return TRUE
+ // In Torpor, but not in a Coffin? Heal faster anyways.
+ else if(HAS_TRAIT(user, TRAIT_NODEATH))
+ fireheal = min(user.getFireLoss_nonProsthetic(), actual_regen) / 1.2 // 20% slower than being in a coffin
+ mult *= 3
+ // Heal if Damaged
+ if((bruteheal + fireheal > 0) && mult != 0) // Just a check? Don't heal/spend, and return.
+ // We have damage. Let's heal (one time)
+ user.adjustBruteLoss(-bruteheal * mult, forced=TRUE) // Heal BRUTE / BURN in random portions throughout the body.
+ user.adjustFireLoss(-fireheal * mult, forced=TRUE)
+ AddBloodVolume(((bruteheal * -0.5) + (fireheal * -1)) * costMult * mult) // Costs blood to heal
+ return TRUE
+
+/datum/antagonist/bloodsucker/proc/check_limbs(costMult = 1)
+ var/limb_regen_cost = 50 * -costMult
+ var/mob/living/carbon/user = owner.current
+ var/list/missing = user.get_missing_limbs()
+ if(missing.len && (bloodsucker_blood_volume < limb_regen_cost + 5))
+ return FALSE
+ for(var/missing_limb in missing) //Find ONE Limb and regenerate it.
+ user.regenerate_limb(missing_limb, FALSE)
+ AddBloodVolume(-limb_regen_cost)
+ var/obj/item/bodypart/missing_bodypart = user.get_bodypart(missing_limb) // 2) Limb returns Damaged
+ missing_bodypart.brute_dam = 60
+ to_chat(user, "Your flesh knits as it regrows your [missing_bodypart]!")
+ playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE)
+ return TRUE
+
+/*
+ * # Heal Vampire Organs
+ *
+ * This is used by Bloodsuckers, these are the steps of this proc:
+ * Step 1 - Cure husking and Regenerate organs. regenerate_organs() removes their Vampire Heart & Eye augments, which leads us to...
+ * Step 2 - Repair any (shouldn't be possible) Organ damage, then return their Vampiric Heart & Eye benefits.
+ * Step 3 - Revive them, clear all wounds, remove any Tumors (If any).
+ *
+ * This is called on Bloodsucker's Assign, and when they end Torpor.
+ */
+
+/datum/antagonist/bloodsucker/proc/heal_vampire_organs()
+ var/mob/living/carbon/bloodsuckeruser = owner.current
+
+ bloodsuckeruser.cure_husk()
+ bloodsuckeruser.regenerate_organs(regenerate_existing = FALSE)
+
+ for(var/obj/item/organ/organ as anything in bloodsuckeruser.internal_organs)
+ organ.setOrganDamage(0)
+ if(!HAS_TRAIT(bloodsuckeruser, TRAIT_MASQUERADE))
+ var/obj/item/organ/heart/current_heart = bloodsuckeruser.getorganslot(ORGAN_SLOT_HEART)
+ current_heart.beating = FALSE
+ var/obj/item/organ/eyes/current_eyes = bloodsuckeruser.getorganslot(ORGAN_SLOT_EYES)
+ if(current_eyes)
+ current_eyes.flash_protect = max(initial(current_eyes.flash_protect) - 1, - 1)
+ current_eyes.sight_flags = SEE_MOBS
+ bloodsuckeruser.update_sight()
+
+ if(bloodsuckeruser.stat == DEAD)
+ bloodsuckeruser.revive()
+ // From [powers/panacea.dm]
+ var/list/bad_organs = list(
+ bloodsuckeruser.getorgan(/obj/item/organ/body_egg),
+ bloodsuckeruser.getorgan(/obj/item/organ/zombie_infection))
+ for(var/tumors in bad_organs)
+ var/obj/item/organ/yucky_organs = tumors
+ if(!istype(yucky_organs))
+ continue
+ yucky_organs.Remove(bloodsuckeruser)
+ yucky_organs.forceMove(get_turf(bloodsuckeruser))
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// DEATH
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// FINAL DEATH
+/datum/antagonist/bloodsucker/proc/HandleDeath()
+ // Not "Alive"?
+ if(!owner.current)
+ FinalDeath()
+ return
+ // Fire Damage? (above double health)
+ if(owner.current.getFireLoss() >= owner.current.maxHealth * 2.5)
+ FinalDeath()
+ return
+ // Staked while "Temp Death" or Asleep
+ if(owner.current.StakeCanKillMe() && owner.current.am_staked())
+ FinalDeath()
+ return
+ // Temporary Death? Convert to Torpor.
+ if(HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ return
+ to_chat(owner.current, "Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor.")
+ check_begin_torpor(TRUE)
+
+/datum/antagonist/bloodsucker/proc/HandleStarving() // I am thirsty for blood!
+ // Nutrition - The amount of blood is how full we are.
+ owner.current.set_nutrition(min(bloodsucker_blood_volume, NUTRITION_LEVEL_FED))
+
+ // BLOOD_VOLUME_GOOD: [336] - Pale
+// handled in bloodsucker_integration.dm
+
+ // BLOOD_VOLUME_EXIT: [250] - Exit Frenzy (If in one) This is high because we want enough to kill the poor soul they feed off of.
+ if(bloodsucker_blood_volume >= FRENZY_THRESHOLD_EXIT && frenzied)
+ owner.current.remove_status_effect(/datum/status_effect/frenzy)
+ // BLOOD_VOLUME_BAD: [224] - Jitter
+ if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD && prob(0.5) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
+ owner.current.jitteriness = 3 SECONDS
+ // BLOOD_VOLUME_SURVIVE: [122] - Blur Vision
+ if(bloodsucker_blood_volume < BLOOD_VOLUME_SURVIVE)
+ owner.current.set_blurriness((8 - 8 * (bloodsucker_blood_volume / BLOOD_VOLUME_BAD))*2 SECONDS)
+
+ // The more blood, the better the Regeneration, get too low blood, and you enter Frenzy.
+ if(bloodsucker_blood_volume < (FRENZY_THRESHOLD_ENTER + (humanity_lost * 5)) && !frenzied)
+ owner.current.apply_status_effect(/datum/status_effect/frenzy)
+ else if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD)
+ additional_regen = 0.1
+ else if(bloodsucker_blood_volume < BLOOD_VOLUME_OKAY)
+ additional_regen = 0.2
+ else if(bloodsucker_blood_volume < BLOOD_VOLUME_NORMAL)
+ additional_regen = 0.3
+ else if(bloodsucker_blood_volume < BS_BLOOD_VOLUME_MAX_REGEN)
+ additional_regen = 0.4
+ else
+ additional_regen = 0.5
+
+/// Makes your blood_volume look like your bloodsucker blood, unless you're Masquerading.
+/datum/antagonist/bloodsucker/proc/update_blood()
+ if(HAS_TRAIT(owner.current, TRAIT_NO_BLOOD))
+ return
+ //If we're on Masquerade, we appear to have full blood, unless we are REALLY low, in which case we don't look as bad.
+ if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
+ switch(bloodsucker_blood_volume)
+ if(BLOOD_VOLUME_OKAY to INFINITY) // 336 and up, we are perfectly fine.
+ owner.current.blood_volume = initial(bloodsucker_blood_volume)
+ if(BLOOD_VOLUME_BAD to BLOOD_VOLUME_OKAY) // 224 to 336
+ owner.current.blood_volume = BLOOD_VOLUME_SAFE
+ else // 224 and below
+ owner.current.blood_volume = BLOOD_VOLUME_OKAY
+ return
+
+ owner.current.blood_volume = bloodsucker_blood_volume
+
+/// Gibs the Bloodsucker, roundremoving them.
+/datum/antagonist/bloodsucker/proc/FinalDeath()
+ // If we have no body, end here.
+ if(!owner.current)
+ return
+ UnregisterSignal(src, list(
+ COMSIG_BLOODSUCKER_ON_LIFETICK,
+ COMSIG_LIVING_REVIVE,
+ COMSIG_LIVING_LIFE,
+ COMSIG_LIVING_DEATH,
+ ))
+ UnregisterSignal(SSsunlight, list(
+ COMSIG_SOL_RANKUP_BLOODSUCKERS,
+ COMSIG_SOL_NEAR_START,
+ COMSIG_SOL_END,
+ COMSIG_SOL_RISE_TICK,
+ COMSIG_SOL_WARNING_GIVEN,
+ ))
+ free_all_vassals()
+ DisableAllPowers(forced = TRUE)
+ if(!iscarbon(owner.current))
+ owner.current.gib(TRUE, FALSE, FALSE)
+ return
+ // Drop anything in us and play a tune
+ var/mob/living/carbon/user = owner.current
+ owner.current.drop_all_held_items()
+ owner.current.unequip_everything()
+ user.remove_all_embedded_objects()
+ playsound(owner.current, 'sound/effects/tendril_destroyed.ogg', 40, TRUE)
+
+ var/unique_death = SEND_SIGNAL(src, BLOODSUCKER_FINAL_DEATH)
+ if(unique_death & DONT_DUST)
+ return
+
+ // Elders get dusted, Fledglings get gibbed.
+ if(bloodsucker_level >= 4)
+ user.visible_message(
+ "[user]'s skin crackles and dries, their skin and bones withering to dust. A hollow cry whips from what is now a sandy pile of remains.",
+ "Your soul escapes your withering body as the abyss welcomes you to your Final Death.",
+ "You hear a dry, crackling sound.",
+ )
+ addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living, dust)), 5 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
+ return
+ user.visible_message(
+ "[user]'s skin bursts forth in a spray of gore and detritus. A horrible cry echoes from what is now a wet pile of decaying meat.",
+ "Your soul escapes your withering body as the abyss welcomes you to your Final Death.",
+ "You hear a wet, bursting sound.",
+ )
+ addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living, gib), TRUE, FALSE, FALSE), 2 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
+
+#undef BLOODSUCKER_PASSIVE_BLOOD_DRAIN
diff --git a/code/modules/antagonists/bloodsucker/misc_procs_bloodsucker.dm b/code/modules/antagonists/bloodsucker/misc_procs_bloodsucker.dm
new file mode 100644
index 0000000000000..a67795270df22
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/misc_procs_bloodsucker.dm
@@ -0,0 +1,172 @@
+/datum/antagonist/bloodsucker/proc/on_examine(datum/source, mob/examiner, examine_text)
+ SIGNAL_HANDLER
+
+ if(!iscarbon(source))
+ return
+ var/vamp_examine = return_vamp_examine(examiner)
+ if(vamp_examine)
+ examine_text += vamp_examine
+
+///Called when a Bloodsucker buys a power: (power)
+/datum/antagonist/bloodsucker/proc/BuyPower(datum/action/cooldown/bloodsucker/power)
+ for(var/datum/action/cooldown/bloodsucker/current_powers as anything in powers)
+ if(current_powers.type == power.type)
+ return FALSE
+ powers += power
+
+ power.Grant(owner.current)
+ log_game("[key_name(owner.current)] purchased [power].")
+ return TRUE
+
+///Called when a Bloodsucker loses a power: (power)
+/datum/antagonist/bloodsucker/proc/RemovePower(datum/action/cooldown/bloodsucker/power)
+ if(power.active)
+ power.DeactivatePower()
+ powers -= power
+ power.Remove(owner.current)
+
+///When a Bloodsucker breaks the Masquerade, they get their HUD icon changed, and Malkavian Bloodsuckers get alerted.
+/datum/antagonist/bloodsucker/proc/break_masquerade(mob/admin)
+ if(broke_masquerade)
+ return
+
+ owner.current.playsound_local(null, 'sound/bloodsuckers/lunge_warn.ogg', 100, FALSE, pressure_affected = FALSE)
+ to_chat(owner.current, "You have broken the Masquerade!")
+ to_chat(owner.current, "Bloodsucker Tip: When you break the Masquerade, you become open for termination by fellow Bloodsuckers, and your Vassals are no longer completely loyal to you, as other Bloodsuckers can steal them for themselves!")
+ broke_masquerade = TRUE
+ antag_hud_name = "masquerade_broken"
+ set_antag_hud(owner.current, antag_hud_name)
+ SEND_GLOBAL_SIGNAL(COMSIG_BLOODSUCKER_BROKE_MASQUERADE)
+
+///This is admin-only of reverting a broken masquerade, sadly it doesn't remove the Malkavian objectives yet.
+/datum/antagonist/bloodsucker/proc/fix_masquerade(mob/admin)
+ if(!broke_masquerade)
+ return
+ to_chat(owner.current, "You have re-entered the Masquerade.")
+ broke_masquerade = FALSE
+
+/datum/antagonist/bloodsucker/proc/give_masquerade_infraction()
+ if(broke_masquerade)
+ return
+ masquerade_infractions++
+ if(masquerade_infractions >= 3)
+ break_masquerade()
+ else
+ to_chat(owner.current, "You violated the Masquerade! Break the Masquerade [3 - masquerade_infractions] more times and you will become a criminal to the Bloodsucker's Cause!")
+
+/datum/antagonist/bloodsucker/proc/RankUp()
+ if(!owner || !owner.current || IS_FAVORITE_VASSAL(owner.current))
+ return
+ bloodsucker_level_unspent++
+ if(!my_clan)
+ to_chat(owner.current, "You have gained a rank. Join a Clan to spend it.")
+ return
+ // Spend Rank Immediately?
+ if(!istype(owner.current.loc, /obj/structure/closet/crate/coffin))
+ to_chat(owner, "You have grown more ancient! Sleep in a coffin (or put your Favorite Vassal on a persuasion rack for Ventrue) that you have claimed to thicken your blood and become more powerful.")
+ if(bloodsucker_level_unspent >= 2)
+ to_chat(owner, "You have grown more ancient! Sleep in a coffin (or put your Favorite Vassal on a persuasion rack for Ventrue) that you have claimed to thicken your blood and become more powerful.")
+ return
+ SpendRank()
+
+/datum/antagonist/bloodsucker/proc/RankDown()
+ bloodsucker_level_unspent--
+
+/datum/antagonist/bloodsucker/proc/remove_nondefault_powers(return_levels = FALSE)
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ if(power.purchase_flags & BLOODSUCKER_DEFAULT_POWER)
+ continue
+ RemovePower(power)
+ if(return_levels)
+ bloodsucker_level_unspent++
+
+/datum/antagonist/bloodsucker/proc/LevelUpPowers()
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ if(power.purchase_flags & TREMERE_CAN_BUY)
+ continue
+ power.upgrade_power()
+
+///Disables all powers, accounting for torpor
+/datum/antagonist/bloodsucker/proc/DisableAllPowers(forced = FALSE)
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ if(forced || ((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(owner.current, TRAIT_NODEATH)))
+ if(power.active)
+ power.DeactivatePower()
+
+/datum/antagonist/bloodsucker/proc/SpendRank(mob/living/carbon/human/target, cost_rank = TRUE, blood_cost)
+ if(!owner || !owner.current || !owner.current.client || (cost_rank && bloodsucker_level_unspent <= 0))
+ return
+ SEND_SIGNAL(src, BLOODSUCKER_RANK_UP, target, cost_rank, blood_cost)
+
+/**
+ * Called when a Bloodsucker reaches Final Death
+ * Releases all Vassals and gives them the ex_vassal datum.
+ */
+/datum/antagonist/bloodsucker/proc/free_all_vassals()
+ for(var/datum/antagonist/vassal/all_vassals in vassals)
+ // Skip over any Bloodsucker Vassals, they're too far gone to have all their stuff taken away from them
+ if(all_vassals.owner.has_antag_datum(/datum/antagonist/bloodsucker))
+ all_vassals.owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+ continue
+ if(all_vassals.special_type == REVENGE_VASSAL)
+ continue
+ all_vassals.owner.add_antag_datum(/datum/antagonist/ex_vassal)
+ all_vassals.owner.remove_antag_datum(/datum/antagonist/vassal)
+
+/**
+ * Returns a Vampire's examine strings.
+ * Args:
+ * viewer - The person examining.
+ */
+/datum/antagonist/bloodsucker/proc/return_vamp_examine(mob/living/viewer)
+ if(!viewer.mind)
+ return FALSE
+ // Viewer is Target's Vassal?
+ if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in vassals)
+ var/returnString = "\[This is your Master!\]"
+ var/returnIcon = "[icon2html('icons/bloodsuckers/vampiric.dmi', world, "bloodsucker")]"
+ returnString += "\n"
+ return returnIcon + returnString
+ // Viewer not a Vamp AND not the target's vassal?
+ if(!viewer.mind.has_antag_datum((/datum/antagonist/bloodsucker)) && !(viewer in vassals))
+ if(!broke_masquerade)
+ return FALSE
+ // Default String
+ var/returnString = "\[[return_full_name()]\]"
+ var/returnIcon = "[icon2html('icons/bloodsuckers/vampiric.dmi', world, "bloodsucker")]"
+
+ // In Disguise (Veil)?
+ //if (name_override != null)
+ // returnString += " ([real_name] in disguise!) "
+
+ //returnString += "\n" Don't need spacers. Using . += "" in examine.dm does this on its own.
+ return returnIcon + returnString
+
+/**
+ * CARBON INTEGRATION
+ *
+ * All overrides of mob/living and mob/living/carbon
+ */
+/// Brute
+/mob/living/proc/getBruteLoss_nonProsthetic()
+ return getBruteLoss()
+
+/mob/living/carbon/getBruteLoss_nonProsthetic()
+ var/amount = 0
+ for(var/obj/item/bodypart/chosen_bodypart as anything in bodyparts)
+ if(!IS_ORGANIC_LIMB(chosen_bodypart))
+ continue
+ amount += chosen_bodypart.brute_dam
+ return amount
+
+/// Burn
+/mob/living/proc/getFireLoss_nonProsthetic()
+ return getFireLoss()
+
+/mob/living/carbon/getFireLoss_nonProsthetic()
+ var/amount = 0
+ for(var/obj/item/bodypart/chosen_bodypart as anything in bodyparts)
+ if(!IS_ORGANIC_LIMB(chosen_bodypart))
+ continue
+ amount += chosen_bodypart.burn_dam
+ return amount
diff --git a/code/modules/antagonists/bloodsucker/moodlets_bloodsucker.dm b/code/modules/antagonists/bloodsucker/moodlets_bloodsucker.dm
new file mode 100644
index 0000000000000..13c3987006501
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/moodlets_bloodsucker.dm
@@ -0,0 +1,50 @@
+/datum/mood_event/drankblood
+ description = "I have fed greedly from that which nourishes me.\n"
+ mood_change = 10
+ timeout = 8 MINUTES
+
+/datum/mood_event/drankblood_bad
+ description = "I drank the blood of a lesser creature. Disgusting.\n"
+ mood_change = -4
+ timeout = 3 MINUTES
+
+/datum/mood_event/drankblood_dead
+ description = "I drank dead blood. I am better than this.\n"
+ mood_change = -7
+ timeout = 8 MINUTES
+
+/datum/mood_event/drankblood_synth
+ description = "I drank synthetic blood. What is wrong with me?\n"
+ mood_change = -7
+ timeout = 8 MINUTES
+
+/datum/mood_event/drankkilled
+ description = "I fed off of a dead person. I feel... less human.\n"
+ mood_change = -15
+ timeout = 10 MINUTES
+
+/datum/mood_event/madevamp
+ description = "A mortal has reached an apotheosis- undeath- by my own hand.\n"
+ mood_change = 15
+ timeout = 20 MINUTES
+
+/datum/mood_event/coffinsleep
+ description = "I slept in a coffin during the day. I feel whole again.\n"
+ mood_change = 10
+ timeout = 6 MINUTES
+
+/datum/mood_event/daylight_1
+ description = "I slept poorly in a makeshift coffin during the day.\n"
+ mood_change = -3
+ timeout = 6 MINUTES
+
+/datum/mood_event/daylight_2
+ description = "I have been scorched by the unforgiving rays of the sun.\n"
+ mood_change = -6
+ timeout = 6 MINUTES
+
+///Candelabrum's mood event to non Bloodsucker/Vassals
+/datum/mood_event/vampcandle
+ description = "Something is making your mind feel... loose.\n"
+ mood_change = -15
+ timeout = 5 MINUTES
diff --git a/code/modules/antagonists/bloodsucker/names_bloodsucker.dm b/code/modules/antagonists/bloodsucker/names_bloodsucker.dm
new file mode 100644
index 0000000000000..01e1a29d1007c
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/names_bloodsucker.dm
@@ -0,0 +1,135 @@
+/datum/antagonist/bloodsucker/proc/return_full_name()
+ var/fullname = bloodsucker_name ? bloodsucker_name : owner.current.name
+ if(bloodsucker_title)
+ fullname = "[bloodsucker_title] [fullname]"
+ if(bloodsucker_reputation)
+ fullname += " the [bloodsucker_reputation]"
+
+ return fullname
+
+///Returns a First name for the Bloodsucker.
+/datum/antagonist/bloodsucker/proc/SelectFirstName()
+ if(owner.current.gender == MALE)
+ bloodsucker_name = pick(
+ "Desmond","Rudolph","Dracula","Vlad","Pyotr","Gregor",
+ "Cristian","Christoff","Marcu","Andrei","Constantin",
+ "Gheorghe","Grigore","Ilie","Iacob","Luca","Mihail","Pavel",
+ "Vasile","Octavian","Sorin","Sveyn","Aurel","Alexe","Iustin",
+ "Theodor","Dimitrie","Octav","Damien","Magnus","Caine","Abel", // Romanian/Ancient
+ "Lucius","Gaius","Otho","Balbinus","Arcadius","Romanos","Alexios","Vitellius", // Latin
+ "Melanthus","Teuthras","Orchamus","Amyntor","Axion", // Greek
+ "Thoth","Thutmose","Osorkon,","Nofret","Minmotu","Khafra", // Egyptian
+ "Dio",
+ )
+ else
+ bloodsucker_name = pick(
+ "Islana","Tyrra","Greganna","Pytra","Hilda",
+ "Andra","Crina","Viorela","Viorica","Anemona",
+ "Camelia","Narcisa","Sorina","Alessia","Sophia",
+ "Gladda","Arcana","Morgan","Lasarra","Ioana","Elena",
+ "Alina","Rodica","Teodora","Denisa","Mihaela",
+ "Svetla","Stefania","Diyana","Kelssa","Lilith", // Romanian/Ancient
+ "Alexia","Athanasia","Callista","Karena","Nephele","Scylla","Ursa", // Latin
+ "Alcestis","Damaris","Elisavet","Khthonia","Teodora", // Greek
+ "Nefret","Ankhesenpep", // Egyptian
+ )
+
+///Returns a Title for the Bloodsucker.
+/datum/antagonist/bloodsucker/proc/SelectTitle(am_fledgling = 0, forced = FALSE)
+ // Already have Title
+ if(!forced && bloodsucker_title != null)
+ return
+ // Titles [Master]
+ if(am_fledgling)
+ bloodsucker_title = null
+ return
+ if(owner.current.gender == MALE)
+ bloodsucker_title = pick(
+ "Count",
+ "Baron",
+ "Viscount",
+ "Prince",
+ "Duke",
+ "Tzar",
+ "Dreadlord",
+ "Lord",
+ "Master",
+ )
+ else
+ bloodsucker_title = pick(
+ "Countess",
+ "Baroness",
+ "Viscountess",
+ "Princess",
+ "Duchess",
+ "Tzarina",
+ "Dreadlady",
+ "Lady",
+ "Mistress",
+ )
+ to_chat(owner, "You have earned a title! You are now known as [return_full_name()]!")
+
+///Returns a Reputation for the Bloodsucker.
+/datum/antagonist/bloodsucker/proc/SelectReputation(am_fledgling = FALSE, forced = FALSE)
+ // Already have Reputation
+ if(!forced && bloodsucker_reputation != null)
+ return
+
+ if(am_fledgling)
+ bloodsucker_reputation = pick(
+ "Crude",
+ "Callow",
+ "Unlearned",
+ "Neophyte",
+ "Novice",
+ "Unseasoned",
+ "Fledgling",
+ "Young",
+ "Neonate",
+ "Scrapling",
+ "Untested",
+ "Unproven",
+ "Unknown",
+ "Newly Risen",
+ "Born",
+ "Scavenger",
+ "Unknowing",
+ "Unspoiled",
+ "Disgraced",
+ "Defrocked",
+ "Shamed",
+ "Meek",
+ "Timid",
+ "Broken",
+ "Fresh",
+ )
+ else if(owner.current.gender == MALE && prob(10))
+ bloodsucker_reputation = pick(
+ "King of the Damned",
+ "Blood King",
+ "Emperor of Blades",
+ "Sinlord",
+ "God-King",
+ )
+ else if(owner.current.gender == FEMALE && prob(10))
+ bloodsucker_reputation = pick(
+ "Queen of the Damned",
+ "Blood Queen",
+ "Empress of Blades",
+ "Sinlady",
+ "God-Queen",
+ )
+ else
+ bloodsucker_reputation = pick(
+ "Butcher","Blood Fiend","Crimson","Red","Black","Terror",
+ "Nightman","Feared","Ravenous","Fiend","Malevolent","Wicked",
+ "Ancient","Plaguebringer","Sinister","Forgotten","Wretched","Baleful",
+ "Inqisitor","Harvester","Reviled","Robust","Betrayer","Destructor",
+ "Damned","Accursed","Terrible","Vicious","Profane","Vile",
+ "Depraved","Foul","Slayer","Manslayer","Sovereign","Slaughterer",
+ "Forsaken","Mad","Dragon","Savage","Villainous","Nefarious",
+ "Inquisitor","Marauder","Horrible","Immortal","Undying","Overlord",
+ "Corrupt","Hellspawn","Tyrant","Sanguineous",
+ )
+
+ to_chat(owner, "You have earned a reputation! You are now known as [return_full_name()]!")
diff --git a/code/modules/antagonists/bloodsucker/objectives_bloodsucker.dm b/code/modules/antagonists/bloodsucker/objectives_bloodsucker.dm
new file mode 100644
index 0000000000000..eba340d345a6e
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/objectives_bloodsucker.dm
@@ -0,0 +1,329 @@
+/datum/objective/bloodsucker
+ martyr_compatible = TRUE
+
+// GENERATE
+/datum/objective/bloodsucker/New()
+ update_explanation_text()
+ ..()
+
+//////////////////////////////////////////////////////////////////////////////
+// // PROCS // //
+
+/// Look at all crew members, and for/loop through.
+/datum/objective/bloodsucker/proc/return_possible_targets()
+ var/list/possible_targets = list()
+ for(var/datum/mind/possible_target in get_crewmember_minds())
+ // Check One: Default Valid User
+ if(possible_target != owner && ishuman(possible_target.current) && possible_target.current.stat != DEAD)
+ // Check Two: Am Bloodsucker?
+ if(IS_BLOODSUCKER(possible_target.current))
+ continue
+ possible_targets += possible_target
+
+ return possible_targets
+
+/// Check Vassals and get their occupations
+/datum/objective/bloodsucker/proc/get_vassal_occupations()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(!bloodsuckerdatum || !bloodsuckerdatum.vassals.len)
+ return FALSE
+ var/list/all_vassal_jobs = list()
+ var/vassal_job
+ for(var/datum/antagonist/vassal/bloodsucker_vassals in bloodsuckerdatum.vassals)
+ if(!bloodsucker_vassals || !bloodsucker_vassals.owner) // Must exist somewhere, and as a vassal.
+ continue
+ // Mind Assigned
+ if(bloodsucker_vassals.owner?.assigned_role)
+ vassal_job = bloodsucker_vassals.owner.assigned_role
+ // Mob Assigned
+ else if(bloodsucker_vassals.owner?.current?.job)
+ vassal_job = SSjob.GetJob(bloodsucker_vassals.owner.current.job)
+ // PDA Assigned
+ else if(bloodsucker_vassals.owner?.current && ishuman(bloodsucker_vassals.owner.current))
+ var/mob/living/carbon/human/vassal = bloodsucker_vassals.owner.current
+ vassal_job = SSjob.GetJob(vassal.get_assignment())
+ if(vassal_job)
+ all_vassal_jobs += vassal_job
+ return all_vassal_jobs
+
+//////////////////////////////////////////////////////////////////////////////////////
+// // OBJECTIVES // //
+//////////////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////
+// DEFAULT OBJECTIVES //
+//////////////////////////////
+
+/datum/objective/bloodsucker/lair
+ name = "claimlair"
+
+// EXPLANATION
+/datum/objective/bloodsucker/lair/update_explanation_text()
+ explanation_text = "Create a lair by claiming a coffin, and protect it until the end of the shift."// Make sure to keep it safe!"
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/lair/check_completion()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum && bloodsuckerdatum.coffin && bloodsuckerdatum.bloodsucker_lair_area)
+ return TRUE
+ return FALSE
+
+/// Space_Station_13_areas.dm <--- all the areas
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+/datum/objective/survive/bloodsucker
+ name = "bloodsuckersurvive"
+ explanation_text = "Survive the entire shift without succumbing to Final Death."
+
+// WIN CONDITIONS?
+// Handled by parent
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+
+/// Vassalize a certain person / people
+/datum/objective/bloodsucker/conversion
+ name = "vassalization"
+
+/////////////////////////////////
+
+// Vassalize a head of staff
+/datum/objective/bloodsucker/conversion/command
+ name = "vassalizationcommand"
+ target_amount = 1
+
+// EXPLANATION
+/datum/objective/bloodsucker/conversion/command/update_explanation_text()
+ explanation_text = "Guarantee a Vassal ends up as a Department Head or in a Leadership role."
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/conversion/command/check_completion()
+ var/list/vassal_jobs = get_vassal_occupations()
+ for(var/datum/job/checked_job in vassal_jobs)
+ if(checked_job.departments & DEPT_BITFLAG_COM)
+ return TRUE // We only need one, so we stop as soon as we get a match
+ return FALSE
+
+/////////////////////////////////
+
+// Vassalize crewmates in a department
+/datum/objective/bloodsucker/conversion/department
+ name = "vassalize department"
+
+ ///The selected department we have to vassalize.
+ var/target_department
+ ///List of all departments that can be selected for the objective.
+ var/static/list/possible_departments = list(
+ "engineering" = DEPT_BITFLAG_ENG,
+ "medical" = DEPT_BITFLAG_MED,
+ "science" = DEPT_BITFLAG_SCI,
+ "cargo" = DEPT_BITFLAG_CAR,
+ "service" = DEPT_BITFLAG_SRV,
+ )
+
+
+// GENERATE!
+/datum/objective/bloodsucker/conversion/department/New()
+ target_department = pick(possible_departments)
+ target_amount = rand(2, 3)
+ return ..()
+
+// EXPLANATION
+/datum/objective/bloodsucker/conversion/department/update_explanation_text()
+ explanation_text = "Have [target_amount] Vassal[target_amount == 1 ? "" : "s"] in the [target_department] department."
+ return ..()
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/conversion/department/check_completion()
+ var/list/vassal_jobs = get_vassal_occupations()
+ var/converted_count = 0
+ for(var/datum/job/checked_job in vassal_jobs)
+ if(checked_job.departments & target_department)
+ converted_count++
+ if(converted_count >= target_amount)
+ return TRUE
+ return FALSE
+
+ /**
+ * # IMPORTANT NOTE!!
+ *
+ * Look for Job Values on mobs! This is assigned at the start, but COULD be changed via the HoP
+ * ALSO - Search through all jobs (look for prefs earlier that look for all jobs, and search through all jobs to see if their head matches the head listed, or it IS the head)
+ * ALSO - registered_account in _vending.dm for banks, and assigning new ones.
+ */
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+// NOTE: Look up /steal in objective.dm for inspiration.
+/// Steal hearts. You just really wanna have some hearts.
+/datum/objective/bloodsucker/heartthief
+ name = "heartthief"
+
+// GENERATE!
+/datum/objective/bloodsucker/heartthief/New()
+ target_amount = rand(2,3)
+ ..()
+
+// EXPLANATION
+/datum/objective/bloodsucker/heartthief/update_explanation_text()
+ . = ..()
+ explanation_text = "Steal and keep [target_amount] organic heart\s."
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/heartthief/check_completion()
+ if(!owner.current)
+ return FALSE
+
+ var/list/all_items = owner.current.get_contents()
+ var/heart_count = 0
+ for(var/obj/item/organ/heart/current_hearts in all_items)
+ if(current_hearts.organ_flags & ORGAN_SYNTHETIC) // No robo-hearts allowed
+ continue
+ heart_count++
+
+ if(heart_count >= target_amount)
+ return TRUE
+ return FALSE
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+///Eat blood from a lot of people
+/datum/objective/bloodsucker/gourmand
+ name = "gourmand"
+
+// GENERATE!
+/datum/objective/bloodsucker/gourmand/New()
+ target_amount = rand(450,650)
+ ..()
+
+// EXPLANATION
+/datum/objective/bloodsucker/gourmand/update_explanation_text()
+ . = ..()
+ explanation_text = "Using your Feed ability, drink [target_amount] units of Blood."
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/gourmand/check_completion()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(!bloodsuckerdatum)
+ return FALSE
+ var/stolen_blood = bloodsuckerdatum.total_blood_drank
+ if(stolen_blood >= target_amount)
+ return TRUE
+ return FALSE
+
+// HOW: Track each feed (if human). Count victory.
+
+// NOTE: Look up /assassinate in objective.dm for inspiration.6
+/// Vassalize a target.
+/datum/objective/bloodsucker/vassalhim
+ name = "vassalhim"
+ var/target_department_type = FALSE
+
+/datum/objective/bloodsucker/vassalhim/New()
+ find_target()
+ ..()
+
+// EXPLANATION
+/datum/objective/bloodsucker/vassalhim/update_explanation_text()
+ . = ..()
+ if(target?.current)
+ explanation_text = "Ensure [target.name], the [target.assigned_role], is Vassalized via the Persuasion Rack."
+ else
+ explanation_text = "Free Objective"
+
+/datum/objective/bloodsucker/vassalhim/admin_edit(mob/admin)
+ admin_simple_target_pick(admin)
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/vassalhim/check_completion()
+ if(!target || target.has_antag_datum(/datum/antagonist/vassal))
+ return TRUE
+ return FALSE
+
+
+
+//////////////////////////////
+// CLAN OBJECTIVES //
+//////////////////////////////
+
+/// Steal the Archive of the Kindred - Nosferatu Clan objective
+/datum/objective/bloodsucker/kindred
+ name = "steal kindred"
+
+// EXPLANATION
+/datum/objective/bloodsucker/kindred/update_explanation_text()
+ . = ..()
+ explanation_text = "Ensure Nosferatu steals and keeps control over the Archive of the Kindred."
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/kindred/check_completion()
+ if(!owner.current)
+ return FALSE
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(!bloodsuckerdatum)
+ return FALSE
+
+ for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
+ var/obj/item/book/kindred/the_book = locate() in bloodsucker_minds.current.get_contents()
+ if(the_book)
+ return TRUE
+ return FALSE
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+/// Max out a Tremere Power - Tremere Clan objective
+/datum/objective/bloodsucker/tremere_power
+ name = "tremerepower"
+
+// EXPLANATION
+/datum/objective/bloodsucker/tremere_power/update_explanation_text()
+ explanation_text = "Upgrade a Blood Magic power to the maximum level, remember that Vassalizing gives more Ranks!"
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/tremere_power/check_completion()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
+ for(var/datum/action/cooldown/bloodsucker/targeted/tremere/tremere_powers in bloodsuckerdatum.powers)
+ if(tremere_powers.level_current >= 5)
+ return TRUE
+ return FALSE
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+/// Convert a crewmate - Ventrue Clan objective
+/datum/objective/bloodsucker/embrace
+ name = "embrace"
+
+// EXPLANATION
+/datum/objective/bloodsucker/embrace/update_explanation_text()
+ . = ..()
+ explanation_text = "Use the Candelabrum to Rank your Favorite Vassal up enough to become a Bloodsucker."
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/embrace/check_completion()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(!bloodsuckerdatum)
+ return FALSE
+ for(var/datum/antagonist/vassal/vassaldatum in bloodsuckerdatum.vassals)
+ if(IS_FAVORITE_VASSAL(vassaldatum.owner.current))
+ if(vassaldatum.owner.has_antag_datum(/datum/antagonist/bloodsucker))
+ return TRUE
+ return FALSE
+
+
+
+//////////////////////////////
+// VASSAL OBJECTIVES //
+//////////////////////////////
+
+/datum/objective/bloodsucker/vassal
+
+// EXPLANATION
+/datum/objective/bloodsucker/vassal/update_explanation_text()
+ . = ..()
+ explanation_text = "Guarantee the success of your Master's mission!"
+
+// WIN CONDITIONS?
+/datum/objective/bloodsucker/vassal/check_completion()
+ var/datum/antagonist/vassal/antag_datum = owner.has_antag_datum(/datum/antagonist/vassal)
+ return antag_datum.master?.owner?.current?.stat != DEAD
diff --git a/code/modules/antagonists/bloodsucker/powers/_power.dm b/code/modules/antagonists/bloodsucker/powers/_power.dm
new file mode 100644
index 0000000000000..9041fb4b7228e
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/_power.dm
@@ -0,0 +1,216 @@
+/datum/action/cooldown/bloodsucker
+ name = "Vampiric Gift"
+ desc = "A vampiric gift."
+ button_icon = 'icons/bloodsuckers/actions_bloodsucker.dmi'
+ background_icon_state = "vamp_power_off"
+ icon_icon = 'icons/bloodsuckers/actions_bloodsucker.dmi'
+ button_icon_state = "power_feed"
+ buttontooltipstyle = "cult"
+ transparent_when_unavailable = TRUE
+
+ /// Cooldown you'll have to wait between each use, decreases depending on level.
+ cooldown_time = 2 SECONDS
+
+ ///Background icon when the Power is active.
+ var/active_background_icon_state = "vamp_power_on"
+ ///Background icon when the Power is NOT active.
+ var/base_background_icon_state = "vamp_power_off"
+
+ var/background_icon_state_on = "vamp_power_on"
+ var/background_icon_state_off = "vamp_power_off"
+
+ /// The text that appears when using the help verb, meant to explain how the Power changes when ranking up.
+ var/power_explanation = ""
+ ///The owner's stored Bloodsucker datum
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum_power
+
+ // FLAGS //
+ /// The effects on this Power (Toggled/Single Use/Static Cooldown)
+ var/power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN|BP_AM_COSTLESS_UNCONSCIOUS
+ /// Requirement flags for checks
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ /// Who can purchase the Power
+ var/purchase_flags = NONE // BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER|TREMERE_CAN_BUY|VASSAL_CAN_BUY
+
+ // VARS //
+ /// If the Power is currently active, differs from action cooldown because of how powers are handled.
+ var/active = FALSE
+ ///Can increase to yield new abilities - Each Power ranks up each Rank
+ var/level_current = 0
+ ///The cost to ACTIVATE this Power
+ var/bloodcost = 0
+ ///The cost to MAINTAIN this Power - Only used for Constant Cost Powers
+ var/constant_bloodcost = 0
+
+// Modify description to add cost.
+/datum/action/cooldown/bloodsucker/New(Target)
+ . = ..()
+ if(bloodcost > 0)
+ desc += "
COST: [bloodcost] Blood"
+ if(constant_bloodcost > 0)
+ desc += "
CONSTANT COST: [name] costs [constant_bloodcost] Blood maintain active."
+ if(power_flags & BP_AM_SINGLEUSE)
+ desc += "
SINGLE USE: [name] can only be used once per night."
+
+/datum/action/cooldown/bloodsucker/Destroy()
+ bloodsuckerdatum_power = null
+ return ..()
+
+/datum/action/cooldown/bloodsucker/IsAvailable(feedback = FALSE)
+ return next_use_time <= world.time
+
+/datum/action/cooldown/bloodsucker/Grant(mob/user)
+ . = ..()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner)
+ if(bloodsuckerdatum)
+ bloodsuckerdatum_power = bloodsuckerdatum
+
+//This is when we CLICK on the ability Icon, not USING.
+/datum/action/cooldown/bloodsucker/Trigger(trigger_flags, atom/target)
+ if(active && can_deactivate()) // Active? DEACTIVATE AND END!
+ DeactivatePower()
+ return FALSE
+ if(!can_pay_cost() || !can_use(owner, trigger_flags))
+ return FALSE
+ pay_cost()
+ ActivatePower(trigger_flags)
+ if(!(power_flags & BP_AM_TOGGLE) || !active)
+ StartCooldown()
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/proc/can_pay_cost()
+ if(!owner || !owner.mind)
+ return FALSE
+ // Cooldown?
+ if(!COOLDOWN_FINISHED(src, next_use_time))
+ owner.balloon_alert(owner, "power unavailable!")
+ return FALSE
+ if(!bloodsuckerdatum_power)
+ var/mob/living/living_owner = owner
+ if(!HAS_TRAIT(living_owner, TRAIT_NO_BLOOD) && living_owner.blood_volume < bloodcost)
+ to_chat(owner, "You need at least [bloodcost] blood to activate [name]")
+ return FALSE
+ return TRUE
+
+ // Have enough blood? Bloodsuckers in a Frenzy don't need to pay them
+ if(bloodsuckerdatum_power.frenzied)
+ return TRUE
+ if(bloodsuckerdatum_power.bloodsucker_blood_volume < bloodcost)
+ to_chat(owner, "You need at least [bloodcost] blood to activate [name]")
+ return FALSE
+ return TRUE
+
+///Called when the Power is upgraded.
+/datum/action/cooldown/bloodsucker/proc/upgrade_power()
+ level_current++
+
+///Checks if the Power is available to use.
+/datum/action/cooldown/bloodsucker/proc/can_use(mob/living/carbon/user, trigger_flags)
+ if(!owner)
+ return FALSE
+ if(!isliving(user))
+ return FALSE
+ // Torpor?
+ if((check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(user, TRAIT_NODEATH))
+ to_chat(user, "Not while you're in Torpor.")
+ return FALSE
+ // Frenzy?
+ if((check_flags & BP_CANT_USE_IN_FRENZY) && (bloodsuckerdatum_power?.frenzied))
+ to_chat(user, "You cannot use powers while in a Frenzy!")
+ return FALSE
+ // Stake?
+ if((check_flags & BP_CANT_USE_WHILE_STAKED) && user.am_staked())
+ to_chat(user, "You have a stake in your chest! Your powers are useless.")
+ return FALSE
+ // Conscious? -- We use our own (AB_CHECK_CONSCIOUS) here so we can control it more, like the error message.
+ if((check_flags & BP_CANT_USE_WHILE_UNCONSCIOUS) && user.stat != CONSCIOUS)
+ to_chat(user, "You can't do this while you are unconcious!")
+ return FALSE
+ // Incapacitated?
+ if((check_flags & BP_CANT_USE_WHILE_INCAPACITATED) && (user.incapacitated(IGNORE_RESTRAINTS, IGNORE_GRAB)))
+ to_chat(user, "Not while you're incapacitated!")
+ return FALSE
+ // Constant Cost (out of blood)
+ if(constant_bloodcost > 0 && bloodsuckerdatum_power?.bloodsucker_blood_volume <= 0)
+ to_chat(user, "You don't have the blood to upkeep [src].")
+ return FALSE
+ return TRUE
+
+/// NOTE: With this formula, you'll hit half cooldown at level 8 for that power.
+/datum/action/cooldown/bloodsucker/StartCooldown()
+ // Calculate Cooldown (by power's level)
+ if(power_flags & BP_AM_STATIC_COOLDOWN)
+ cooldown_time = initial(cooldown_time)
+ else
+ cooldown_time = max(initial(cooldown_time) / 2, initial(cooldown_time) - (initial(cooldown_time) / 16 * (level_current-1)))
+
+ return ..()
+
+/datum/action/cooldown/bloodsucker/proc/can_deactivate()
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/UpdateButtonIcon(force = FALSE)
+ background_icon_state = active ? background_icon_state_on : background_icon_state_off
+ . = ..()
+
+/datum/action/cooldown/bloodsucker/proc/pay_cost()
+ // Non-bloodsuckers will pay in other ways.
+ if(!bloodsuckerdatum_power)
+ var/mob/living/living_owner = owner
+ if(!HAS_TRAIT(living_owner, TRAIT_NO_BLOOD))
+ living_owner.blood_volume -= bloodcost
+ return
+ // Bloodsuckers in a Frenzy don't have enough Blood to pay it, so just don't.
+ if(bloodsuckerdatum_power.frenzied)
+ return
+ bloodsuckerdatum_power.bloodsucker_blood_volume -= bloodcost
+ bloodsuckerdatum_power.update_hud()
+
+/datum/action/cooldown/bloodsucker/proc/ActivatePower(trigger_flags)
+ active = TRUE
+ if(power_flags & BP_AM_TOGGLE)
+ START_PROCESSING(SSprocessing, src)
+
+ owner.log_message("used [src][bloodcost != 0 ? " at the cost of [bloodcost]" : ""].", LOG_ATTACK, color="red")
+ UpdateButtonIcon()
+
+/datum/action/cooldown/bloodsucker/proc/DeactivatePower()
+ if(!active) //Already inactive? Return
+ return
+ if(power_flags & BP_AM_TOGGLE)
+ STOP_PROCESSING(SSprocessing, src)
+ if(power_flags & BP_AM_SINGLEUSE)
+ remove_after_use()
+ return
+ active = FALSE
+ StartCooldown()
+ UpdateButtonIcon()
+
+///Used by powers that are continuously active (That have BP_AM_TOGGLE flag)
+/datum/action/cooldown/bloodsucker/process(seconds_per_tick)
+ SHOULD_CALL_PARENT(TRUE) //Need this to call parent so the cooldown system works
+ . = ..()
+ if(!ContinueActive(owner)) // We can't afford the Power? Deactivate it.
+ DeactivatePower()
+ return FALSE
+ // We can keep this up (For now), so Pay Cost!
+ if(!(power_flags & BP_AM_COSTLESS_UNCONSCIOUS) && owner.stat != CONSCIOUS)
+ if(bloodsuckerdatum_power)
+ bloodsuckerdatum_power.AddBloodVolume(-constant_bloodcost)
+ else
+ var/mob/living/living_owner = owner
+ if(!HAS_TRAIT(living_owner, TRAIT_NO_BLOOD))
+ living_owner.blood_volume -= constant_bloodcost
+ return TRUE
+
+/// Checks to make sure this power can stay active
+/datum/action/cooldown/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target)
+ if(!user)
+ return FALSE
+ if(!constant_bloodcost > 0 || bloodsuckerdatum_power.bloodsucker_blood_volume > 0)
+ return TRUE
+
+/// Used to unlearn Single-Use Powers
+/datum/action/cooldown/bloodsucker/proc/remove_after_use()
+ bloodsuckerdatum_power?.powers -= src
+ Remove(owner)
diff --git a/code/modules/antagonists/bloodsucker/powers/cloak.dm b/code/modules/antagonists/bloodsucker/powers/cloak.dm
new file mode 100644
index 0000000000000..4a796e228aeb5
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/cloak.dm
@@ -0,0 +1,69 @@
+/datum/action/cooldown/bloodsucker/cloak
+ name = "Cloak of Darkness"
+ desc = "Blend into the shadows and become invisible to the untrained and Artificial eye."
+ button_icon_state = "power_cloak"
+ power_explanation = "Cloak of Darkness:\n\
+ Activate this Power in the shadows and you will slowly turn nearly invisible.\n\
+ While using Cloak of Darkness, attempting to run will crush you.\n\
+ Additionally, while Cloak is active, you are completely invisible to the AI.\n\
+ Higher levels will increase how invisible you are."
+ power_flags = BP_AM_TOGGLE
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
+ bloodcost = 5
+ constant_bloodcost = 0.2
+ cooldown_time = 5 SECONDS
+ var/was_running
+
+/// Must have nobody around to see the cloak
+/datum/action/cooldown/bloodsucker/cloak/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ for(var/mob/living/watchers in view(9, owner) - owner)
+ owner.balloon_alert(owner, "you can only vanish unseen.")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/cloak/ActivatePower(trigger_flags)
+ . = ..()
+ var/mob/living/user = owner
+ was_running = (user.m_intent == MOVE_INTENT_RUN)
+ if(was_running)
+ user.toggle_move_intent()
+ user.AddElement(/datum/element/digital_camo)
+ user.balloon_alert(user, "cloak turned on.")
+
+/datum/action/cooldown/bloodsucker/cloak/process(seconds_per_tick)
+ // Checks that we can keep using this.
+ . = ..()
+ if(!.)
+ return
+ if(!active)
+ return
+ var/mob/living/user = owner
+ animate(user, alpha = max(25, owner.alpha - min(75, 10 + 5 * level_current)), time = 1.5 SECONDS)
+ // Prevents running while on Cloak of Darkness
+ if(user.m_intent != MOVE_INTENT_WALK)
+ owner.balloon_alert(owner, "you attempt to run, crushing yourself.")
+ user.toggle_move_intent()
+ user.adjustBruteLoss(rand(5,15))
+
+/datum/action/cooldown/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
+ . = ..()
+ if(!.)
+ return FALSE
+ /// Must be CONSCIOUS
+ if(user.stat != CONSCIOUS)
+ to_chat(owner, "Your cloak failed due to you falling unconcious!")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/cloak/DeactivatePower()
+ var/mob/living/user = owner
+ animate(user, alpha = 255, time = 1 SECONDS)
+ user.RemoveElement(/datum/element/digital_camo)
+ if(was_running && user.m_intent == MOVE_INTENT_WALK)
+ user.toggle_move_intent()
+ user.balloon_alert(user, "cloak turned off.")
+ return ..()
diff --git a/code/modules/antagonists/bloodsucker/powers/feed.dm b/code/modules/antagonists/bloodsucker/powers/feed.dm
new file mode 100644
index 0000000000000..d5c35dbed04bd
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/feed.dm
@@ -0,0 +1,249 @@
+#define FEED_NOTICE_RANGE 2
+#define FEED_DEFAULT_TIMER (10 SECONDS)
+
+/datum/action/cooldown/bloodsucker/feed
+ name = "Feed"
+ desc = "Feed blood off of a living creature."
+ button_icon_state = "power_feed"
+ power_explanation = "Feed:\n\
+ Activate Feed while next to someone and you will begin to feed blood off of them.\n\
+ The time needed before you start feeding speeds up the higher level you are.\n\
+ Feeding off of someone while you have them aggressively grabbed will put them to sleep.\n\
+ While feeding, you can't speak, as your mouth is covered.\n\
+ Feeding while nearby (2 tiles away from) a mortal who is unaware of Bloodsuckers' existence, will cause a Masquerade Infraction\n\
+ If you get too many Masquerade Infractions, you will break the Masquerade.\n\
+ If you are in desperate need of blood, mice can be fed off of, at a cost."
+ power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER
+ bloodcost = 0
+ cooldown_time = 15 SECONDS
+ ///Amount of blood taken, reset after each Feed. Used for logging.
+ var/blood_taken = 0
+ ///The amount of Blood a target has since our last feed, this loops and lets us not spam alerts of low blood.
+ var/warning_target_bloodvol = BLOOD_VOLUME_MAXIMUM
+ ///Reference to the target we've fed off of
+ var/datum/weakref/target_ref
+ ///Are we feeding with passive grab or not?
+ var/silent_feed = TRUE
+
+/datum/action/cooldown/bloodsucker/feed/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(target_ref) //already sucking blood.
+ return FALSE
+ if(user.is_mouth_covered() && !isplasmaman(user))
+ owner.balloon_alert(owner, "mouth covered!")
+ return FALSE
+ //Find target, it will alert what the problem is, if any.
+ if(!find_target())
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/feed/ContinueActive(mob/living/user, mob/living/target)
+ if(!target)
+ return FALSE
+ if(!user.Adjacent(target))
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/feed/DeactivatePower()
+ var/mob/living/user = owner
+ if(target_ref)
+ var/mob/living/feed_target = target_ref.resolve()
+ log_combat(user, feed_target, "fed on blood", addition="(and took [blood_taken] blood)")
+ to_chat(user, "You slowly release [feed_target].")
+ if(feed_target.stat == DEAD)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankkilled", /datum/mood_event/drankkilled)
+ bloodsuckerdatum_power.AddHumanityLost(10)
+
+ target_ref = null
+ warning_target_bloodvol = BLOOD_VOLUME_MAXIMUM
+ blood_taken = 0
+ REMOVE_TRAIT(user, TRAIT_IMMOBILIZED, FEED_TRAIT)
+ REMOVE_TRAIT(user, TRAIT_MUTE, FEED_TRAIT)
+ return ..()
+
+/datum/action/cooldown/bloodsucker/feed/ActivatePower(trigger_flags)
+ var/mob/living/feed_target = target_ref.resolve()
+ if(istype(feed_target, /mob/living/simple_animal/mouse))
+ to_chat(owner, "You recoil at the taste of a lesser lifeform.")
+ if(bloodsuckerdatum_power.my_clan && bloodsuckerdatum_power.my_clan.blood_drink_type != BLOODSUCKER_DRINK_INHUMANELY)
+ var/mob/living/user = owner
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
+ bloodsuckerdatum_power.AddHumanityLost(1)
+ bloodsuckerdatum_power.AddBloodVolume(25)
+ DeactivatePower()
+ feed_target.death()
+ return
+ var/feed_timer = clamp(round(FEED_DEFAULT_TIMER / (1.25 * (level_current || 1))), 1, FEED_DEFAULT_TIMER)
+ if(bloodsuckerdatum_power.frenzied)
+ feed_timer = 2 SECONDS
+
+ owner.balloon_alert(owner, "feeding off [feed_target]...")
+ if(!do_after(owner, feed_timer, feed_target, NONE, TRUE))
+ owner.balloon_alert(owner, "feed stopped")
+ DeactivatePower()
+ return
+ if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
+ if(!IS_BLOODSUCKER(feed_target) && !IS_VASSAL(feed_target) && !IS_CURATOR(feed_target))
+ feed_target.Unconscious((5 + level_current) SECONDS)
+ if(!feed_target.density)
+ feed_target.Move(owner.loc)
+ owner.visible_message(
+ "[owner] closes [owner.p_their()] mouth around [feed_target]'s neck!",
+ "You sink your fangs into [feed_target]'s neck."
+ )
+ silent_feed = FALSE //no more mr nice guy
+ else
+ // Only people who AREN'T the target will notice this action.
+ var/dead_message = feed_target.stat != DEAD ? " [feed_target.p_they(TRUE)] looks dazed, and will not remember this." : ""
+ owner.visible_message(
+ "[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth.", \
+ "You slip your fangs into [feed_target]'s wrist.[dead_message]", \
+ vision_distance = FEED_NOTICE_RANGE, ignored_mobs = feed_target)
+
+ //check if we were seen
+ for(var/mob/living/watcher in oviewers(FEED_NOTICE_RANGE) - feed_target)
+ if(!watcher.client)
+ continue
+ if(watcher.has_unlimited_silicon_privilege)
+ continue
+ if(watcher.stat >= DEAD)
+ continue
+ if(watcher.is_blind() || HAS_TRAIT(watcher, TRAIT_NEARSIGHT))
+ continue
+ if(IS_BLOODSUCKER(watcher) || IS_VASSAL(watcher))
+ continue
+ owner.balloon_alert(owner, "feed noticed!")
+ bloodsuckerdatum_power.give_masquerade_infraction()
+ break
+
+ ADD_TRAIT(owner, TRAIT_MUTE, FEED_TRAIT)
+ ADD_TRAIT(owner, TRAIT_IMMOBILIZED, FEED_TRAIT)
+ return ..()
+
+/datum/action/cooldown/bloodsucker/feed/process(seconds_per_tick)
+ if(!active) //If we aren't active (running on SSfastprocess)
+ return ..() //Manage our cooldown timers
+ var/mob/living/user = owner
+ var/mob/living/feed_target = target_ref.resolve()
+ if(!ContinueActive(user, feed_target))
+ if(!silent_feed)
+ user.visible_message(
+ "[user] is ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!",
+ "Your teeth are ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!",
+ )
+ // Deal Damage to Target (should have been more careful!)
+ if(iscarbon(feed_target))
+ var/mob/living/carbon/carbon_target = feed_target
+ carbon_target.bleed(15)
+ playsound(get_turf(feed_target), 'sound/effects/splat.ogg', 40, TRUE)
+ if(ishuman(feed_target))
+ var/mob/living/carbon/human/target_user = feed_target
+ target_user.add_bleeding(BLEED_CRITICAL)
+ feed_target.add_splatter_floor(get_turf(feed_target))
+ user.add_mob_blood(feed_target) // Put target's blood on us. The donor goes in the ( )
+ feed_target.add_mob_blood(feed_target)
+ feed_target.apply_damage(10, BRUTE, BODY_ZONE_HEAD)
+ INVOKE_ASYNC(feed_target, TYPE_PROC_REF(/mob, emote), "scream")
+ DeactivatePower()
+ return
+
+ var/feed_strength_mult = 0
+ if(bloodsuckerdatum_power.frenzied)
+ feed_strength_mult = 2
+ else if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
+ feed_strength_mult = 1
+ else
+ feed_strength_mult = 0.3
+ blood_taken += bloodsuckerdatum_power.handle_feeding(feed_target, feed_strength_mult, level_current)
+
+ if(feed_strength_mult > 5 && feed_target.stat < DEAD)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood)
+ // Drank mindless as Ventrue? - BAD
+ if((bloodsuckerdatum_power.my_clan && bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY) && !feed_target.mind)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
+ if(feed_target.stat >= DEAD)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
+
+ if(!IS_BLOODSUCKER(feed_target))
+ if(feed_target.blood_volume <= BLOOD_VOLUME_BAD && warning_target_bloodvol > BLOOD_VOLUME_BAD)
+ owner.balloon_alert(owner, "your victim's blood is fatally low!")
+ else if(feed_target.blood_volume <= BLOOD_VOLUME_OKAY && warning_target_bloodvol > BLOOD_VOLUME_OKAY)
+ owner.balloon_alert(owner, "your victim's blood is dangerously low.")
+ else if(feed_target.blood_volume <= BLOOD_VOLUME_SAFE && warning_target_bloodvol > BLOOD_VOLUME_SAFE)
+ owner.balloon_alert(owner, "your victim's blood is at an unsafe level.")
+ warning_target_bloodvol = feed_target.blood_volume
+
+ if(bloodsuckerdatum_power.bloodsucker_blood_volume >= bloodsuckerdatum_power.max_blood_volume)
+ user.balloon_alert(owner, "full on blood!")
+ DeactivatePower()
+ return
+ if(feed_target.blood_volume <= 0)
+ user.balloon_alert(owner, "no blood left!")
+ DeactivatePower()
+ return
+ owner.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
+ //play sound to target to show they're dying.
+ if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
+ feed_target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
+
+/datum/action/cooldown/bloodsucker/feed/proc/find_target()
+ if(owner.pulling && isliving(owner.pulling))
+ if(!can_feed_from(owner.pulling, give_warnings = TRUE))
+ return FALSE
+ target_ref = WEAKREF(owner.pulling)
+ return TRUE
+
+ var/list/close_living_mobs = list()
+ var/list/close_dead_mobs = list()
+ for(var/mob/living/near_targets in oview(1, owner))
+ if(!owner.Adjacent(near_targets))
+ continue
+ if(near_targets.stat)
+ close_living_mobs |= near_targets
+ else
+ close_dead_mobs |= near_targets
+ //Check living first
+ for(var/mob/living/suckers in close_living_mobs)
+ if(can_feed_from(suckers))
+ target_ref = WEAKREF(suckers)
+ return TRUE
+ //If not, check dead
+ for(var/mob/living/suckers in close_dead_mobs)
+ if(can_feed_from(suckers))
+ target_ref = WEAKREF(suckers)
+ return TRUE
+ //No one to suck blood from.
+ return FALSE
+
+/datum/action/cooldown/bloodsucker/feed/proc/can_feed_from(mob/living/target, give_warnings = FALSE)
+ if(istype(target, /mob/living/simple_animal/mouse))
+ if(bloodsuckerdatum_power.my_clan && bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY)
+ if(give_warnings)
+ owner.balloon_alert(owner, "too disgusting!")
+ return FALSE
+ return TRUE
+ //Mice check done, only humans are otherwise allowed
+ if(!ishuman(target))
+ return FALSE
+
+ var/mob/living/carbon/human/target_user = target
+ if(!(target_user.dna?.species) || !(target_user.mob_biotypes & MOB_ORGANIC))
+ if(give_warnings)
+ owner.balloon_alert(owner, "no blood!")
+ return FALSE
+ if(!target_user.can_inject(owner, BODY_ZONE_HEAD, (1 << 0)))
+ if(give_warnings)
+ owner.balloon_alert(owner, "suit too thick!")
+ return FALSE
+ if((bloodsuckerdatum_power.my_clan && bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY) && !target_user.mind && !bloodsuckerdatum_power.frenzied)
+ if(give_warnings)
+ owner.balloon_alert(owner, "cant drink from mindless!")
+ return FALSE
+ return TRUE
+
+#undef FEED_NOTICE_RANGE
+#undef FEED_DEFAULT_TIMER
diff --git a/code/modules/antagonists/bloodsucker/powers/fortitude.dm b/code/modules/antagonists/bloodsucker/powers/fortitude.dm
new file mode 100644
index 0000000000000..635ab1b054c53
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/fortitude.dm
@@ -0,0 +1,75 @@
+/datum/action/cooldown/bloodsucker/fortitude
+ name = "Fortitude"
+ desc = "Withstand egregious physical wounds and walk away from attacks that would stun, pierce, and dismember lesser beings."
+ button_icon_state = "power_fortitude"
+ power_explanation = "Fortitude:\n\
+ Activating Fortitude will provide pierce, stun and dismember immunity.\n\
+ You will additionally gain resistance to Brute and Stamina damge, scaling with level.\n\
+ While using Fortitude, attempting to run will crush you.\n\
+ At level 4, you gain complete stun immunity.\n\
+ Higher levels will increase Brute and Stamina resistance."
+ power_flags = BP_AM_TOGGLE|BP_AM_COSTLESS_UNCONSCIOUS
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY
+ purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
+ bloodcost = 30
+ cooldown_time = 8 SECONDS
+ constant_bloodcost = 0.2
+ var/was_running
+ var/fortitude_resist // So we can raise and lower your brute resist based on what your level_current WAS.
+
+/datum/action/cooldown/bloodsucker/fortitude/ActivatePower(trigger_flags)
+ . = ..()
+ owner.balloon_alert(owner, "fortitude turned on.")
+ to_chat(owner, "Your flesh, skin, and muscles become as hard as steel.")
+ // Traits & Effects
+ ADD_TRAIT(owner, TRAIT_PIERCEIMMUNE, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(owner, TRAIT_NODISMEMBER, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(owner, TRAIT_PUSHIMMUNE, BLOODSUCKER_TRAIT)
+ if(level_current >= 4)
+ ADD_TRAIT(owner, TRAIT_STUNIMMUNE, BLOODSUCKER_TRAIT) // They'll get stun resistance + this, who cares.
+ var/mob/living/carbon/human/bloodsucker_user = owner
+ if(IS_BLOODSUCKER(owner) || IS_VASSAL(owner))
+ fortitude_resist = max(0.3, 0.7 - level_current * 0.1)
+ bloodsucker_user.physiology.brute_mod *= fortitude_resist
+ bloodsucker_user.physiology.stamina_mod *= fortitude_resist
+
+ was_running = (owner.m_intent == MOVE_INTENT_RUN)
+ if(was_running)
+ owner.toggle_move_intent()
+
+/datum/action/cooldown/bloodsucker/fortitude/process(seconds_per_tick)
+ // Checks that we can keep using this.
+ . = ..()
+ if(!.)
+ return
+ if(!active)
+ return
+ var/mob/living/carbon/user = owner
+ /// Prevents running while on Fortitude
+ if(user.m_intent != MOVE_INTENT_WALK)
+ user.toggle_move_intent()
+ user.balloon_alert(user, "you attempt to run, crushing yourself.")
+ user.adjustBruteLoss(rand(5,15))
+ /// We don't want people using fortitude being able to use vehicles
+ if(user.buckled && istype(user.buckled, /obj/vehicle))
+ user.buckled.unbuckle_mob(src, force=TRUE)
+
+/datum/action/cooldown/bloodsucker/fortitude/DeactivatePower()
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/bloodsucker_user = owner
+ if(IS_BLOODSUCKER(owner) || IS_VASSAL(owner))
+ bloodsucker_user.physiology.brute_mod /= fortitude_resist
+ if(!HAS_TRAIT_FROM(bloodsucker_user, TRAIT_STUNIMMUNE, BLOODSUCKER_TRAIT))
+ bloodsucker_user.physiology.stamina_mod /= fortitude_resist
+ // Remove Traits & Effects
+ REMOVE_TRAIT(owner, TRAIT_PIERCEIMMUNE, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(owner, TRAIT_NODISMEMBER, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(owner, TRAIT_PUSHIMMUNE, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(owner, TRAIT_STUNIMMUNE, BLOODSUCKER_TRAIT)
+
+ if(was_running && bloodsucker_user.m_intent == MOVE_INTENT_WALK)
+ owner.toggle_move_intent()
+ owner.balloon_alert(owner, "fortitude turned off.")
+
+ return ..()
diff --git a/code/modules/antagonists/bloodsucker/powers/gohome.dm b/code/modules/antagonists/bloodsucker/powers/gohome.dm
new file mode 100644
index 0000000000000..1fdc77550e0e3
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/gohome.dm
@@ -0,0 +1,133 @@
+#define GOHOME_START 0
+#define GOHOME_FLICKER_ONE 2
+#define GOHOME_FLICKER_TWO 4
+#define GOHOME_TELEPORT 6
+
+/**
+ * Given to Bloodsuckers near Sol if they have a Coffin claimed.
+ * Teleports them to their Coffin after a delay.
+ * Makes them drop everything if someone witnesses the act.
+ */
+/datum/action/cooldown/bloodsucker/gohome
+ name = "Vanishing Act"
+ desc = "As dawn aproaches, disperse into mist and return directly to your Lair.
WARNING: You will drop ALL of your possessions if observed by mortals."
+ button_icon_state = "power_gohome"
+ active_background_icon_state = "vamp_power_off_oneshot"
+ base_background_icon_state = "vamp_power_off_oneshot"
+ power_explanation = "Vanishing Act: \n\
+ Activating Vanishing Act will, after a short delay, teleport the user to their Claimed Coffin. \n\
+ The power will cancel out if the Claimed Coffin is somehow destroyed. \n\
+ Immediately after activating, lights around the user will begin to flicker. \n\
+ Once the user teleports to their coffin, in their place will be a Rat or Bat."
+ power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN
+ check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED
+ purchase_flags = NONE
+ bloodcost = 100
+ constant_bloodcost = 2
+ cooldown_time = 100 SECONDS
+ ///What stage of the teleportation are we in
+ var/teleporting_stage = GOHOME_START
+ ///The types of mobs that will drop post-teleportation.
+ var/static/list/spawning_mobs = list(
+ /mob/living/simple_animal/mouse = 3,
+ /mob/living/simple_animal/hostile/retaliate/bat = 1,
+ )
+
+/datum/action/cooldown/bloodsucker/gohome/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ /// Have No Lair (NOTE: You only got this power if you had a lair, so this means it's destroyed)
+ if(!istype(bloodsuckerdatum_power) || !bloodsuckerdatum_power.coffin)
+ owner.balloon_alert(owner, "coffin was destroyed!")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/gohome/ActivatePower(trigger_flags)
+ . = ..()
+ owner.balloon_alert(owner, "preparing to teleport...")
+
+/datum/action/cooldown/bloodsucker/gohome/process(seconds_per_tick)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ switch(teleporting_stage)
+ if(GOHOME_START)
+ INVOKE_ASYNC(src, PROC_REF(flicker_lights), 3, 20)
+ if(GOHOME_FLICKER_ONE)
+ INVOKE_ASYNC(src, PROC_REF(flicker_lights), 4, 40)
+ if(GOHOME_FLICKER_TWO)
+ INVOKE_ASYNC(src, PROC_REF(flicker_lights), 4, 60)
+ if(GOHOME_TELEPORT)
+ INVOKE_ASYNC(src, PROC_REF(teleport_to_coffin), owner)
+ teleporting_stage++
+
+/datum/action/cooldown/bloodsucker/gohome/ContinueActive(mob/living/user, mob/living/target)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isturf(owner.loc))
+ return FALSE
+ if(!bloodsuckerdatum_power.coffin)
+ user.balloon_alert(user, "coffin destroyed!")
+ to_chat(owner, "Your coffin has been destroyed! You no longer have a destination.")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/gohome/proc/flicker_lights(flicker_range, beat_volume)
+ for(var/obj/machinery/light/nearby_lights in view(flicker_range, get_turf(owner)))
+ nearby_lights.flicker(5)
+ playsound(get_turf(owner), 'sound/effects/singlebeat.ogg', beat_volume, 1)
+
+/datum/action/cooldown/bloodsucker/gohome/proc/teleport_to_coffin(mob/living/carbon/user)
+ var/drop_item = FALSE
+ var/turf/current_turf = get_turf(owner)
+ // If we aren't in the dark, anyone watching us will cause us to drop out stuff
+ if(current_turf && current_turf.lighting_object && current_turf.get_lumcount() >= 0.2)
+ for(var/mob/living/watchers in viewers(world.view, get_turf(owner)) - owner)
+ if(!watchers.client)
+ continue
+ if(watchers.has_unlimited_silicon_privilege)
+ continue
+ if(watchers.is_blind())
+ continue
+ if(!IS_BLOODSUCKER(watchers) && !IS_VASSAL(watchers))
+ drop_item = TRUE
+ break
+ // Drop all necessary items (handcuffs, legcuffs, items if seen)
+ if(user.handcuffed)
+ var/obj/item/handcuffs = user.handcuffed
+ user.dropItemToGround(handcuffs)
+ if(user.legcuffed)
+ var/obj/item/legcuffs = user.legcuffed
+ user.dropItemToGround(legcuffs)
+ if(drop_item)
+ for(var/obj/item/literally_everything in owner)
+ owner.dropItemToGround(literally_everything, TRUE)
+
+ playsound(current_turf, 'sound/magic/summon_karp.ogg', 60, 1)
+
+ var/datum/effect_system/steam_spread/bloodsucker/puff = new /datum/effect_system/steam_spread/bloodsucker()
+ puff.set_up(3, 0, current_turf)
+ puff.start()
+
+ /// STEP FIVE: Create animal at prev location
+ var/mob/living/simple_animal/new_mob = pick_weight(spawning_mobs)
+ new new_mob(current_turf)
+ /// TELEPORT: Move to Coffin & Close it!
+ user.set_resting(TRUE, TRUE, FALSE)
+ do_teleport(owner, bloodsuckerdatum_power.coffin, no_effects = TRUE, forced = TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
+ bloodsuckerdatum_power.coffin.close(owner)
+ bloodsuckerdatum_power.coffin.take_contents()
+ playsound(bloodsuckerdatum_power.coffin.loc, bloodsuckerdatum_power.coffin.close_sound, 15, 1, -3)
+
+ DeactivatePower()
+
+/datum/effect_system/steam_spread/bloodsucker
+ effect_type = /obj/effect/particle_effect/smoke/vampsmoke
+
+#undef GOHOME_START
+#undef GOHOME_FLICKER_ONE
+#undef GOHOME_FLICKER_TWO
+#undef GOHOME_TELEPORT
diff --git a/code/modules/antagonists/bloodsucker/powers/masquerade.dm b/code/modules/antagonists/bloodsucker/powers/masquerade.dm
new file mode 100644
index 0000000000000..35ce7f0b608f3
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/masquerade.dm
@@ -0,0 +1,97 @@
+/**
+ * # WITHOUT THIS POWER:
+ *
+ * - Mid-Blood: SHOW AS PALE
+ * - Low-Blood: SHOW AS DEAD
+ * - No Heartbeat
+ * - Examine shows actual blood
+ * - Thermal homeostasis (ColdBlooded)
+ * WITH THIS POWER:
+ * - Normal body temp -- remove Cold Blooded (return on deactivate)
+ */
+
+/datum/action/cooldown/bloodsucker/masquerade
+ name = "Masquerade"
+ desc = "Feign the vital signs of a mortal, and escape both casual and medical notice as the monster you truly are."
+ button_icon_state = "power_human"
+ power_explanation = "Masquerade:\n\
+ Activating Masquerade will forge your identity to be practically identical to that of a human;\n\
+ - You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus and cold immunity.\n\
+ - Your eyes turn to that of a regular human as your heart begins to beat.\n\
+ - You gain a Genetic sequence, and appear to have 100% blood when scanned by a Health Analyzer.\n\
+ - You will not appear as Pale when examined. Anything further than Pale, however, will not be hidden.\n\
+ At the end of a Masquerade, you will re-gain your Vampiric abilities, as well as lose any Disease & Gene you might have."
+ power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN|BP_AM_COSTLESS_UNCONSCIOUS
+ check_flags = BP_CANT_USE_IN_FRENZY
+ purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER
+ bloodcost = 10
+ cooldown_time = 5 SECONDS
+ constant_bloodcost = 0.1
+
+/datum/action/cooldown/bloodsucker/masquerade/ActivatePower(trigger_flags)
+ . = ..()
+ var/mob/living/carbon/user = owner
+ owner.balloon_alert(owner, "masquerade turned on.")
+ to_chat(user, "Your heart beats falsely within your lifeless chest. You may yet pass for a mortal.")
+ to_chat(user, "Your vampiric healing is halted while imitating life.")
+
+ // Give status effect
+ user.apply_status_effect(/datum/status_effect/masquerade)
+
+ // Handle Traits
+ for(var/all_traits in bloodsuckerdatum_power.bloodsucker_traits)
+ REMOVE_TRAIT(owner, all_traits, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(user, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT)
+ // Handle organs
+ var/obj/item/organ/heart/vampheart = user.getorgan(/obj/item/organ/heart)
+ vampheart.Restart()
+ var/obj/item/organ/eyes/eyes = user.getorgan(/obj/item/organ/eyes)
+ if(eyes)
+ eyes.flash_protect = initial(eyes.flash_protect)
+
+/datum/action/cooldown/bloodsucker/masquerade/DeactivatePower()
+ . = ..() // activate = FALSE
+ var/mob/living/carbon/user = owner
+ owner.balloon_alert(owner, "masquerade turned off.")
+
+ // Remove status effect, mutations & diseases that you got while on masq.
+ user.remove_status_effect(/datum/status_effect/masquerade)
+ user.dna.remove_all_mutations()
+ for(var/datum/disease/diseases as anything in user.diseases)
+ diseases.cure()
+
+ // Handle Traits
+ for(var/all_traits in bloodsuckerdatum_power.bloodsucker_traits)
+ ADD_TRAIT(owner, all_traits, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(user, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT)
+
+ // Handle organs
+ var/obj/item/organ/heart/vampheart = user.getorganslot(ORGAN_SLOT_HEART)
+ vampheart.Stop()
+ var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES)
+ if(eyes)
+ eyes.flash_protect = max(initial(eyes.flash_protect) - 1, - 1)
+ to_chat(user, "Your heart beats one final time, while your skin dries out and your icy pallor returns.")
+
+/**
+ * # Status effect
+ *
+ * This is what the Masquerade power gives, handles their bonuses and gives them a neat icon to tell them they're on Masquerade.
+ */
+
+/datum/status_effect/masquerade
+ id = "masquerade"
+ duration = -1
+ tick_interval = -1
+ alert_type = /atom/movable/screen/alert/status_effect/masquerade
+
+/atom/movable/screen/alert/status_effect/masquerade
+ name = "Masquerade"
+ desc = "You are currently hiding your identity using the Masquerade power. This halts Vampiric healing."
+ icon = 'icons/bloodsuckers/actions_bloodsucker.dmi'
+ icon_state = "power_human"
+ alerttooltipstyle = "cult"
+
+/atom/movable/screen/alert/status_effect/masquerade/MouseEntered(location,control,params)
+ desc = initial(desc)
+ return ..()
diff --git a/code/modules/antagonists/bloodsucker/powers/targeted/_targeted.dm b/code/modules/antagonists/bloodsucker/powers/targeted/_targeted.dm
new file mode 100644
index 0000000000000..cb18d7572b6a7
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/targeted/_targeted.dm
@@ -0,0 +1,105 @@
+// NOTE: All Targeted spells are Toggles! We just don't bother checking here.
+/datum/action/cooldown/bloodsucker/targeted
+ power_flags = BP_AM_TOGGLE
+
+ ///If set, how far the target has to be for the power to work.
+ var/target_range
+ ///Message sent to chat when clicking on the power, before you use it.
+ var/prefire_message
+ ///Most powers happen the moment you click. Some, like Mesmerize, require time and shouldn't cost you if they fail.
+ var/power_activates_immediately = TRUE
+ ///Is this power LOCKED due to being used?
+ var/power_in_use = FALSE
+
+/// Modify description to add notice that this is aimed.
+/datum/action/cooldown/bloodsucker/targeted/New(Target)
+ desc += "
\[Targeted Power\]"
+ return ..()
+
+/datum/action/cooldown/bloodsucker/targeted/Remove(mob/living/remove_from)
+ . = ..()
+ if(remove_from.click_intercept == src)
+ unset_click_ability(remove_from)
+
+/datum/action/cooldown/bloodsucker/targeted/Trigger(trigger_flags, atom/target)
+ if((active) && can_deactivate())
+ DeactivatePower()
+ return FALSE
+ if(!can_pay_cost(owner) || !can_use(owner, trigger_flags))
+ return FALSE
+
+ if(prefire_message)
+ to_chat(owner, "[prefire_message]")
+
+ ActivatePower(trigger_flags)
+ if(target)
+ return InterceptClickOn(owner, null, target)
+
+ return set_click_ability(owner)
+
+/datum/action/cooldown/bloodsucker/targeted/DeactivatePower()
+ if(power_flags & BP_AM_TOGGLE)
+ STOP_PROCESSING(SSprocessing, src)
+ active = FALSE
+ UpdateButtonIcon()
+ unset_click_ability(owner)
+// ..() // we don't want to pay cost here
+
+/// Check if target is VALID (wall, turf, or character?)
+/datum/action/cooldown/bloodsucker/targeted/proc/CheckValidTarget(atom/target_atom)
+ if(target_atom == owner)
+ return FALSE
+ return TRUE
+
+/// Check if valid target meets conditions
+/datum/action/cooldown/bloodsucker/targeted/proc/CheckCanTarget(atom/target_atom)
+ if(target_range)
+ // Out of Range
+ if(!(target_atom in view(target_range, owner)))
+ if(target_range > 1) // Only warn for range if it's greater than 1. Brawn doesn't need to announce itself.
+ owner.balloon_alert(owner, "out of range.")
+ return FALSE
+ return istype(target_atom)
+
+/datum/action/cooldown/bloodsucker/targeted/proc/unset_click_ability(mob/on_who, refund_cooldown = TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ on_who.click_intercept = null
+ UpdateButtonIcon()
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/proc/set_click_ability(mob/on_who)
+ SHOULD_CALL_PARENT(TRUE)
+
+ on_who.click_intercept = src
+ UpdateButtonIcon()
+ return TRUE
+
+/// Click Target
+/datum/action/cooldown/bloodsucker/targeted/proc/click_with_power(atom/target_atom)
+ // CANCEL RANGED TARGET check
+ if(power_in_use || !CheckValidTarget(target_atom))
+ return FALSE
+ // Valid? (return true means DON'T cancel power!)
+ if(!can_pay_cost() || !can_use(owner) || !CheckCanTarget(target_atom))
+ return TRUE
+ power_in_use = TRUE // Lock us into this ability until it successfully fires off. Otherwise, we pay the blood even if we fail.
+ FireTargetedPower(target_atom) // We use this instead of ActivatePower(trigger_flags), which has no input
+ // Skip this part so we can return TRUE right away.
+ if(power_activates_immediately)
+ power_activated_sucessfully() // Mesmerize pays only after success.
+ power_in_use = FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/proc/FireTargetedPower(atom/target_atom)
+ log_combat(owner, target_atom, "used [name] on")
+
+/// The power went off! We now pay the cost of the power.
+/datum/action/cooldown/bloodsucker/targeted/proc/power_activated_sucessfully()
+ unset_click_ability(owner)
+ pay_cost()
+ StartCooldown()
+ DeactivatePower()
+
+/datum/action/cooldown/bloodsucker/targeted/proc/InterceptClickOn(mob/living/caller, params, atom/target)
+ click_with_power(target)
diff --git a/code/modules/antagonists/bloodsucker/powers/targeted/brawn.dm b/code/modules/antagonists/bloodsucker/powers/targeted/brawn.dm
new file mode 100644
index 0000000000000..74ca769ca8def
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/targeted/brawn.dm
@@ -0,0 +1,188 @@
+/datum/action/cooldown/bloodsucker/targeted/brawn
+ name = "Brawn"
+ desc = "Snap restraints, break lockers and doors, or deal terrible damage with your bare hands."
+ button_icon_state = "power_strength"
+ power_explanation = "Brawn:\n\
+ Click any person to bash into them, break restraints you have or knocking a grabber down. Only one of these can be done per use.\n\
+ Punching a Cyborg will heavily EMP them in addition to deal damage.\n\
+ At level 3, you get the ability to break closets open, additionally can both break restraints AND knock a grabber down in the same use.\n\
+ At level 4, you get the ability to bash airlocks open, as long as they aren't bolted.\n\
+ Higher levels will increase the damage and knockdown when punching someone."
+ power_flags = BP_AM_TOGGLE
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
+ bloodcost = 8
+ cooldown_time = 9 SECONDS
+ target_range = 1
+ power_activates_immediately = TRUE
+ prefire_message = "Select a target."
+
+/datum/action/cooldown/bloodsucker/targeted/brawn/ActivatePower(trigger_flags)
+ // Did we break out of our handcuffs?
+ if(break_restraints())
+ power_activated_sucessfully()
+ return FALSE
+ // Did we knock a grabber down? We can only do this while not also breaking restraints if strong enough.
+ if(level_current >= 3 && escape_puller())
+ power_activated_sucessfully()
+ return FALSE
+ // Did neither, now we can PUNCH.
+ return ..()
+
+// Look at 'biodegrade.dm' for reference
+/datum/action/cooldown/bloodsucker/targeted/brawn/proc/break_restraints()
+ var/mob/living/carbon/human/user = owner
+ ///Only one form of shackles removed per use
+ var/used = FALSE
+
+ // Breaks out of lockers
+ if(istype(user.loc, /obj/structure/closet))
+ var/obj/structure/closet/closet = user.loc
+ if(!istype(closet))
+ return FALSE
+ closet.visible_message(
+ "[closet] tears apart as [user] bashes it open from within!",
+ "[closet] tears apart as you bash it open from within!",
+ )
+ to_chat(user, "We bash [closet] wide open!")
+ addtimer(CALLBACK(src, PROC_REF(break_closet), user, closet), 1)
+ used = TRUE
+
+ // Remove both Handcuffs & Legcuffs
+ var/obj/cuffs = user.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
+ var/obj/legcuffs = user.get_item_by_slot(ITEM_SLOT_LEGCUFFED)
+ if(!used && (istype(cuffs) || istype(legcuffs)))
+ user.visible_message(
+ "[user] discards their restraints like it's nothing!",
+ "We break through our restraints!",
+ )
+ user.clear_cuffs(cuffs, TRUE)
+ user.clear_cuffs(legcuffs, TRUE)
+ used = TRUE
+
+ // Remove Straightjackets
+ if(user.wear_suit?.breakouttime && !used)
+ var/obj/item/clothing/suit/straightjacket = user.get_item_by_slot(ITEM_SLOT_OCLOTHING)
+ user.visible_message(
+ "[user] rips straight through the [user.p_their()] [straightjacket]!",
+ "We tear through our [straightjacket]!",
+ )
+ if(straightjacket && user.wear_suit == straightjacket)
+ qdel(straightjacket)
+ used = TRUE
+
+ // Did we end up using our ability? If so, play the sound effect and return TRUE
+ if(used)
+ playsound(get_turf(user), 'sound/effects/grillehit.ogg', 80, 1, -1)
+ return used
+
+// This is its own proc because its done twice, to repeat code copypaste.
+/datum/action/cooldown/bloodsucker/targeted/brawn/proc/break_closet(mob/living/carbon/human/user, obj/structure/closet/closet)
+ if(closet)
+ closet.welded = FALSE
+ closet.locked = FALSE
+ closet.broken = TRUE
+ closet.open()
+
+/datum/action/cooldown/bloodsucker/targeted/brawn/proc/escape_puller()
+ if(!owner.pulledby) // || owner.pulledby.grab_state <= GRAB_PASSIVE)
+ return FALSE
+ var/mob/pulled_mob = owner.pulledby
+ var/pull_power = pulled_mob.grab_state
+ playsound(get_turf(pulled_mob), 'sound/effects/woodhit.ogg', 75, 1, -1)
+ // Knock Down (if Living)
+ if(isliving(pulled_mob))
+ var/mob/living/hit_target = pulled_mob
+ hit_target.Knockdown(pull_power * 10 + 20)
+ // Knock Back (before Knockdown, which probably cancels pull)
+ var/send_dir = get_dir(owner, pulled_mob)
+ var/turf/turf_thrown_at = get_ranged_target_turf(pulled_mob, send_dir, pull_power)
+ owner.newtonian_move(send_dir) // Bounce back in 0 G
+ pulled_mob.throw_at(turf_thrown_at, pull_power, TRUE, owner, FALSE) // Throw distance based on grab state! Harder grabs punished more aggressively.
+ log_combat(owner, pulled_mob, "used Brawn power")
+ owner.visible_message(
+ "[owner] tears free of [pulled_mob]'s grasp!",
+ "You shrug off [pulled_mob]'s grasp!",
+ )
+ owner.pulledby = null // It's already done, but JUST IN CASE.
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/brawn/FireTargetedPower(atom/target_atom)
+ . = ..()
+ var/mob/living/user = owner
+ // Target Type: Mob
+ if(isliving(target_atom))
+ var/mob/living/target = target_atom
+ var/mob/living/carbon/carbonuser = user
+ var/hitStrength = carbonuser.dna.species.punchdamage * 1.25 + 2
+ // Knockdown!
+ var/powerlevel = min(5, 1 + level_current)
+ if(rand(5 + powerlevel) >= 5)
+ target.visible_message(
+ "[user] lands a vicious punch, sending [target] away!", \
+ "[user] has landed a horrifying punch on you, sending you flying!",
+ )
+ target.Knockdown(min(5, rand(10, 10 * powerlevel)))
+ // Attack!
+ owner.balloon_alert(owner, "you punch [target]!")
+ playsound(get_turf(target), 'sound/weapons/punch4.ogg', 60, 1, -1)
+ user.do_attack_animation(target, ATTACK_EFFECT_SMASH)
+ var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(target.zone_selected))
+ target.apply_damage(hitStrength, BRUTE, affecting)
+ // Knockback
+ var/send_dir = get_dir(owner, target)
+ var/turf/turf_thrown_at = get_ranged_target_turf(target, send_dir, powerlevel)
+ owner.newtonian_move(send_dir) // Bounce back in 0 G
+ target.throw_at(turf_thrown_at, powerlevel, TRUE, owner) //new /datum/forced_movement(target, get_ranged_target_turf(target, send_dir, (hitStrength / 4)), 1, FALSE)
+ // Target Type: Cyborg (Also gets the effects above)
+ if(issilicon(target))
+ target.emp_act(EMP_HEAVY)
+ // Target Type: Locker
+ else if(istype(target_atom, /obj/structure/closet) && level_current >= 3)
+ var/obj/structure/closet/target_closet = target_atom
+ user.balloon_alert(user, "you prepare to bash [target_closet] open...")
+ if(!do_after(user, 2.5 SECONDS, target_closet))
+ user.balloon_alert(user, "interrupted!")
+ return FALSE
+ target_closet.visible_message("[target_closet] breaks open as [user] bashes it!")
+ addtimer(CALLBACK(src, PROC_REF(break_closet), user, target_closet), 1)
+ playsound(get_turf(user), 'sound/effects/grillehit.ogg', 80, TRUE, -1)
+ // Target Type: Door
+ else if(istype(target_atom, /obj/machinery/door) && level_current >= 4)
+ var/obj/machinery/door/target_airlock = target_atom
+ playsound(get_turf(user), 'sound/machines/airlock_alien_prying.ogg', 40, TRUE, -1)
+ owner.balloon_alert(owner, "you prepare to tear open [target_airlock]...")
+ if(!do_after(user, 2.5 SECONDS, target_airlock))
+ user.balloon_alert(user, "interrupted!")
+ return FALSE
+ if(target_airlock.Adjacent(user))
+ target_airlock.visible_message("[target_airlock] breaks open as [user] bashes it!")
+ user.Stun(10)
+ user.do_attack_animation(target_airlock, ATTACK_EFFECT_SMASH)
+ playsound(get_turf(target_airlock), 'sound/effects/bang.ogg', 30, 1, -1)
+ target_airlock.open(2) // open(2) is like a crowbar or jaws of life.
+
+/datum/action/cooldown/bloodsucker/targeted/brawn/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isliving(target_atom) || istype(target_atom, /obj/machinery/door) || istype(target_atom, /obj/structure/closet)
+
+/datum/action/cooldown/bloodsucker/targeted/brawn/CheckCanTarget(atom/target_atom)
+ // DEFAULT CHECKS (Distance)
+ . = ..()
+ if(!.) // Disable range notice for Brawn.
+ return FALSE
+ // Must outside Closet to target anyone!
+ if(!isturf(owner.loc))
+ return FALSE
+ // Target Type: Living
+ if(isliving(target_atom))
+ return TRUE
+ // Target Type: Door
+ else if(istype(target_atom, /obj/machinery/door))
+ return TRUE
+ // Target Type: Locker
+ else if(istype(target_atom, /obj/structure/closet))
+ return TRUE
+ return FALSE
diff --git a/code/modules/antagonists/bloodsucker/powers/targeted/haste.dm b/code/modules/antagonists/bloodsucker/powers/targeted/haste.dm
new file mode 100644
index 0000000000000..db4f971b44505
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/targeted/haste.dm
@@ -0,0 +1,89 @@
+/* Level 1: Speed to location
+ * Level 2: Dodge Bullets
+ * Level 3: Stun People Passed
+ */
+
+/datum/action/cooldown/bloodsucker/targeted/haste
+ name = "Immortal Haste"
+ desc = "Dash somewhere with supernatural speed. Those nearby may be knocked away, stunned, or left empty-handed."
+ button_icon_state = "power_speed"
+ power_explanation = "Immortal Haste:\n\
+ Click anywhere to immediately dash towards that location.\n\
+ The Power will not work if you are lying down, in no gravity, or are aggressively grabbed.\n\
+ Anyone in your way during your Haste will be knocked down.\n\
+ Higher levels will increase the knockdown dealt to enemies."
+ power_flags = BP_AM_TOGGLE
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
+ bloodcost = 6
+ cooldown_time = 12 SECONDS
+ target_range = 15
+ power_activates_immediately = TRUE
+ ///List of all people hit by our power, so we don't hit them again.
+ var/list/hit = list()
+
+/datum/action/cooldown/bloodsucker/targeted/haste/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ // Being Grabbed
+ if(user.pulledby && user.pulledby.grab_state >= GRAB_AGGRESSIVE)
+ user.balloon_alert(user, "you're being grabbed!")
+ return FALSE
+ if(!user.has_gravity(user.loc)) //We dont want people to be able to use this to fly around in space
+ user.balloon_alert(user, "you cannot dash while floating!")
+ return FALSE
+ if(user.body_position == LYING_DOWN)
+ user.balloon_alert(user, "you must be standing to tackle!")
+ return FALSE
+ return TRUE
+
+/// Anything will do, if it's not me or my square
+/datum/action/cooldown/bloodsucker/targeted/haste/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return target_atom.loc != owner.loc
+
+/// This is a non-async proc to make sure the power is "locked" until this finishes.
+/datum/action/cooldown/bloodsucker/targeted/haste/FireTargetedPower(atom/target_atom)
+ . = ..()
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
+ var/mob/living/user = owner
+ var/turf/targeted_turf = isturf(target_atom) ? target_atom : get_turf(target_atom)
+ // Pulled? Not anymore.
+ user.pulledby?.stop_pulling()
+ // Go to target turf
+ // DO NOT USE WALK TO.
+ owner.balloon_alert(owner, "you dash into the air!")
+ playsound(get_turf(owner), 'sound/weapons/punchmiss.ogg', 25, 1, -1)
+ var/safety = get_dist(user, targeted_turf) * 3 + 1
+ var/consequetive_failures = 0
+ while(--safety && (get_turf(user) != targeted_turf))
+ var/success = step_towards(user, targeted_turf) //This does not try to go around obstacles.
+ if(!success)
+ success = step_to(user, targeted_turf) //this does
+ if(!success)
+ consequetive_failures++
+ if(consequetive_failures >= 3) //if 3 steps don't work
+ break //just stop
+ else
+ consequetive_failures = 0 //reset so we can keep moving
+ if(user.resting || user.incapacitated(IGNORE_RESTRAINTS, IGNORE_GRAB)) //actually down? stop.
+ break
+ if(success) //don't sleep if we failed to move.
+ sleep(world.tick_lag)
+
+/datum/action/cooldown/bloodsucker/targeted/haste/power_activated_sucessfully()
+ . = ..()
+ UnregisterSignal(owner, COMSIG_MOVABLE_MOVED)
+ hit.Cut()
+
+/datum/action/cooldown/bloodsucker/targeted/haste/proc/on_move()
+ for(var/mob/living/hit_living in dview(1, get_turf(owner)) - owner)
+ if(hit.Find(hit_living))
+ continue
+ hit += hit_living
+ playsound(hit_living, "sound/weapons/punch[rand(1,4)].ogg", 15, 1, -1)
+ hit_living.Knockdown(10 + level_current * 4)
+ hit_living.spin(10, 1)
diff --git a/code/modules/antagonists/bloodsucker/powers/targeted/lunge.dm b/code/modules/antagonists/bloodsucker/powers/targeted/lunge.dm
new file mode 100644
index 0000000000000..82847932cf822
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/targeted/lunge.dm
@@ -0,0 +1,169 @@
+/datum/action/cooldown/bloodsucker/targeted/lunge
+ name = "Predatory Lunge"
+ desc = "Spring at your target to grapple them without warning, or tear the dead's heart out. Attacks from concealment or the rear may even knock them down if strong enough."
+ button_icon_state = "power_lunge"
+ power_explanation = "Predatory Lunge:\n\
+ Click any player to start spinning wildly and, after a short delay, dash at them.\n\
+ When lunging at someone, you will grab them, immediately starting off at aggressive.\n\
+ Riot gear and Monster Hunters are protected and will only be passively grabbed.\n\
+ You cannot use the Power if you are already grabbing someone, or are being grabbed.\n\
+ If you grab from behind, or from darkness (Cloak of Darkness works), you will knock the target down.\n\
+ If used on a dead body, will tear their heart out.\n\
+ Higher levels increase the knockdown dealt to enemies.\n\
+ At level 4, you will no longer spin, but you will be limited to tackling from only 6 tiles away."
+ power_flags = NONE
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
+ bloodcost = 10
+ cooldown_time = 10 SECONDS
+ power_activates_immediately = FALSE
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/upgrade_power()
+ . = ..()
+ //range is lowered when you get stronger.
+ if(level_current > 3)
+ target_range = 6
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ // Are we being grabbed?
+ if(user.pulledby && user.pulledby.grab_state >= GRAB_AGGRESSIVE)
+ owner.balloon_alert(user, "grabbed!")
+ return FALSE
+ if(user.pulling)
+ owner.balloon_alert(user, "grabbing someone!")
+ return FALSE
+ return TRUE
+
+/// Check: Are we lunging at a person?
+/datum/action/cooldown/bloodsucker/targeted/lunge/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isliving(target_atom)
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/CheckCanTarget(atom/target_atom)
+ // Default Checks
+ . = ..()
+ if(!.)
+ return FALSE
+ // Check: Turf
+ var/mob/living/turf_target = target_atom
+ if(!isturf(turf_target.loc))
+ return FALSE
+ // Check: can the Bloodsucker even move?
+ var/mob/living/user = owner
+ if(user.body_position == LYING_DOWN || HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/can_deactivate()
+ return !(datum_flags & DF_ISPROCESSING) //only if you aren't lunging
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/FireTargetedPower(atom/target_atom)
+ . = ..()
+ owner.face_atom(target_atom)
+ if(level_current > 3)
+ do_lunge(target_atom)
+ return
+
+ prepare_target_lunge(target_atom)
+ return TRUE
+
+///Starts processing the power and prepares the lunge by spinning, calls lunge at the end of it.
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/prepare_target_lunge(atom/target_atom)
+ START_PROCESSING(SSprocessing, src)
+ owner.balloon_alert(owner, "lunge started!")
+ //animate them shake
+ var/base_x = owner.base_pixel_x
+ var/base_y = owner.base_pixel_y
+ animate(owner, pixel_x = base_x, pixel_y = base_y, time = 1, loop = -1)
+ for(var/i in 1 to 25)
+ var/x_offset = base_x + rand(-3, 3)
+ var/y_offset = base_y + rand(-3, 3)
+ animate(pixel_x = x_offset, pixel_y = y_offset, time = 1)
+
+ if(!do_after(owner, 4 SECONDS, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE), extra_checks = CALLBACK(src, PROC_REF(CheckCanTarget), target_atom)))
+ end_target_lunge(base_x, base_y)
+
+ return FALSE
+
+ end_target_lunge()
+ do_lunge(target_atom)
+ return TRUE
+
+///When preparing to lunge ends, this clears it up.
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/end_target_lunge(base_x, base_y)
+ animate(owner, pixel_x = base_x, pixel_y = base_y, time = 1)
+ STOP_PROCESSING(SSprocessing, src)
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/process()
+ if(!active) //If running SSfasprocess (on cooldown)
+ return ..() //Manage our cooldown timers
+ if(prob(75))
+ owner.spin(8, 1)
+ owner.balloon_alert_to_viewers("spins wildly!", "you spin!")
+ return
+ do_smoke(0, owner.loc, smoke_type = /obj/effect/particle_effect/smoke/transparent)
+
+///Actually lunges the target, then calls lunge end.
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/do_lunge(atom/hit_atom)
+ var/turf/targeted_turf = get_turf(hit_atom)
+
+ var/safety = get_dist(owner, targeted_turf) * 3 + 1
+ var/consequetive_failures = 0
+ while(--safety && !hit_atom.Adjacent(owner))
+ if(!step_to(owner, targeted_turf))
+ consequetive_failures++
+ if(consequetive_failures >= 3) // If 3 steps don't work, just stop.
+ break
+
+ lunge_end(hit_atom, targeted_turf)
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/lunge_end(atom/hit_atom, turf/target_turf)
+ power_activated_sucessfully()
+ // Am I next to my target to start giving the effects?
+ if(!owner.Adjacent(hit_atom))
+ return
+
+ var/mob/living/user = owner
+ var/mob/living/carbon/target = hit_atom
+
+ // Did I slip or get knocked unconscious?
+ if(user.body_position != STANDING_UP || user.incapacitated())
+ var/send_dir = get_dir(user, target_turf)
+ new /datum/forced_movement(user, get_ranged_target_turf(user, send_dir, 1), 1, FALSE)
+ user.spin(10)
+ return
+
+ if(IS_CURATOR(target) || target.is_shove_knockdown_blocked())
+ owner.balloon_alert(owner, "pushed away!")
+ target.grabbedby(owner)
+ return
+
+ owner.balloon_alert(owner, "you lunge at [target]!")
+ if(target.stat == DEAD)
+ target.apply_damage(50, BRUTE, BODY_ZONE_CHEST)
+ owner.visible_message(
+ "[owner] tears into [target]'s chest!",
+ "You tear into [target]'s chest!",
+ )
+
+ var/obj/item/organ/heart/myheart_now = user.getorgan(/obj/item/organ/heart)
+ if(myheart_now)
+ myheart_now.Remove(target)
+ user.put_in_hands(myheart_now)
+
+ else
+ target.grabbedby(owner)
+ target.grippedby(owner, instant = TRUE)
+ // Did we knock them down?
+ if(!is_source_facing_target(target, owner) || owner.alpha <= 40)
+ target.Knockdown(10 + level_current * 5)
+ target.Paralyze(0.1)
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/DeactivatePower()
+ REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, BLOODSUCKER_TRAIT)
+ return ..()
diff --git a/code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm b/code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm
new file mode 100644
index 0000000000000..135db98da2f53
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm
@@ -0,0 +1,140 @@
+/**
+ * MEZMERIZE
+ * Locks a target in place for a certain amount of time.
+ *
+ * Level 2: Additionally mutes
+ * Level 3: Can be used through face protection
+ * Level 5: Doesn't need to be facing you anymore
+ */
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize
+ name = "Mesmerize"
+ desc = "Dominate the mind of a mortal who can see your eyes."
+ button_icon_state = "power_mez"
+ power_explanation = "Mesmerize:\n\
+ Click any player to attempt to mesmerize them.\n\
+ You cannot wear anything covering your face, and both parties must be facing eachother. Obviously, both parties need to not be blind. \n\
+ If your target is already mesmerized or a Monster Hunter, the Power will fail.\n\
+ Once mesmerized, the target will be unable to move for a certain amount of time, scaling with level.\n\
+ At level 2, your target will additionally be muted.\n\
+ At level 3, you will be able to use the power through items covering your face.\n\
+ At level 5, you will be able to mesmerize regardless of your target's direction.\n\
+ Higher levels will increase the time of the mesmerize's freeze."
+ power_flags = NONE
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
+ bloodcost = 30
+ cooldown_time = 20 SECONDS
+ target_range = 8
+ power_activates_immediately = FALSE
+ prefire_message = "Whom will you subvert to your will?"
+ ///Our mesmerized target - Prevents several mesmerizes.
+ var/datum/weakref/target_ref
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.) // Default checks
+ return FALSE
+ if(!user.getorganslot(ORGAN_SLOT_EYES))
+ // Cant use balloon alert, they've got no eyes!
+ to_chat(user, "You have no eyes with which to mesmerize.")
+ return FALSE
+ // Check: Eyes covered?
+ if(istype(user) && (user.is_eyes_covered() && level_current <= 2) || !isturf(user.loc))
+ user.balloon_alert(user, "your eyes are concealed from sight.")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isliving(target_atom)
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/CheckCanTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ var/mob/living/current_target = target_atom // We already know it's carbon due to CheckValidTarget()
+ // No mind
+ if(!current_target.mind)
+ owner.balloon_alert(owner, "[current_target] is mindless.")
+ return FALSE
+ // Bloodsucker
+ if(IS_BLOODSUCKER(current_target))
+ owner.balloon_alert(owner, "bloodsuckers are immune to [src].")
+ return FALSE
+ // Dead/Unconscious
+ if(current_target.stat > CONSCIOUS)
+ owner.balloon_alert(owner, "[current_target] is not [(current_target.stat == DEAD || HAS_TRAIT(current_target, TRAIT_FAKEDEATH)) ? "alive" : "conscious"].")
+ return FALSE
+ // Target has eyes?
+ if(!current_target.getorganslot(ORGAN_SLOT_EYES) && !issilicon(current_target))
+ owner.balloon_alert(owner, "[current_target] has no eyes.")
+ return FALSE
+ // Target blind?
+ if(current_target.is_blind() && !issilicon(current_target))
+ owner.balloon_alert(owner, "[current_target] is blind.")
+ return FALSE
+ // Facing target?
+ if(!is_source_facing_target(owner, current_target)) // in unsorted.dm
+ owner.balloon_alert(owner, "you must be facing [current_target].")
+ return FALSE
+ // Target facing me? (On the floor, they're facing everyone)
+ if(((current_target.mobility_flags & MOBILITY_STAND) && !is_source_facing_target(current_target, owner) && level_current <= 4))
+ owner.balloon_alert(owner, "[current_target] must be facing you.")
+ return FALSE
+
+ // Gone through our checks, let's mark our guy.
+ target_ref = WEAKREF(current_target)
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/target_atom)
+ . = ..()
+
+ var/mob/living/user = owner
+ var/mob/living/carbon/mesmerized_target = target_ref.resolve()
+
+ if(issilicon(mesmerized_target))
+ var/mob/living/silicon/mesmerized = mesmerized_target
+ mesmerized.emp_act(EMP_HEAVY)
+ owner.balloon_alert(owner, "temporarily shut [mesmerized] down.")
+ power_activated_sucessfully() // PAY COST! BEGIN COOLDOWN!
+ return
+
+ if(istype(mesmerized_target))
+ owner.balloon_alert(owner, "attempting to hypnotically gaze [mesmerized_target]...")
+
+ if(!do_after(user, 4 SECONDS, mesmerized_target, NONE, TRUE, extra_checks = CALLBACK(src, PROC_REF(ContinueActive), user, mesmerized_target)))
+ return
+
+ var/power_time = 9 SECONDS + level_current * 1.5 SECONDS
+ if(IS_CURATOR(mesmerized_target))
+ to_chat(mesmerized_target, "You feel your eyes burn for a while, but it passes.")
+ return
+ if(HAS_TRAIT_FROM(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT))
+ owner.balloon_alert(owner, "[mesmerized_target] is already in a hypnotic gaze.")
+ return
+ if(iscarbon(mesmerized_target))
+ owner.balloon_alert(owner, "successfully mesmerized [mesmerized_target].")
+ if(level_current >= 2)
+ ADD_TRAIT(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+ mesmerized_target.Immobilize(power_time)
+ mesmerized_target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // mesmerized_target.changeNext_move(power_time) // check click.dm
+ mesmerized_target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
+ addtimer(CALLBACK(src, PROC_REF(end_mesmerize), user, mesmerized_target), power_time)
+ power_activated_sucessfully() // PAY COST! BEGIN COOLDOWN!
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/DeactivatePower()
+ target_ref = null
+ . = ..()
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/end_mesmerize(mob/living/user, mob/living/target)
+ target.notransform = FALSE
+ REMOVE_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+ // They Woke Up! (Notice if within view)
+ if(istype(user) && target.stat == CONSCIOUS && (target in view(6, get_turf(user))))
+ owner.balloon_alert(owner, "[target] snapped out of their trance.")
+
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/ContinueActive(mob/living/user, mob/living/target)
+ return ..() && can_use(user) && CheckCanTarget(target)
diff --git a/code/modules/antagonists/bloodsucker/powers/targeted/trespass.dm b/code/modules/antagonists/bloodsucker/powers/targeted/trespass.dm
new file mode 100644
index 0000000000000..8d25d0614f9b9
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/targeted/trespass.dm
@@ -0,0 +1,106 @@
+/datum/action/cooldown/bloodsucker/targeted/trespass
+ name = "Trespass"
+ desc = "Become mist and advance two tiles in one direction. Useful for skipping past doors and barricades."
+ button_icon_state = "power_tres"
+ power_explanation = "Trespass:\n\
+ Click anywhere from 1-2 tiles away from you to teleport.\n\
+ This power goes through all obstacles except Walls.\n\
+ Higher levels decrease the sound played from using the Power, and increase the speed of the transition."
+ power_flags = BP_AM_TOGGLE
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
+ bloodcost = 10
+ cooldown_time = 8 SECONDS
+ prefire_message = "Select a destination."
+ //target_range = 2
+ var/turf/target_turf // We need to decide where we're going based on where we clicked. It's not actually the tile we clicked.
+
+/datum/action/cooldown/bloodsucker/targeted/trespass/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(user.notransform || !get_turf(user))
+ return FALSE
+ return TRUE
+
+
+/datum/action/cooldown/bloodsucker/targeted/trespass/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ // Can't target my tile
+ if(target_atom == get_turf(owner) || get_turf(target_atom) == get_turf(owner))
+ return FALSE
+ return TRUE // All we care about is destination. Anything you click is fine.
+
+
+/datum/action/cooldown/bloodsucker/targeted/trespass/CheckCanTarget(atom/target_atom)
+ // NOTE: Do NOT use ..()! We don't want to check distance or anything.
+
+ // Get clicked tile
+ var/final_turf = isturf(target_atom) ? target_atom : get_turf(target_atom)
+
+ // Are either tiles WALLS?
+ var/turf/from_turf = get_turf(owner)
+ var/this_dir // = get_dir(from_turf, target_turf)
+ for(var/i = 1 to 2)
+ // Keep Prev Direction if we've reached final turf
+ if(from_turf != final_turf)
+ this_dir = get_dir(from_turf, final_turf) // Recalculate dir so we don't overshoot on a diagonal.
+ from_turf = get_step(from_turf, this_dir)
+ // ERROR! Wall!
+ if(iswallturf(from_turf))
+ var/wallwarning = (i == 1) ? "in the way" : "at your destination"
+ owner.balloon_alert(owner, "There is a wall [wallwarning].")
+ return FALSE
+ // Done
+ target_turf = from_turf
+
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/trespass/FireTargetedPower(atom/target_atom)
+ . = ..()
+
+ // Find target turf, at or below Atom
+ var/mob/living/carbon/user = owner
+ var/turf/my_turf = get_turf(owner)
+
+ user.visible_message(
+ "[user]'s form dissipates into a cloud of mist!",
+ "You disspiate into formless mist.",
+ )
+ // Effect Origin
+ var/sound_strength = max(60, 70 - level_current * 10)
+ playsound(get_turf(owner), 'sound/magic/summon_karp.ogg', sound_strength, 1)
+ var/datum/effect_system/steam_spread/bloodsucker/puff = new /datum/effect_system/steam_spread()
+ puff.set_up(3, 0, my_turf)
+ puff.start()
+
+ var/mist_delay = max(5, 20 - level_current * 2.5) // Level up and do this faster.
+
+ // Freeze Me
+ user.Stun(mist_delay, ignore_canstun = TRUE)
+ user.density = FALSE
+ var/invis_was = user.invisibility
+ user.invisibility = INVISIBILITY_MAXIMUM
+
+ // Wait...
+ sleep(mist_delay / 2)
+ // Move & Freeze
+ if(isturf(target_turf))
+ do_teleport(owner, target_turf, no_effects=TRUE, channel = TELEPORT_CHANNEL_QUANTUM) // in teleport.dm?
+ user.Stun(mist_delay / 2, ignore_canstun = TRUE)
+
+ // Wait...
+ sleep(mist_delay / 2)
+ // Un-Hide & Freeze
+ user.dir = get_dir(my_turf, target_turf)
+ user.Stun(mist_delay / 2, ignore_canstun = TRUE)
+ user.density = 1
+ user.invisibility = invis_was
+ // Effect Destination
+ playsound(get_turf(owner), 'sound/magic/summon_karp.ogg', 60, 1)
+ puff = new /datum/effect_system/steam_spread/()
+ puff.effect_type = /obj/effect/particle_effect/smoke/vampsmoke
+ puff.set_up(3, 0, target_turf)
+ puff.start()
diff --git a/code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm b/code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm
new file mode 100644
index 0000000000000..87b1365c0fc72
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm
@@ -0,0 +1,118 @@
+/**
+ * # Auspex
+ *
+ * Level 1 - Cloak of Darkness until clicking an area, teleports the user to the selected area (max 2 tile)
+ * Level 2 - Cloak of Darkness until clicking an area, teleports the user to the selected area (max 3 tiles)
+ * Level 3 - Cloak of Darkness until clicking an area, teleports the user to the selected area
+ * Level 4 - Cloak of Darkness until clicking an area, teleports the user to the selected area, causes nearby people to bleed.
+ * Level 5 - Cloak of Darkness until clicking an area, teleports the user to the selected area, causes nearby people to fall asleep.
+ */
+
+// Look to /datum/action/cooldown/spell/pointed/void_phase for help.
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex
+ name = "Level 1: Auspex"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/two
+ level_current = 1
+ desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport up to 2 tiles away."
+ button_icon_state = "power_auspex"
+ power_explanation = "Level 1: Auspex:\n\
+ When Activated, you will be hidden in a Cloak of Darkness.\n\
+ Click any area up to 2 tile away to teleport there, ending the Power."
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ bloodcost = 5
+ constant_bloodcost = 2
+ cooldown_time = 12 SECONDS
+ target_range = 2
+ prefire_message = "Where do you wish to teleport to?"
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/two
+ name = "Level 2: Auspex"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/three
+ level_current = 2
+ desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport up to 3 tiles away."
+ power_explanation = "Level 2: Auspex:\n\
+ When Activated, you will be hidden in a Cloak of Darkness.\n\
+ Click any area up to 3 tile away to teleport there, ending the Power."
+ bloodcost = 10
+ cooldown_time = 10 SECONDS
+ target_range = 3
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/three
+ name = "Level 3: Auspex"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced
+ level_current = 3
+ desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport."
+ power_explanation = "Level 3: Auspex:\n\
+ When Activated, you will be hidden in a Cloak of Darkness.\n\
+ Click any area up to teleport there, ending the Power."
+ bloodcost = 15
+ cooldown_time = 8 SECONDS
+ target_range = null
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced
+ name = "Level 4: Auspex"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced/two
+ level_current = 4
+ desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport, leaving nearby people bleeding."
+ power_explanation = "Level 4: Auspex:\n\
+ When Activated, you will be hidden in a Cloak of Darkness.\n\
+ Click any area up to teleport there, ending the Power and causing people at your end location to start bleeding."
+ background_icon_state = "tremere_power_gold_off"
+ active_background_icon_state = "tremere_power_gold_on"
+ base_background_icon_state = "tremere_power_gold_off"
+ bloodcost = 20
+ cooldown_time = 6 SECONDS
+ target_range = null
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced/two
+ name = "Level 5: Auspex"
+ upgraded_power = null
+ level_current = 5
+ desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport, leaving nearby people bleeding and asleep."
+ power_explanation = "Level 5: Auspex:\n\
+ When Activated, you will be hidden in a Cloak of Darkness.\n\
+ Click any area up to teleport there, ending the Power and causing people at your end location to fall over in pain."
+ bloodcost = 25
+ cooldown_time = 8 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isturf(target_atom)
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/ActivatePower(trigger_flags)
+ . = ..()
+ owner.AddElement(/datum/element/digital_camo)
+ animate(owner, alpha = 15, time = 1 SECONDS)
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/DeactivatePower()
+ animate(owner, alpha = 255, time = 1 SECONDS)
+ owner.RemoveElement(/datum/element/digital_camo)
+ return ..()
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/FireTargetedPower(atom/target_atom)
+ . = ..()
+ var/mob/living/user = owner
+ var/turf/targeted_turf = get_turf(target_atom)
+ auspex_blink(user, targeted_turf)
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/proc/auspex_blink(mob/living/user, turf/targeted_turf)
+ playsound(user, 'sound/magic/summon_karp.ogg', 60)
+ playsound(targeted_turf, 'sound/magic/summon_karp.ogg', 60)
+
+ new /obj/effect/particle_effect/smoke/vampsmoke(user.drop_location())
+ new /obj/effect/particle_effect/smoke/vampsmoke(targeted_turf)
+
+ for(var/mob/living/carbon/living_mob in range(1, targeted_turf)-user)
+ if(IS_BLOODSUCKER(living_mob) || IS_VASSAL(living_mob))
+ continue
+ if(level_current >= 4)
+ living_mob.add_bleeding(BLEED_CRITICAL)
+ living_mob.adjustBruteLoss(15)
+ if(level_current >= 5)
+ living_mob.Knockdown(10 SECONDS, ignore_canstun = TRUE)
+
+ do_teleport(owner, targeted_turf, no_effects = TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
+ power_activated_sucessfully()
diff --git a/code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm b/code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm
new file mode 100644
index 0000000000000..eb466d7598740
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm
@@ -0,0 +1,193 @@
+/**
+ * # Dominate;
+ *
+ * Level 1 - Mesmerizes target
+ * Level 2 - Mesmerizes and mutes target
+ * Level 3 - Mesmerizes, blinds and mutes target
+ * Level 4 - Target (if at least in crit & has a mind) will revive as a Mute/Deaf Vassal for 5 minutes before dying.
+ * Level 5 - Target (if at least in crit & has a mind) will revive as a Vassal for 8 minutes before dying.
+ */
+
+// Copied from mesmerize.dm
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate
+ name = "Level 1: Dominate"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/two
+ level_current = 1
+ desc = "Mesmerize any foe who stands still long enough."
+ button_icon_state = "power_dominate"
+ power_explanation = "Level 1: Dominate:\n\
+ Click any person to, after a 4 second timer, Mesmerize them.\n\
+ This will completely immobilize them for the next 10.5 seconds."
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
+ bloodcost = 15
+ constant_bloodcost = 2
+ cooldown_time = 50 SECONDS
+ target_range = 6
+ prefire_message = "Select a target."
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/two
+ name = "Level 2: Dominate"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/three
+ level_current = 2
+ desc = "Mesmerize and mute any foe who stands still long enough."
+ power_explanation = "Level 2: Dominate:\n\
+ Click any person to, after a 4 second timer, Mesmerize them.\n\
+ This will completely immobilize and mute them for the next 12 seconds."
+ bloodcost = 20
+ cooldown_time = 40 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/three
+ name = "Level 3: Dominate"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced
+ level_current = 3
+ desc = "Mesmerize, mute and blind any foe who stands still long enough."
+ power_explanation = "Level 3: Dominate:\n\
+ Click any person to, after a 4 second timer, Mesmerize them.\n\
+ This will completely immobilize, mute, and blind them for the next 13.5 seconds."
+ bloodcost = 30
+ cooldown_time = 35 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isliving(target_atom)
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/CheckCanTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ var/mob/living/selected_target = target_atom
+ if(!selected_target.mind)
+ owner.balloon_alert(owner, "[selected_target] is mindless.")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced
+ name = "Level 4: Possession"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/two
+ level_current = 4
+ desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged to temporary Vassals."
+ power_explanation = "Level 4: Possession:\n\
+ Click any person to, after a 4 second timer, Mesmerize them.\n\
+ This will completely immobilize, mute, and blind them for the next 13.5 seconds.\n\
+ However, while adjacent to the target, if your target is in critical condition or dead, they will instead be turned into a temporary Vassal.\n\
+ If you use this on a currently dead normal Vassal, you will instead revive them normally.\n\
+ Despite being Mute and Deaf, they will still have complete loyalty to you, until their death in 5 minutes upon use."
+ background_icon_state = "tremere_power_gold_off"
+ active_background_icon_state = "tremere_power_gold_on"
+ base_background_icon_state = "tremere_power_gold_off"
+ bloodcost = 80
+ cooldown_time = 3 MINUTES
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/two
+ name = "Level 5: Possession"
+ desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged to temporary Vassals."
+ level_current = 5
+ upgraded_power = null
+ power_explanation = "Level 5: Possession:\n\
+ Click any person to, after a 4 second timer, Mesmerize them.\n\
+ This will completely immobilize, mute, and blind them for the next 13.5 seconds.\n\
+ However, while adjacent to the target, if your target is in critical condition or dead, they will instead be turned into a temporary Vassal.\n\
+ If you use this on a currently dead normal Vassal, you will instead revive them normally.\n\
+ They will have complete loyalty to you, until their death in 8 minutes upon use."
+ bloodcost = 100
+ cooldown_time = 2 MINUTES
+
+// The advanced version
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/CheckCanTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ var/mob/living/selected_target = target_atom
+ if((IS_VASSAL(selected_target) || selected_target.stat >= SOFT_CRIT) && !owner.Adjacent(selected_target))
+ owner.balloon_alert(owner, "out of range.")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/FireTargetedPower(atom/target_atom)
+ . = ..()
+ var/mob/living/target = target_atom
+ var/mob/living/user = owner
+ if(target.stat >= SOFT_CRIT && user.Adjacent(target) && level_current >= 4)
+ attempt_vassalize(target, user)
+ return
+ else if(IS_VASSAL(target))
+ owner.balloon_alert(owner, "vassal cant be revived")
+ return
+ attempt_mesmerize(target, user)
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/proc/attempt_mesmerize(mob/living/target, mob/living/user)
+ owner.balloon_alert(owner, "attempting to mesmerize.")
+ if(!do_after(user, 3 SECONDS, target, NONE, TRUE))
+ return
+
+ power_activated_sucessfully()
+ var/power_time = 90 + level_current * 15
+ if(IS_CURATOR(target))
+ to_chat(target, "You feel you something crawling under your skin, but it passes.")
+ return
+ if(HAS_TRAIT_FROM(target, TRAIT_MUTE, BLOODSUCKER_TRAIT))
+ owner.balloon_alert(owner, "[target] is already in some form of hypnotic gaze.")
+ return
+ if(iscarbon(target))
+ var/mob/living/carbon/mesmerized = target
+ owner.balloon_alert(owner, "successfully mesmerized [mesmerized].")
+ if(level_current >= 2)
+ ADD_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+ if(level_current >= 3)
+ target.become_blind(BLOODSUCKER_TRAIT)
+ mesmerized.Immobilize(power_time)
+ mesmerized.next_move = world.time + power_time
+ mesmerized.notransform = TRUE
+ addtimer(CALLBACK(src, PROC_REF(end_mesmerize), user, target), power_time)
+ if(issilicon(target))
+ var/mob/living/silicon/mesmerized = target
+ mesmerized.emp_act(EMP_HEAVY)
+ owner.balloon_alert(owner, "temporarily shut [mesmerized] down.")
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/proc/end_mesmerize(mob/living/user, mob/living/target)
+ target.notransform = FALSE
+ target.cure_blind(BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+ if(istype(user) && target.stat == CONSCIOUS && (target in view(6, get_turf(user))))
+ owner.balloon_alert(owner, "[target] snapped out of their trance.")
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/proc/attempt_vassalize(mob/living/target, mob/living/user)
+ owner.balloon_alert(owner, "attempting to vassalize.")
+ if(!do_after(user, 6 SECONDS, target, NONE, TRUE))
+ return
+
+ if(IS_VASSAL(target))
+ power_activated_sucessfully()
+ to_chat(user, "We revive [target]!")
+ target.mind.grab_ghost()
+ target.revive(full_heal = TRUE)
+ return
+ if(IS_CURATOR(target))
+ to_chat(target, "Their body refuses to react...")
+ return
+ if(!bloodsuckerdatum_power.make_vassal(target))
+ return
+ power_activated_sucessfully()
+ to_chat(user, "We revive [target]!")
+ target.mind.grab_ghost()
+ target.revive(full_heal = TRUE)
+ var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal)
+ vassaldatum.special_type = TREMERE_VASSAL //don't turn them into a favorite please
+ var/living_time
+ if(level_current == 4)
+ living_time = 5 MINUTES
+ ADD_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+ ADD_TRAIT(owner, TRAIT_DEAF, BLOODSUCKER_TRAIT)
+ else if(level_current == 5)
+ living_time = 8 MINUTES
+ addtimer(CALLBACK(src, PROC_REF(end_possession), target), living_time)
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/proc/end_possession(mob/living/user)
+ REMOVE_TRAIT(user, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+ REMOVE_TRAIT(user, TRAIT_DEAF, BLOODSUCKER_TRAIT)
+ user.mind.remove_antag_datum(/datum/antagonist/vassal)
+ to_chat(user, "You feel the Blood of your Master run out!")
+ user.death()
diff --git a/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgey.dm b/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgey.dm
new file mode 100644
index 0000000000000..197d26a82378c
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgey.dm
@@ -0,0 +1,186 @@
+/**
+ * # Thaumaturgy
+ *
+ * Level 1 - One shot bloodbeam spell
+ * Level 2 - Bloodbeam spell - Gives them a Blood shield until they use Bloodbeam
+ * Level 3 - Bloodbeam spell that breaks open lockers/doors - Gives them a Blood shield until they use Bloodbeam
+ * Level 4 - Bloodbeam spell that breaks open lockers/doors + double damage to victims - Gives them a Blood shield until they use Bloodbeam
+ * Level 5 - Bloodbeam spell that breaks open lockers/doors + double damage & steals blood - Gives them a Blood shield until they use Bloodbeam
+ */
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy
+ name = "Level 1: Thaumaturgy"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/two
+ desc = "Fire a blood bolt at your enemy, dealing Burn damage."
+ level_current = 1
+ button_icon_state = "power_thaumaturgy"
+ power_explanation = "Thaumaturgy:\n\
+ Gives you a one shot blood bolt spell, firing it at a person deals 20 Burn damage"
+ check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
+ bloodcost = 20
+ constant_bloodcost = 0
+ cooldown_time = 6 SECONDS
+ prefire_message = "Click where you wish to fire."
+ ///Blood shield given while this Power is active.
+ var/datum/weakref/blood_shield
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/two
+ name = "Level 2: Thaumaturgy"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/three
+ desc = "Create a Blood shield and fire a blood bolt at your enemy, dealing Burn damage."
+ level_current = 2
+ power_explanation = "Thaumaturgy:\n\
+ Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
+ The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
+ You will also have the ability to fire a Blood beam, ending the Power.\n\
+ If the Blood beam hits a person, it will deal 20 Burn damage."
+ prefire_message = "Click where you wish to fire (using your power removes blood shield)."
+ bloodcost = 40
+ cooldown_time = 4 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/three
+ name = "Level 3: Thaumaturgy"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced
+ desc = "Create a Blood shield and fire a blood bolt, dealing Burn damage and opening doors/lockers."
+ level_current = 3
+ power_explanation = "Thaumaturgy:\n\
+ Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
+ The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
+ You will also have the ability to fire a Blood beam, ending the Power.\n\
+ If the Blood beam hits a person, it will deal 20 Burn damage. If it hits a locker or door, it will break it open."
+ bloodcost = 50
+ cooldown_time = 6 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced
+ name = "Level 4: Blood Strike"
+ upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced/two
+ desc = "Create a Blood shield and fire a blood bolt, dealing Burn damage and opening doors/lockers."
+ level_current = 4
+ power_explanation = "Thaumaturgy:\n\
+ Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
+ The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
+ You will also have the ability to fire a Blood beam, ending the Power.\n\
+ If the Blood beam hits a person, it will deal 40 Burn damage.\n\
+ If it hits a locker or door, it will break it open."
+ background_icon_state = "tremere_power_gold_off"
+ active_background_icon_state = "tremere_power_gold_on"
+ base_background_icon_state = "tremere_power_gold_off"
+ prefire_message = "Click where you wish to fire (using your power removes blood shield)."
+ bloodcost = 60
+ cooldown_time = 6 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced/two
+ name = "Level 5: Blood Strike"
+ upgraded_power = null
+ desc = "Create a Blood shield and fire a blood bolt, dealing Burn damage, stealing Blood and opening doors/lockers."
+ level_current = 5
+ power_explanation = "Thaumaturgy:\n\
+ Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
+ The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
+ You will also have the ability to fire a Blood beam, ending the Power.\n\
+ If the Blood beam hits a person, it will deal 40 Burn damage and steal blood to feed yourself, though at a net-negative.\n\
+ If it hits a locker or door, it will break it open."
+ bloodcost = 80
+ cooldown_time = 8 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/ActivatePower(trigger_flags)
+ . = ..()
+ owner.balloon_alert(owner, "you start thaumaturgy")
+ if(level_current >= 2) // Only if we're at least level 2.
+ var/obj/item/shield/bloodsucker/new_shield = new
+ blood_shield = WEAKREF(new_shield)
+ if(!owner.put_in_inactive_hand(new_shield))
+ owner.balloon_alert(owner, "off hand is full!")
+ to_chat(owner, "Blood shield couldn't be activated as your off hand is full.")
+ return FALSE
+ owner.visible_message(
+ "[owner]\'s hands begins to bleed and forms into a blood shield!",
+ "We activate our Blood shield!",
+ "You hear liquids forming together.",
+ )
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/DeactivatePower()
+ if(blood_shield)
+ QDEL_NULL(blood_shield)
+ return ..()
+
+/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/FireTargetedPower(atom/target_atom)
+ . = ..()
+
+ var/mob/living/user = owner
+ owner.balloon_alert(owner, "you fire a blood bolt!")
+ to_chat(user, "You fire a blood bolt!")
+ user.changeNext_move(CLICK_CD_RANGE)
+ user.newtonian_move(get_dir(target_atom, user))
+ var/obj/projectile/magic/arcane_barrage/bloodsucker/magic_9ball = new(user.loc)
+ magic_9ball.bloodsucker_power = src
+ magic_9ball.firer = user
+ magic_9ball.def_zone = ran_zone(user.zone_selected)
+ magic_9ball.preparePixelProjectile(target_atom, user)
+ INVOKE_ASYNC(magic_9ball, TYPE_PROC_REF(/obj/projectile, fire))
+ playsound(user, 'sound/magic/wand_teleport.ogg', 60, TRUE)
+ power_activated_sucessfully()
+
+/**
+ * # Blood Bolt
+ *
+ * This is the projectile this Power will fire.
+ */
+/obj/projectile/magic/arcane_barrage/bloodsucker
+ name = "blood bolt"
+ icon_state = "mini_leaper"
+ damage = 20
+ var/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/bloodsucker_power
+
+/obj/projectile/magic/arcane_barrage/bloodsucker/on_hit(target)
+ if(istype(target, /obj/structure/closet) && bloodsucker_power.level_current >= 3)
+ var/obj/structure/closet/hit_closet = target
+ if(hit_closet)
+ hit_closet.welded = FALSE
+ hit_closet.locked = FALSE
+ hit_closet.broken = TRUE
+ hit_closet.update_appearance()
+ qdel(src)
+ return BULLET_ACT_HIT
+ if(istype(target, /obj/machinery/door) && bloodsucker_power.level_current >= 3)
+ var/obj/machinery/door/hit_airlock = target
+ hit_airlock.open(2)
+ qdel(src)
+ return BULLET_ACT_HIT
+ if(ismob(target))
+ if(bloodsucker_power.level_current >= 4)
+ damage = 40
+ if(bloodsucker_power.level_current >= 5)
+ var/mob/living/person_hit = target
+ person_hit.blood_volume -= 60
+ bloodsucker_power.bloodsuckerdatum_power.AddBloodVolume(60)
+ qdel(src)
+ return BULLET_ACT_HIT
+ . = ..()
+
+/**
+ * # Blood Shield
+ *
+ * The shield spawned when using Thaumaturgy when strong enough.
+ * Copied mostly from '/obj/item/shield/changeling'
+ */
+
+/obj/item/shield/bloodsucker
+ name = "blood shield"
+ desc = "A shield made out of blood, requiring blood to sustain hits."
+ item_flags = ABSTRACT | DROPDEL
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ icon_state = "blood_shield"
+ lefthand_file = 'icons/bloodsuckers/bs_leftinhand.dmi'
+ righthand_file = 'icons/bloodsuckers/bs_rightinhand.dmi'
+ block_power = 75
+
+/obj/item/shield/bloodsucker/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, BLOODSUCKER_TRAIT)
+
+/obj/item/shield/bloodsucker/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum)
+ bloodsuckerdatum.AddBloodVolume(-15)
+ return ..()
diff --git a/code/modules/antagonists/bloodsucker/powers/tremere/tremere_base.dm b/code/modules/antagonists/bloodsucker/powers/tremere/tremere_base.dm
new file mode 100644
index 0000000000000..cb83521c45be1
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/tremere/tremere_base.dm
@@ -0,0 +1,28 @@
+/**
+ * # Tremere Powers
+ *
+ * This file is for Tremere power procs and Bloodsucker procs that deals exclusively with Tremere.
+ * Tremere has quite a bit of unique things to it, so I thought it's own subtype would be nice
+ */
+
+/datum/action/cooldown/bloodsucker/targeted/tremere
+ name = "Tremere Gift"
+ desc = "A Tremere exclusive gift."
+ button_icon = 'icons/bloodsuckers/actions_tremere_bloodsucker.dmi'
+ background_icon_state = "tremere_power_off"
+ icon_icon = 'icons/bloodsuckers/actions_tremere_bloodsucker.dmi'
+ button_icon_state = "power_auspex"
+
+ active_background_icon_state = "tremere_power_on"
+ base_background_icon_state = "tremere_power_off"
+
+ // Tremere powers don't level up, we have them hardcoded.
+ level_current = 0
+ // Re-defining these as we want total control over them
+ power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
+ purchase_flags = TREMERE_CAN_BUY
+ // Targeted stuff
+ power_activates_immediately = FALSE
+
+ ///The upgraded version of this Power. 'null' means it's the max level.
+ var/upgraded_power = null
diff --git a/code/modules/antagonists/bloodsucker/powers/vassal/distress.dm b/code/modules/antagonists/bloodsucker/powers/vassal/distress.dm
new file mode 100644
index 0000000000000..3b02ca8d4e3ac
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/vassal/distress.dm
@@ -0,0 +1,22 @@
+/datum/action/cooldown/bloodsucker/distress
+ name = "Distress"
+ desc = "Injure yourself, allowing you to make a desperate call for help to your Master."
+ button_icon_state = "power_distress"
+ power_explanation = "Distress:\n\
+ Use this Power from anywhere and your Master Bloodsucker will instantly be alerted of your location."
+ power_flags = NONE
+ check_flags = NONE
+ purchase_flags = NONE
+ bloodcost = 10
+ cooldown_time = 10 SECONDS
+
+/datum/action/cooldown/bloodsucker/distress/ActivatePower(trigger_flags)
+ . = ..()
+ var/turf/open/floor/target_area = get_area(owner)
+ var/datum/antagonist/vassal/vassaldatum = owner.mind.has_antag_datum(/datum/antagonist/vassal)
+
+ owner.balloon_alert(owner, "you call out for your master!")
+ to_chat(vassaldatum.master.owner, "[owner], your loyal Vassal, is desperately calling for aid at [target_area]!")
+
+ var/mob/living/user = owner
+ user.adjustBruteLoss(10)
diff --git a/code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm b/code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm
new file mode 100644
index 0000000000000..6b0e889292cfb
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm
@@ -0,0 +1,64 @@
+/// Used by Vassals
+/datum/action/cooldown/bloodsucker/recuperate
+ name = "Sanguine Recuperation"
+ desc = "Slowly heals you overtime using your master's blood, in exchange for some of your own blood and effort."
+ button_icon_state = "power_recup"
+ power_explanation = "Recuperate:\n\
+ Activating this Power will begin to heal your wounds.\n\
+ You will heal Brute and Toxin damage, at the cost of Stamina damage, and blood from both you and your Master.\n\
+ If you aren't a bloodless race, you will additionally heal Burn damage.\n\
+ The power will cancel out if you are incapacitated or dead."
+ power_flags = BP_AM_TOGGLE
+ check_flags = BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
+ purchase_flags = NONE
+ bloodcost = 1.5
+ cooldown_time = 10 SECONDS
+
+/datum/action/cooldown/bloodsucker/recuperate/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ if(user.stat >= DEAD || user.incapacitated())
+ user.balloon_alert(user, "you are incapacitated...")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/recuperate/ActivatePower(trigger_flags)
+ . = ..()
+ to_chat(owner, "Your muscles clench as your master's immortal blood mixes with your own, knitting your wounds.")
+ owner.balloon_alert(owner, "recuperate turned on.")
+
+/datum/action/cooldown/bloodsucker/recuperate/process(seconds_per_tick)
+ . = ..()
+ if(!.)
+ return
+
+ if(!active)
+ return
+ var/mob/living/carbon/user = owner
+ var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(user)
+ vassaldatum.master.AddBloodVolume(-1)
+ user.Jitter(5 SECONDS)
+ user.adjustStaminaLoss(bloodcost * 1.1)
+ user.adjustBruteLoss(-2.5)
+ user.adjustToxLoss(-2, forced = TRUE)
+ // Plasmamen won't lose blood, they don't have any, so they don't heal from Burn.
+ if(!HAS_TRAIT(user, TRAIT_NO_BLOOD))
+ user.blood_volume -= bloodcost
+ user.adjustFireLoss(-1.5)
+ // Stop Bleeding
+ if(istype(user) && user.is_bleeding())
+ for(var/obj/item/bodypart/part in user.bodyparts)
+ user.add_bleeding(-10)
+
+/datum/action/cooldown/bloodsucker/recuperate/ContinueActive(mob/living/user, mob/living/target)
+ if(user.stat >= DEAD)
+ return FALSE
+ if(user.incapacitated())
+ owner.balloon_alert(owner, "too exhausted...")
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/recuperate/DeactivatePower()
+ owner.balloon_alert(owner, "recuperate turned off.")
+ return ..()
diff --git a/code/modules/antagonists/bloodsucker/powers/vassal/vassal_fold.dm b/code/modules/antagonists/bloodsucker/powers/vassal/vassal_fold.dm
new file mode 100644
index 0000000000000..6c84d53147fe7
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/vassal/vassal_fold.dm
@@ -0,0 +1,111 @@
+/datum/action/cooldown/bloodsucker/vassal_blood
+ name = "Help Vassal"
+ desc = "Bring an ex-Vassal back into the fold, or create blood using a bag."
+ button_icon_state = "power_torpor"
+ power_explanation = "Help Vassal:\n\
+ Use this power while you have an ex-Vassal grabbed to bring them back into the fold. \
+ Use this power with a bloodbag in your hand to instead fill it with Vampiric Blood which \
+ can be used to reset ex-vassal deconversion timers."
+ power_flags = NONE
+ check_flags = NONE
+ purchase_flags = NONE
+ bloodcost = 10
+ cooldown_time = 10 SECONDS
+
+ ///Bloodbag we have in our hands.
+ var/obj/item/reagent_containers/blood/bloodbag
+ ///Weakref to a target we're bringing into the fold.
+ var/datum/weakref/target_ref
+
+/datum/action/cooldown/bloodsucker/vassal_blood/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(revenge_vassal)
+ return FALSE
+
+ if(owner.pulling && isliving(owner.pulling))
+ var/mob/living/pulled_target = owner.pulling
+ var/datum/antagonist/ex_vassal/former_vassal = pulled_target.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(!former_vassal)
+ owner.balloon_alert(owner, "not a former vassal!")
+ return FALSE
+ target_ref = WEAKREF(owner.pulling)
+ return TRUE
+
+ var/blood_bag = locate(/obj/item/reagent_containers/blood) in user.held_items
+ if(!blood_bag)
+ owner.balloon_alert(owner, "blood bag needed!")
+ return FALSE
+ if(istype(blood_bag, /obj/item/reagent_containers/blood/OMinus/bloodsucker))
+ owner.balloon_alert(owner, "already bloodsucker blood!")
+
+ bloodbag = blood_bag
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/vassal_blood/ActivatePower(trigger_flags, var/altclick = FALSE)
+ . = ..()
+ var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/vassal/revenge)
+
+ if(target_ref)
+ var/mob/living/target = target_ref.resolve()
+ var/datum/antagonist/ex_vassal/former_vassal = target.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(!former_vassal || former_vassal.revenge_vassal)
+ target_ref = null
+ return
+ if(do_after(owner, 5 SECONDS, target))
+ former_vassal.return_to_fold(revenge_vassal)
+ target_ref = null
+ DeactivatePower()
+ return
+
+ if(bloodbag)
+ var/mob/living/living_owner = owner
+ living_owner.blood_volume -= 150
+ QDEL_NULL(bloodbag)
+ var/obj/item/reagent_containers/blood/OMinus/bloodsucker/new_bag = new(owner.loc)
+ owner.put_in_active_hand(new_bag)
+ DeactivatePower()
+
+/datum/action/cooldown/bloodsucker/vassal_checkstatus
+ name = "Check Vassals"
+ desc = "Check vassal status"
+ button_icon_state = "original_moon"
+ power_explanation = "Help Vassal:\nShow the status of all Vassals"
+ power_flags = NONE
+ check_flags = NONE
+ purchase_flags = NONE
+ bloodcost = 10
+ cooldown_time = 10 SECONDS
+
+/datum/action/cooldown/bloodsucker/vassal_checkstatus/can_use(mob/living/carbon/user, trigger_flags)
+ . = ..()
+ if(!.)
+ return
+
+ var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(revenge_vassal)
+ return FALSE
+
+ if(revenge_vassal.ex_vassals.len)
+ owner.balloon_alert(owner, "no vassals!")
+ return FALSE
+
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/vassal_checkstatus/ActivatePower(trigger_flags)
+ . = ..()
+ var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/vassal/revenge)
+ for(var/datum/antagonist/ex_vassal/former_vassals as anything in revenge_vassal.ex_vassals)
+ var/information = "[former_vassals.owner.current]"
+ information += " - has [round(COOLDOWN_TIMELEFT(former_vassals, blood_timer) / 600)] minutes left of Blood"
+ var/turf/open/floor/target_area = get_area(owner)
+ if(target_area)
+ information += " - currently at [target_area]."
+ if(former_vassals.owner.current.stat >= DEAD)
+ information += " - DEAD."
+
+ to_chat(owner, "[information]")
+
+ DeactivatePower()
diff --git a/code/modules/antagonists/bloodsucker/powers/veil.dm b/code/modules/antagonists/bloodsucker/powers/veil.dm
new file mode 100644
index 0000000000000..00d613b55c000
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/powers/veil.dm
@@ -0,0 +1,139 @@
+/datum/action/cooldown/bloodsucker/veil
+ name = "Veil of Many Faces"
+ desc = "Disguise yourself in the illusion of another identity."
+ button_icon_state = "power_veil"
+ power_explanation = "Veil of Many Faces: \n\
+ Activating Veil of Many Faces will shroud you in smoke and forge you a new identity.\n\
+ Your name and appearance will be completely randomized, and turning the ability off again will undo it all.\n\
+ Clothes, gear, and Security/Medical HUD status is kept the same while this power is active."
+ power_flags = BP_AM_TOGGLE
+ check_flags = BP_CANT_USE_IN_FRENZY
+ purchase_flags = BLOODSUCKER_DEFAULT_POWER|VASSAL_CAN_BUY
+ bloodcost = 15
+ constant_bloodcost = 0.1
+ cooldown_time = 10 SECONDS
+ // Outfit Vars
+// var/list/original_items = list()
+ // Identity Vars
+ var/prev_gender
+ var/prev_skin_tone
+ var/prev_hair_style
+ var/prev_facial_hair_style
+ var/prev_hair_color
+ var/prev_facial_hair_color
+ var/prev_underwear
+ var/prev_undershirt
+ var/prev_socks
+ var/prev_disfigured
+ var/list/prev_features // For lizards and such
+
+/datum/action/cooldown/bloodsucker/veil/ActivatePower(trigger_flags)
+ . = ..()
+ cast_effect() // POOF
+// if(blahblahblah)
+// Disguise_Outfit()
+ veil_user()
+ owner.balloon_alert(owner, "veil turned on.")
+
+/* // Meant to disguise your character's clothing into fake ones.
+/datum/action/cooldown/bloodsucker/veil/proc/Disguise_Outfit()
+ return
+ // Step One: Back up original items
+*/
+
+/datum/action/cooldown/bloodsucker/veil/proc/veil_user()
+ // Change Name/Voice
+ var/mob/living/carbon/human/user = owner
+ user.name_override = user.dna.species.random_name(user.gender)
+ user.name = user.name_override
+ user.SetSpecialVoice(user.name_override)
+ to_chat(owner, "You mystify the air around your person. Your identity is now altered.")
+
+ // Store Prev Appearance
+ prev_gender = user.gender
+ prev_skin_tone = user.skin_tone
+ prev_hair_style = user.hair_style
+ prev_facial_hair_style = user.facial_hair_style
+ prev_hair_color = user.hair_color
+ prev_facial_hair_color = user.facial_hair_color
+ prev_underwear = user.underwear
+ prev_undershirt = user.undershirt
+ prev_socks = user.socks
+// prev_eye_color
+ prev_disfigured = HAS_TRAIT(user, TRAIT_DISFIGURED) // I was disfigured! //prev_disabilities = user.disabilities
+ prev_features = user.dna.features
+
+ // Change Appearance
+ user.gender = pick(MALE, FEMALE, PLURAL)
+ user.skin_tone = random_skin_tone()
+ user.hair_style = random_hair_style(user.gender)
+ user.facial_hair_style = pick(random_facial_hair_style(user.gender), "Shaved")
+ user.hair_color = random_short_color()
+ user.facial_hair_color = user.hair_color
+ user.underwear = random_underwear(user.gender)
+ user.undershirt = random_undershirt(user.gender)
+ user.socks = random_socks(user.gender)
+ //user.eye_color = random_eye_color()
+ if(prev_disfigured)
+ REMOVE_TRAIT(user, TRAIT_DISFIGURED, null)
+ user.dna.features = random_features()
+
+ // Apply Appearance
+ user.update_body() // Outfit and underware, also body.
+ user.update_mutant_bodyparts() // Lizard tails etc
+ user.update_hair()
+ user.update_body_parts()
+
+/datum/action/cooldown/bloodsucker/veil/DeactivatePower()
+ . = ..()
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/user = owner
+
+ // Revert Identity
+ user.UnsetSpecialVoice()
+ user.name_override = null
+ user.name = user.real_name
+
+ // Revert Appearance
+ user.gender = prev_gender
+ user.skin_tone = prev_skin_tone
+ user.hair_style = prev_hair_style
+ user.facial_hair_style = prev_facial_hair_style
+ user.hair_color = prev_hair_color
+ user.facial_hair_color = prev_facial_hair_color
+ user.underwear = prev_underwear
+ user.undershirt = prev_undershirt
+ user.socks = prev_socks
+
+ //user.disabilities = prev_disabilities // Restore HUSK, CLUMSY, etc.
+ if(prev_disfigured)
+ //We are ASSUMING husk. // user.status_flags |= DISFIGURED // Restore "Unknown" disfigurement
+ ADD_TRAIT(user, TRAIT_DISFIGURED, TRAIT_HUSK)
+ user.dna.features = prev_features
+
+ // Apply Appearance
+ user.update_body() // Outfit and underware, also body.
+ user.update_hair()
+ user.update_body_parts() // Body itself, maybe skin color?
+
+ cast_effect() // POOF
+ owner.balloon_alert(owner, "veil turned off.")
+
+
+// CAST EFFECT // General effect (poof, splat, etc) when you cast. Doesn't happen automatically!
+/datum/action/cooldown/bloodsucker/veil/proc/cast_effect()
+ // Effect
+ playsound(get_turf(owner), 'sound/magic/smoke.ogg', 20, 1)
+ var/datum/effect_system/steam_spread/bloodsucker/puff = new /datum/effect_system/steam_spread/()
+ puff.set_up(3, 0, get_turf(owner))
+ puff.attach(owner) //OPTIONAL
+ puff.start()
+ owner.spin(8, 1) //Spin around like a loon.
+
+/obj/effect/particle_effect/smoke/vampsmoke
+ opacity = FALSE
+ lifetime = 0
+
+/obj/effect/particle_effect/smoke/vampsmoke/fade_out(frames = 0.8 SECONDS)
+ ..(frames)
diff --git a/code/modules/antagonists/bloodsucker/soulstone_bloodsucker.dm b/code/modules/antagonists/bloodsucker/soulstone_bloodsucker.dm
new file mode 100644
index 0000000000000..6b4f13b33b033
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/soulstone_bloodsucker.dm
@@ -0,0 +1,24 @@
+// special soulstone for the malkavian clan
+/obj/item/soulstone/bloodsucker
+ theme = THEME_WIZARD
+ required_role = /datum/antagonist/vassal //vassals can free their master
+
+/obj/item/soulstone/bloodsucker/init_shade(mob/living/carbon/human/victim, mob/user, message_user = FALSE, mob/shade_controller)
+ . = ..()
+ for(var/mob/shades in contents)
+ shades.mind.add_antag_datum(/datum/antagonist/shaded_bloodsucker)
+
+/obj/item/soulstone/bloodsucker/transfer_soul(choice as text, target, mob/user, datum/antagonist/bloodsucker/bloodsuckerdatum)
+ . = ..()
+ for(var/mob/shades in contents)
+ var/datum/antagonist/shaded_bloodsucker/shaded_datum = shades.mind.has_antag_datum(/datum/antagonist/shaded_bloodsucker)
+ shaded_datum.objectives = bloodsuckerdatum.objectives
+
+/obj/item/soulstone/bloodsucker/getCultGhost(mob/living/carbon/victim, mob/user)
+ var/mob/dead/observer/chosen_ghost = victim.get_ghost(FALSE, TRUE)
+ if(!chosen_ghost || !chosen_ghost.client)
+ victim.dust()
+ return FALSE
+ victim.unequip_everything()
+ init_shade(victim, user, shade_controller = chosen_ghost)
+ return TRUE
diff --git a/code/modules/antagonists/bloodsucker/vassals/datum_vassal.dm b/code/modules/antagonists/bloodsucker/vassals/datum_vassal.dm
new file mode 100644
index 0000000000000..ff74facd4463e
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/vassals/datum_vassal.dm
@@ -0,0 +1,152 @@
+#define VASSAL_SCAN_MIN_DISTANCE 5
+#define VASSAL_SCAN_MAX_DISTANCE 500
+/// 2s update time.
+#define VASSAL_SCAN_PING_TIME 20
+
+/datum/antagonist/vassal
+ name = "\improper Vassal"
+ roundend_category = "vassals"
+ antagpanel_category = "Bloodsucker"
+ banning_key = ROLE_BLOODSUCKER
+ show_in_roundend = FALSE
+
+ var/vassal_hud_name = "vassal"
+
+ /// The Master Bloodsucker's antag datum.
+ var/datum/antagonist/bloodsucker/master
+ /// The Bloodsucker's team
+ var/datum/team/bloodsucker/vamp_team
+ /// List of all Purchased Powers, like Bloodsuckers.
+ var/list/datum/action/powers = list()
+ ///Whether this vassal is already a special type of Vassal.
+ var/special_type = FALSE
+ ///Description of what this Vassal does.
+ var/vassal_description
+
+/datum/antagonist/vassal/antag_panel_data()
+ return "Master : [master.owner.name]"
+
+/datum/antagonist/vassal/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ current_mob.apply_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+
+ add_antag_hud(ANTAG_HUD_BLOODSUCKER, vassal_hud_name, current_mob)
+ current_mob.faction |= FACTION_BLOODSUCKER
+
+ vamp_team = master.vamp_team
+
+/datum/antagonist/vassal/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ current_mob.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+
+ remove_antag_hud(ANTAG_HUD_BLOODSUCKER, current_mob)
+ current_mob.faction -= FACTION_BLOODSUCKER
+
+/datum/antagonist/vassal/proc/on_examine(datum/source, mob/examiner, examine_text)
+ SIGNAL_HANDLER
+ var/vassal_examine = return_vassal_examine(examiner)
+ if(vassal_examine)
+ examine_text += vassal_examine
+
+/datum/antagonist/vassal/on_gain()
+ RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(give_warning))
+ /// Enslave them to their Master
+ if(!master || !istype(master, master))
+ return
+ if(special_type)
+ if(!master.special_vassals[special_type])
+ master.special_vassals[special_type] = list()
+ master.special_vassals[special_type] |= src
+ master.vassals |= src
+ owner.enslave_mind_to_creator(master.owner.current)
+ owner.current.log_message("has been vassalized by [master.owner.current]!", LOG_ATTACK, color="#960000")
+ /// Give Recuperate Power
+ BuyPower(new /datum/action/cooldown/bloodsucker/recuperate)
+ /// Give Objectives
+ var/datum/objective/bloodsucker/vassal/vassal_objective = new
+ vassal_objective.owner = owner
+ objectives += vassal_objective
+ /// Give Vampire Language & Hud
+ owner.current.grant_all_languages(FALSE, FALSE, TRUE)
+ owner.current.grant_language(/datum/language/vampiric)
+ return ..()
+
+/datum/antagonist/vassal/on_removal()
+ UnregisterSignal(owner.current, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN)
+ //Free them from their Master
+ if(master && master.owner)
+ if(special_type && master.special_vassals[special_type])
+ master.special_vassals[special_type] -= src
+ master.vassals -= src
+ owner.enslaved_to = null
+ //Remove ALL Traits, as long as its from BLOODSUCKER_TRAIT's source.
+ for(var/all_status_traits in owner.current.status_traits)
+ REMOVE_TRAIT(owner.current, all_status_traits, BLOODSUCKER_TRAIT)
+ //Remove Recuperate Power
+ while(powers.len)
+ var/datum/action/cooldown/bloodsucker/power = pick(powers)
+ powers -= power
+ power.Remove(owner.current)
+ //Remove Language
+ owner.current.remove_language(/datum/language/vampiric)
+ return ..()
+
+/datum/antagonist/vassal/on_body_transfer(mob/living/old_body, mob/living/new_body)
+ . = ..()
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
+ all_powers.Remove(old_body)
+ all_powers.Grant(new_body)
+
+/datum/antagonist/vassal/greet()
+ . = ..()
+ if(silent)
+ return
+
+ to_chat(owner, "You are now the mortal servant of [master.owner.current], a Bloodsucker!")
+ to_chat(owner, "The power of [master.owner.current.p_their()] immortal blood compels you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.\n\
+ You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed.") // if only there was a /p_theirs() proc...
+ owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
+ antag_memory += "You, becoming the mortal servant of [master.owner.current], a bloodsucking vampire!
"
+ /// Message told to your Master.
+ to_chat(master.owner, "[owner.current] has become addicted to your immortal blood. [owner.current.p_they(TRUE)] [owner.current.p_are()] now your undying servant")
+ master.owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
+
+/datum/antagonist/vassal/farewell()
+ if(silent)
+ return
+
+ owner.current.visible_message(
+ "[owner.current]'s eyes dart feverishly from side to side, and then stop. [owner.current.p_they(TRUE)] seem[owner.current.p_s()] calm, \
+ like [owner.current.p_they()] [owner.current.p_have()] regained some lost part of [owner.current.p_them()]self.", \
+ "With a snap, you are no longer enslaved to [master.owner]! You breathe in heavily, having regained your free will.")
+ owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
+ /// Message told to your (former) Master.
+ if(master && master.owner)
+ to_chat(master.owner, "You feel the bond with your vassal [owner.current] has somehow been broken!")
+
+/datum/antagonist/vassal/admin_add(datum/mind/new_owner, mob/admin)
+ var/list/datum/mind/possible_vampires = list()
+ for(var/datum/antagonist/bloodsucker/bloodsuckerdatums in GLOB.antagonists)
+ var/datum/mind/vamp = bloodsuckerdatums.owner
+ if(!vamp)
+ continue
+ if(!vamp.current)
+ continue
+ if(vamp.current.stat == DEAD)
+ continue
+ possible_vampires += vamp
+ if(!length(possible_vampires))
+ message_admins("[key_name_admin(usr)] tried vassalizing [key_name_admin(new_owner)], but there were no bloodsuckers!")
+ return
+ var/datum/mind/choice = input("Which bloodsucker should this vassal belong to?", "Bloodsucker") in possible_vampires
+ if(!choice)
+ return
+ log_admin("[key_name_admin(usr)] turned [key_name_admin(new_owner)] into a vassal of [key_name_admin(choice)]!")
+ var/datum/antagonist/bloodsucker/vampire = choice.has_antag_datum(/datum/antagonist/bloodsucker)
+ master = vampire
+ new_owner.add_antag_datum(src)
+ to_chat(choice, "Through divine intervention, you've gained a new vassal!")
diff --git a/code/modules/antagonists/bloodsucker/vassals/ex_vassal.dm b/code/modules/antagonists/bloodsucker/vassals/ex_vassal.dm
new file mode 100644
index 0000000000000..89baebe4310f1
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/vassals/ex_vassal.dm
@@ -0,0 +1,82 @@
+#define BLOOD_TIMER_REQUIREMENT (10 MINUTES)
+#define BLOOD_TIMER_HALWAY (BLOOD_TIMER_REQUIREMENT / 2)
+
+/datum/antagonist/ex_vassal
+ name = "\improper Ex-Vassal"
+ roundend_category = "vassals"
+ antagpanel_category = "Bloodsucker"
+ banning_key = ROLE_BLOODSUCKER
+ var/vassal_hud_name = "vassal_grey"
+ show_in_roundend = FALSE
+ show_in_antagpanel = FALSE
+ silent = TRUE
+ ui_name = FALSE
+
+ ///The revenge vassal that brought us into the fold.
+ var/datum/antagonist/vassal/revenge/revenge_vassal
+ ///Timer we have to live
+ COOLDOWN_DECLARE(blood_timer)
+
+/datum/antagonist/ex_vassal/on_gain()
+ . = ..()
+ RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+
+/datum/antagonist/ex_vassal/on_removal()
+ if(revenge_vassal)
+ revenge_vassal.ex_vassals -= src
+ revenge_vassal = null
+ blood_timer = null
+
+ remove_antag_hud(ANTAG_HUD_BLOODSUCKER, owner.current)
+ return ..()
+
+/datum/antagonist/ex_vassal/proc/on_examine(datum/source, mob/examiner, examine_text)
+ SIGNAL_HANDLER
+
+ var/datum/antagonist/vassal/revenge/vassaldatum = examiner.mind.has_antag_datum(/datum/antagonist/vassal/revenge)
+ if(vassaldatum && !revenge_vassal)
+ examine_text += "[owner.current] is an ex-vassal!"
+
+/**
+ * Fold return
+ *
+ * Called when a Revenge bloodsucker gets a vassal back into the fold.
+ */
+/datum/antagonist/ex_vassal/proc/return_to_fold(datum/antagonist/vassal/revenge/mike_ehrmantraut)
+ revenge_vassal = mike_ehrmantraut // what did john fulp willard mean by this
+ mike_ehrmantraut.ex_vassals += src
+ COOLDOWN_START(src, blood_timer, BLOOD_TIMER_REQUIREMENT)
+ add_antag_hud(ANTAG_HUD_BLOODSUCKER, vassal_hud_name, owner.current)
+
+ RegisterSignal(src, COMSIG_LIVING_LIFE, PROC_REF(on_life))
+
+/datum/antagonist/ex_vassal/proc/on_life(datum/source, seconds_per_tick, times_fired)
+ SIGNAL_HANDLER
+
+ if(COOLDOWN_TIMELEFT(src, blood_timer) <= BLOOD_TIMER_HALWAY + 2 && COOLDOWN_TIMELEFT(src, blood_timer) >= BLOOD_TIMER_HALWAY - 2) //just about halfway
+ to_chat(owner.current, "You need new blood from your Master!")
+ if(!COOLDOWN_FINISHED(src, blood_timer))
+ return
+ to_chat(owner.current, "You are out of blood!")
+ to_chat(revenge_vassal.owner.current, "[owner.current] has ran out of blood and is no longer in the fold!")
+ owner.remove_antag_datum(/datum/antagonist/ex_vassal)
+
+
+/**
+ * Bloodsucker Blood
+ *
+ * Artificially made, this must be fed to ex-vassals to keep them on their high.
+ */
+/datum/reagent/blood/bloodsucker
+ name = "Blood two"
+
+/datum/reagent/blood/bloodsucker/on_mob_metabolize(mob/living/L)
+ var/datum/antagonist/ex_vassal/former_vassal = L.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(former_vassal)
+ to_chat(L, "You feel the blood restore you... You feel safe.")
+ COOLDOWN_RESET(former_vassal, blood_timer)
+ COOLDOWN_START(former_vassal, blood_timer, BLOOD_TIMER_REQUIREMENT)
+ return ..()
+
+#undef BLOOD_TIMER_REQUIREMENT
+#undef BLOOD_TIMER_HALWAY
diff --git a/code/modules/antagonists/bloodsucker/vassals/favorite_vassal.dm b/code/modules/antagonists/bloodsucker/vassals/favorite_vassal.dm
new file mode 100644
index 0000000000000..b67d8d2928d18
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/vassals/favorite_vassal.dm
@@ -0,0 +1,23 @@
+/**
+ * Favorite Vassal
+ *
+ * Gets some cool abilities depending on the Clan.
+ */
+/datum/antagonist/vassal/favorite
+ name = "\improper Favorite Vassal"
+ show_in_antagpanel = FALSE
+ vassal_hud_name = "vassal6"
+ special_type = FAVORITE_VASSAL
+ vassal_description = "The Favorite Vassal gets unique abilities over other Vassals depending on your Clan \
+ and becomes completely immune to Mindshields. If part of Ventrue, this is the Vassal you will rank up."
+
+ ///Bloodsucker levels, but for Vassals, used by Ventrue.
+ var/vassal_level
+
+/datum/antagonist/vassal/favorite/on_gain()
+ . = ..()
+ SEND_SIGNAL(master, BLOODSUCKER_MAKE_FAVORITE, src)
+
+///Set the Vassal's rank to their Bloodsucker level
+/datum/antagonist/vassal/favorite/proc/set_vassal_level(mob/living/carbon/human/target)
+ master.bloodsucker_level = vassal_level
diff --git a/code/modules/antagonists/bloodsucker/vassals/misc_procs_vassal.dm b/code/modules/antagonists/bloodsucker/vassals/misc_procs_vassal.dm
new file mode 100644
index 0000000000000..1456e4c1bd0cc
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/vassals/misc_procs_vassal.dm
@@ -0,0 +1,72 @@
+/datum/antagonist/vassal/proc/give_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message)
+ SIGNAL_HANDLER
+ if(vassal_warning_message)
+ to_chat(owner, vassal_warning_message)
+
+/**
+ * Returns a Vassals's examine strings.
+ * Args:
+ * viewer - The person examining.
+ */
+/datum/antagonist/vassal/proc/return_vassal_examine(mob/living/viewer)
+ if(!viewer.mind || !iscarbon(owner.current))
+ return FALSE
+ var/mob/living/carbon/carbon_current = owner.current
+ // Target must be a Vassal
+ // Default String
+ var/returnString = "\["
+ var/returnIcon = ""
+ // Vassals and Bloodsuckers recognize eachother, while Monster Hunters can see Vassals.
+ if(!IS_BLOODSUCKER(viewer) && !IS_VASSAL(viewer) && !IS_CURATOR(viewer))
+ return FALSE
+ // Am I Viewer's Vassal?
+ if(master.owner == viewer.mind)
+ returnString += "This [carbon_current.dna.species.name] bears YOUR mark!"
+ returnIcon = "[icon2html('icons/bloodsuckers/vampiric.dmi', world, "vassal")]"
+ // Am I someone ELSE'S Vassal?
+ else if(IS_BLOODSUCKER(viewer) || IS_CURATOR(viewer))
+ returnString += "This [carbon_current.dna.species.name] bears the mark of [master.return_full_name()][master.broke_masquerade ? " who has broken the Masquerade" : ""]"
+ returnIcon = "[icon2html('icons/bloodsuckers/vampiric.dmi', world, "vassal_grey")]"
+ // Are you serving the same master as I am?
+ else if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in master.vassals)
+ returnString += "[p_they(TRUE)] bears the mark of your Master"
+ returnIcon = "[icon2html('icons/bloodsuckers/vampiric.dmi', world, "vassal")]"
+ // You serve a different Master than I do.
+ else
+ returnString += "[p_they(TRUE)] bears the mark of another Bloodsucker"
+ returnIcon = "[icon2html('icons/bloodsuckers/vampiric.dmi', world, "vassal_grey")]"
+
+ returnString += "\]" // \n" Don't need spacers. Using . += "" in examine.dm does this on its own.
+ return returnIcon + returnString
+
+/// Used when your Master teaches you a new Power.
+/datum/antagonist/vassal/proc/BuyPower(datum/action/cooldown/bloodsucker/power)
+ powers += power
+ power.Grant(owner.current)
+ log_game("[key_name(owner.current)] purchased [power] as a vassal.")
+
+/datum/antagonist/vassal/proc/LevelUpPowers()
+ for(var/datum/action/cooldown/bloodsucker/power in powers)
+ power.level_current++
+
+/// Called when we are made into the Favorite Vassal
+/datum/antagonist/vassal/proc/make_special(datum/antagonist/vassal/vassal_type)
+ //store what we need
+ var/datum/mind/vassal_owner = owner
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = master
+
+ //remove our antag datum
+ silent = TRUE
+ vassal_owner.remove_antag_datum(/datum/antagonist/vassal)
+
+ //give our new one
+ var/datum/antagonist/vassal/vassaldatum = new vassal_type(vassal_owner)
+ vassaldatum.master = bloodsuckerdatum
+ vassaldatum.silent = TRUE
+ vassal_owner.add_antag_datum(vassaldatum)
+ vassaldatum.silent = FALSE
+
+ //send alerts of completion
+ to_chat(master, "You have turned [vassal_owner.current] into your [vassaldatum.name]! They will no longer be deconverted upon Mindshielding!")
+ to_chat(vassal_owner, "As Blood drips over your body, you feel closer to your Master... You are now the Favorite Vassal!")
+ vassal_owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 75, FALSE, pressure_affected = FALSE)
diff --git a/code/modules/antagonists/bloodsucker/vassals/pinpointer_vassal.dm b/code/modules/antagonists/bloodsucker/vassals/pinpointer_vassal.dm
new file mode 100644
index 0000000000000..fb9202a5113cf
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/vassals/pinpointer_vassal.dm
@@ -0,0 +1,31 @@
+/**
+ * # Vassal Pinpointer
+ *
+ * Pinpointer that points to their Master's location at all times.
+ * Unlike the Monster hunter one, this one is permanently active, and has no power needed to activate it.
+ */
+
+/atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition
+ name = "Blood Bond"
+ desc = "You always know where your master is."
+
+/datum/status_effect/agent_pinpointer/vassal_edition
+ id = "agent_pinpointer"
+ alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition
+ minimum_range = VASSAL_SCAN_MIN_DISTANCE
+ tick_interval = VASSAL_SCAN_PING_TIME
+ duration = -1
+ range_fuzz_factor = 0
+
+/datum/status_effect/agent_pinpointer/vassal_edition/on_creation(mob/living/new_owner, ...)
+ ..()
+ var/datum/antagonist/vassal/antag_datum = new_owner.mind.has_antag_datum(/datum/antagonist/vassal)
+ scan_target = antag_datum?.master?.owner?.current
+
+/datum/status_effect/agent_pinpointer/vassal_edition/scan_for_target()
+ return
+
+/datum/status_effect/agent_pinpointer/vassal_edition/Destroy()
+ if(scan_target)
+ to_chat(owner, "You've lost your master's trail.")
+ return ..()
diff --git a/code/modules/antagonists/bloodsucker/vassals/revenge_vassal.dm b/code/modules/antagonists/bloodsucker/vassals/revenge_vassal.dm
new file mode 100644
index 0000000000000..2e1072babbdc6
--- /dev/null
+++ b/code/modules/antagonists/bloodsucker/vassals/revenge_vassal.dm
@@ -0,0 +1,83 @@
+/**
+ * Revenge Vassal
+ *
+ * Has the goal to 'get revenge' when their Master dies.
+ */
+/datum/antagonist/vassal/revenge
+ name = "\improper Revenge Vassal"
+ roundend_category = "abandoned Vassals"
+ show_in_roundend = FALSE
+ show_in_antagpanel = FALSE
+ vassal_hud_name = "vassal4"
+ special_type = REVENGE_VASSAL
+ vassal_description = "The Revenge Vassal will not deconvert on your Final Death, \
+ instead they will gain all your Powers, and the objective to take revenge for your demise. \
+ They additionally maintain your Vassals after your departure, rather than become aimless."
+
+ ///all ex-vassals brought back into the fold.
+ var/list/datum/antagonist/ex_vassal/ex_vassals = list()
+
+/datum/antagonist/vassal/revenge/roundend_report()
+ var/list/report = list()
+ report += printplayer(owner)
+ if(objectives.len)
+ report += printobjectives(objectives)
+
+ // Now list their vassals
+ if(ex_vassals.len)
+ report += ""
+ for(var/datum/antagonist/ex_vassal/all_vassals as anything in ex_vassals)
+ if(!all_vassals.owner)
+ continue
+ report += "[all_vassals.owner.name] the [all_vassals.owner.assigned_role]"
+
+ return report.Join("
")
+
+/datum/antagonist/vassal/revenge/on_gain()
+ . = ..()
+ RegisterSignal(master, BLOODSUCKER_FINAL_DEATH, PROC_REF(on_master_death))
+
+/datum/antagonist/vassal/revenge/on_removal()
+ UnregisterSignal(master, BLOODSUCKER_FINAL_DEATH)
+ return ..()
+
+/datum/antagonist/vassal/revenge/ui_static_data(mob/user)
+ var/list/data = list()
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ var/list/power_data = list()
+
+ power_data["power_name"] = power.name
+ power_data["power_explanation"] = power.power_explanation
+ power_data["power_icon"] = power.button_icon_state
+
+ data["power"] += list(power_data)
+
+ return data + ..()
+
+/datum/antagonist/vassal/revenge/proc/on_master_death(datum/antagonist/bloodsucker/bloodsuckerdatum, mob/living/carbon/master)
+ SIGNAL_HANDLER
+
+ show_in_roundend = TRUE
+ for(var/datum/objective/all_objectives as anything in objectives)
+ objectives -= all_objectives
+ BuyPower(new /datum/action/cooldown/bloodsucker/vassal_blood)
+ BuyPower(new /datum/action/cooldown/bloodsucker/vassal_checkstatus)
+ for(var/datum/action/cooldown/bloodsucker/master_powers as anything in bloodsuckerdatum.powers)
+ if(master_powers.purchase_flags & BLOODSUCKER_DEFAULT_POWER)
+ continue
+ master_powers.Grant(owner.current)
+ owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+
+ var/datum/objective/survive/new_objective = new
+ new_objective.name = "Avenge Bloodsucker"
+ new_objective.explanation_text = "Avenge your Bloodsucker's death by recruiting their ex-vassals and continuing their operations."
+ new_objective.owner = owner
+ objectives += new_objective
+
+ if(info_button_ref)
+ QDEL_NULL(info_button_ref)
+
+ ui_name = "AntagInfoRevengeVassal" //give their new ui
+ var/datum/action/antag_info/info_button = new(src)
+ info_button.Grant(owner.current)
+ info_button_ref = WEAKREF(info_button)
diff --git a/code/modules/antagonists/role_preference/role_antagonists.dm b/code/modules/antagonists/role_preference/role_antagonists.dm
index 44111cecfddfe..2b984b232bc8b 100644
--- a/code/modules/antagonists/role_preference/role_antagonists.dm
+++ b/code/modules/antagonists/role_preference/role_antagonists.dm
@@ -328,3 +328,19 @@
use_icon = /datum/role_preference/antagonist/traitor
#undef TRAITOR_DESC_DETAILS
+
+/datum/role_preference/antagonist/bloodsucker
+ name = "Bloodsucker"
+ description="After your death, you awaken to see yourself as an undead monster.\nUse your Vampiric abilities as best you can.\nScrape by Space Station 13, or take over it, vassalizing your way."
+ antag_datum = /datum/antagonist/bloodsucker
+ preview_outfit = /datum/outfit/bloodsucker_outfit
+
+/datum/role_preference/midround_living/bloodsucker
+ name = "Vampiric Accident"
+ description="After your death, you awaken to see yourself as an undead monster.\nUse your Vampiric abilities as best you can.\nScrape by Space Station 13, or take over it, vassalizing your way."
+ antag_datum = /datum/antagonist/bloodsucker
+ preview_outfit = /datum/outfit/bloodsucker_outfit
+
+/datum/outfit/bloodsucker_outfit
+ name = "Bloodsucker outfit (Preview only)"
+ suit = /obj/item/clothing/suit/costume/dracula
diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm
index e5c6e65c03d49..a828c36f95282 100644
--- a/code/modules/language/language_holder.dm
+++ b/code/modules/language/language_holder.dm
@@ -424,6 +424,12 @@ Key procs
/datum/language/sonus = list(LANGUAGE_ATOM),
/datum/language/sylvan = list(LANGUAGE_ATOM))
+/datum/language_holder/vampire
+ understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/vampiric = list(LANGUAGE_ATOM))
+ spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/vampiric = list(LANGUAGE_ATOM))
+
/datum/language_holder/empty
understood_languages = list()
spoken_languages = list()
diff --git a/code/modules/language/vampiric.dm b/code/modules/language/vampiric.dm
new file mode 100644
index 0000000000000..bb9c3694c72b7
--- /dev/null
+++ b/code/modules/language/vampiric.dm
@@ -0,0 +1,21 @@
+/datum/language/vampiric
+ name = "Blah-Sucker"
+ desc = "The native language of the Bloodsucker elders, learned intuitively by Fledglings as they pass from death into immortality."
+ key = "l"
+ space_chance = 40
+ default_priority = 90
+
+ flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD__LINGUIST_ONLY
+ syllables = list(
+ "luk","cha","no","kra","pru","chi","busi","tam","pol","spu","och",
+ "umf","ora","stu","si","ri","li","ka","red","ani","lup","ala","pro",
+ "to","siz","nu","pra","ga","ump","ort","a","ya","yach","tu","lit",
+ "wa","mabo","mati","anta","tat","tana","prol",
+ "tsa","si","tra","te","ele","fa","inz",
+ "nza","est","sti","ra","pral","tsu","ago","esch","chi","kys","praz",
+ "froz","etz","tzil",
+ "t'","k'","t'","k'","th'","tz'"
+ )
+
+ icon_state = "bloodsucker"
+ icon = 'icons/bloodsuckers/vampiric.dmi'
diff --git a/code/modules/library/lib_codex_gigas.dm b/code/modules/library/lib_codex_gigas.dm
index d157ef3e97069..b4a95c271f13b 100644
--- a/code/modules/library/lib_codex_gigas.dm
+++ b/code/modules/library/lib_codex_gigas.dm
@@ -10,3 +10,97 @@
author = "Forces beyond your comprehension"
unique = 1
title = "the Codex Gigas"
+
+/obj/item/book/codex_gigas/Initialize(mapload)
+ . = ..()
+ var/turf/current_turf = get_turf(src)
+ new /obj/item/book/kindred(current_turf)
+
+/**
+ * # Archives of the Kindred:
+ *
+ * A book that can only be used by Curators.
+ * When used on a player, after a short timer, will reveal if the player is a Bloodsucker, including their real name and Clan.
+ * This book should not work on Bloodsuckers using the Masquerade ability.
+ * If it reveals a Bloodsucker, the Curator will then be able to tell they are a Bloodsucker on examine (Like a Vassal).
+ * Reading it normally will allow Curators to read what each Clan does, with some extra flavor text ones.
+ *
+ * Regular Bloodsuckers won't have any negative effects from the book, while everyone else will get burns/eye damage.
+ */
+/obj/item/book/kindred
+ name = "\improper Archive of the Kindred"
+ title = "the Archive of the Kindred"
+ desc = "Cryptic documents explaining hidden truths behind Undead beings. It is said only Curators can decipher what they really mean."
+ icon = 'icons/bloodsuckers/vamp_obj.dmi'
+ lefthand_file = 'icons/bloodsuckers/bs_leftinhand.dmi'
+ righthand_file = 'icons/bloodsuckers/bs_rightinhand.dmi'
+ icon_state = "kindred_book"
+ author = "dozens of generations of Curators"
+ unique = TRUE
+ throw_speed = 1
+ throw_range = 10
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ ///Boolean on whether the book is currently being used, so you can only use it on one person at a time.
+ var/in_use = FALSE
+
+/obj/item/book/kindred/Initialize()
+ . = ..()
+ AddComponent(/datum/component/stationloving, FALSE, TRUE)
+
+///Attacking someone with the book.
+/obj/item/book/kindred/afterattack(mob/living/target, mob/living/user, flag, params)
+ . = ..()
+ if(!user.can_read(src) || in_use || (target == user) || !ismob(target))
+ return
+ if(!IS_CURATOR(user))
+ if(IS_BLOODSUCKER(user))
+ to_chat(user, "[src] seems to be too complicated for you. It would be best to leave this for someone else to take.")
+ return
+ to_chat(user, "[src] burns your hands as you try to use it!")
+ user.apply_damage(3, BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ return
+
+ in_use = TRUE
+ user.balloon_alert_to_viewers(user, "reading book...", "looks at [target] and [src]")
+ if(!do_after(user, 3 SECONDS, target, timed_action_flags = NONE, progress = TRUE))
+ to_chat(user, "You quickly close [src].")
+ in_use = FALSE
+ return
+ in_use = FALSE
+
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(target)
+ // Are we a Bloodsucker | Are we on Masquerade. If one is true, they will fail.
+ if(IS_BLOODSUCKER(target) && !HAS_TRAIT(target, TRAIT_MASQUERADE))
+ if(bloodsuckerdatum.broke_masquerade)
+ to_chat(user, "[target], also known as '[bloodsuckerdatum.return_full_name()]', is indeed a Bloodsucker, but you already knew this.")
+ return
+ to_chat(user, "[target], also known as '[bloodsuckerdatum.return_full_name()]', [bloodsuckerdatum.my_clan ? "is part of the [bloodsuckerdatum.my_clan]!" : "is not part of a clan."] You quickly note this information down, memorizing it.")
+ bloodsuckerdatum.break_masquerade()
+ else
+ to_chat(user, "You fail to draw any conclusions to [target] being a Bloodsucker.")
+
+/obj/item/book/kindred/attack_self(mob/living/user)
+ if(!IS_CURATOR(user))
+ if(IS_BLOODSUCKER(user))
+ to_chat(user, "[src] seems to be too complicated for you. It would be best to leave this for someone else to take.")
+ else
+ to_chat(user, "You feel your eyes unable to read the boring texts...")
+ return
+ ui_interact(user)
+
+/obj/item/book/kindred/ui_interact(mob/living/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "KindredBook", name)
+ ui.open()
+
+/obj/item/book/kindred/ui_static_data(mob/user)
+ var/data = list()
+
+ for(var/datum/bloodsucker_clan/clans as anything in subtypesof(/datum/bloodsucker_clan))
+ var/clan_data = list()
+ clan_data["clan_name"] = initial(clans.name)
+ clan_data["clan_desc"] = initial(clans.description)
+ data["clans"] += list(clan_data)
+
+ return data
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 95b5d8cab1761..c5a61800400e1 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -99,7 +99,7 @@
readout += "
[round(reagent.volume, 0.001)] units of [reagent.name]"
/*
readout += "
Stomach:"
- var/obj/item/organ/internal/stomach/belly = getorganslot(ORGAN_SLOT_STOMACH)
+ var/obj/item/organ/stomach/belly = getorganslot(ORGAN_SLOT_STOMACH)
for(var/datum/reagent/bile in belly?.reagents?.reagent_list)
if(!belly.food_reagents[bile.type])
readout += "
[round(bile.volume, 0.001)] units of [bile.name]"
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index e182215eff8d8..034b537837e48 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1721,3 +1721,27 @@
/mob/living/proc/on_handsblocked_end()
REMOVE_TRAIT(src, TRAIT_UI_BLOCKED, TRAIT_HANDS_BLOCKED)
REMOVE_TRAIT(src, TRAIT_PULL_BLOCKED, TRAIT_HANDS_BLOCKED)
+
+//
+// Bloodsucker functionality
+//
+
+/// Do I have a stake in my heart?
+/mob/living/proc/am_staked()
+ var/obj/item/bodypart/chosen_bodypart = get_bodypart(BODY_ZONE_CHEST)
+ if(!chosen_bodypart)
+ return FALSE
+ for(var/obj/item/embedded_stake in chosen_bodypart.embedded_objects)
+ if(istype(embedded_stake, /obj/item/stake))
+ return TRUE
+ return FALSE
+
+/// You can't go to sleep in a coffin with a stake in you.
+/mob/living/proc/StakeCanKillMe()
+ if(IsSleeping())
+ return TRUE
+ if(stat >= UNCONSCIOUS)
+ return TRUE
+ if(HAS_TRAIT(src, TRAIT_NODEATH))
+ return TRUE
+ return FALSE
diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm
index 37cb861f31e3b..503bc3ad715c4 100644
--- a/code/modules/reagents/reagent_containers/blood_pack.dm
+++ b/code/modules/reagents/reagent_containers/blood_pack.dm
@@ -87,6 +87,16 @@
/obj/item/reagent_containers/blood/universal
blood_type = "U"
+///Bloodbag of Bloodsucker blood (used by Vassals only)
+/obj/item/reagent_containers/blood/OMinus/bloodsucker
+ unique_blood = /datum/reagent/blood/bloodsucker
+
+/obj/item/reagent_containers/blood/OMinus/bloodsucker/examine(mob/user)
+ . = ..()
+ if(user.mind.has_antag_datum(/datum/antagonist/ex_vassal) || user.mind.has_antag_datum(/datum/antagonist/vassal/revenge))
+ . += "Seems to be just about the same color as your Master's..."
+
+
/obj/item/reagent_containers/blood/attackby(obj/item/I, mob/user, params)
if (istype(I, /obj/item/pen) || istype(I, /obj/item/toy/crayon))
if(!user.is_literate())
@@ -105,3 +115,39 @@
update_pack_name()
else
return ..()
+
+#define BLOODBAG_GULP_SIZE 5
+
+/obj/item/reagent_containers/blood/attack(mob/living/victim, mob/living/attacker, params)
+ if(!can_drink(victim, attacker))
+ return
+
+ if(victim != attacker)
+ if(!do_after(victim, 5 SECONDS, attacker))
+ return
+ attacker.visible_message(
+ "[attacker] forces [victim] to drink from the [src].",
+ "You put the [src] up to [victim]'s mouth.",
+ )
+ reagents.trans_to(victim, BLOODBAG_GULP_SIZE, transfered_by = attacker, methods = INGEST)
+ playsound(victim.loc, 'sound/items/drink.ogg', 30, 1)
+ return TRUE
+
+ while(do_after(victim, 1 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE, extra_checks = CALLBACK(src, PROC_REF(can_drink), victim, attacker)))
+ victim.visible_message(
+ "[victim] puts the [src] up to their mouth.",
+ "You take a sip from the [src].",
+ )
+ reagents.trans_to(victim, BLOODBAG_GULP_SIZE, transfered_by = attacker, methods = INGEST)
+ playsound(victim.loc, 'sound/items/drink.ogg', 30, 1)
+ return TRUE
+
+#undef BLOODBAG_GULP_SIZE
+
+/obj/item/reagent_containers/blood/proc/can_drink(mob/living/victim, mob/living/attacker)
+ if(!canconsume(victim, attacker))
+ return FALSE
+ if(!reagents || !reagents.total_volume)
+ to_chat(victim, "[src] is empty!")
+ return FALSE
+ return TRUE
diff --git a/icons/bloodsuckers/512x512.dmi b/icons/bloodsuckers/512x512.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..34ffce129a8b75276059300664a682baa59c40a4
GIT binary patch
literal 460
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
R?)>9b*VfQi|E>ys_dfy++
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
literal 0
HcmV?d00001
diff --git a/icons/bloodsuckers/blood_fountain.dmi b/icons/bloodsuckers/blood_fountain.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..ba8d26f35c7246ac6352668e0a9d53104c0a061e
GIT binary patch
literal 1498
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
literal 0
HcmV?d00001
diff --git a/icons/bloodsuckers/bs_rightinhand.dmi b/icons/bloodsuckers/bs_rightinhand.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..9621475609e9ff6c8656067e7799707ce23bd86d
GIT binary patch
literal 6900
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