[DONE] A bunch of bloodsucker changes (#1061)

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request
This adds a bunch of things as a addition to my original bloodsucker PR,
there's still one PR in the plans to refactor bloodsucker abilities.
#What this adds:
- Bloodsuckers loosing the heart or brain is lethal, before you could
simply walk around without a heart. Does not however cause a final death
- bloodsucker vampire bats will no longer /list you when attacking, and
are also just as fast as human runspeed
- Blood cost for vassalizing was almost completely removed, now only
taking tiny amounts of blood to do so.
- Dullahans can't be bloodsuckers since... their head isn't attached.
- ....buuuut bloodsuckers now kinda become helpless dullahans just with
no body if they get decapicated! Funny talking head go!
- Level up cost is now 15% of max blood, down from 20%
- The stat bar will show you the threshold for exiting frenzy now too
when you are frenzying.
- Maximum blood volume is shown in the stat menu as well
- Hopefully fixes some torpor loops related to frenzy
- Tremere now also get informed of how much blood they need to pay for
level ups.
- Fortitude now gives you burn resistance, just 20% less than brute
resistance
- Bloodsucker abilities now will show their level when you hover over
them. Trust me, this was really neccessary.
- Hopefully fixes cryoing or deleted bloodsuckers not removing vassals.
- Can longer use veil while unconcious or dead
- Caps humanity lost to 50, this means a maximum of 500 blood to enter
frenzy/ 10 fully drained people with clients, at this point you also
loose the masquerade ability.
- Admins can now ban bloodsuckers and vassals. 
- Bloodsuckers can now mesmerize mimes
- Fat bloodsuckers are no longer unable to enter coffins. Now they
resize their lair's coffin. Very obviously.
- Lizards are no longer fucked if they are on fire, they are no longer
forever too hot to survive
- Going into torpor in a coffin will extinguish and set your temperature
to normal
- bloodsuckers will no longer accrue toxin damage from not having a
liver or having a failing one, even though they had the toxin immune
trait
- synths will now heal their prosthetic limbs
- torpor start now checks for oxygen and toxin damage, and heals both
completely on it's end
- Haste will remove stuns at level 3, and do a non-stunning dash
- Tremere dominate vassalizing now actually works!
- Fixes ventrue objectives not succeeding properly
- Refactors the heart thief objective and improves it by requiring it to
be non-monkey hearts and letting bloodsuckers examine hearts to see if
they are valid, even uses the same logic, too!
- Ventrue favorite vassal batform now has night vision, and it's spawned
bats despawn once the cooldown refreshes, and halves it's cooldown to 1
minute
- Traits added to vassals get removed once they loose their antag datum,
ie favorite vassal traits, or the ones gained in the process of ventrue
sire-ing.
- Favorite vassals now clean up propertly when the antag datum is
removed, tremere, and malkavian clean up their related additions.
- Refactors how the blood display updates, instead it now updates every
time your blood level is changed, making it update far faster and only
when it's needed.
- Stakes now have different properties, the normal stake is fast to
stick in someone, but can fall out on it's own, the burnt one takes
longer to be put in, but doesn't fall out, the silver stake is the
slowest to be stuffed in someone's heart, and is the only stake to be
able to cause a Final Death to a sleeping or dead bloodsucker
<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->
closes #1180, closes #1208
<!-- Please make sure to actually test your PRs. If you have not tested
your PR mention it. -->

## Why It's Good For The Game

<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Proof Of Testing
The way I develop is always run a local server and test the changes that
I do, takes awhile but won't have as many bugs as a result
<!-- Compile and run your code locally. Make sure it works. This is the
place to show off your changes! We are not responsible for testing your
features. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and it's effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
add: Decapacitated bloodsuckers can now talk! 
add: The stat bar will show you the threshold for exiting frenzy now too
when you are frenzying.
add: Bloodsucker abilities now will show their level when you hover over
them. Trust me, this was really neccessary.
add: Tremere now also get informed of how much blood they need to pay
for level ups.
add: At level 3, haste will now remove stuns and do a non-stunning dash
add: Bloodsucker blood display now always updates when a bloodsucker's
blood level is changed
add: Bloodsuckers can now examine hearts to see if they are valid for
the heart thief objective
add: Bloodsucker vassals now clean up any traits gained from their
tenure as a vampire's minion.
add: Favorite vassals now properly clean up their effects once the antag
datum is lost.
add: Bloodsucker synths will be able to heal their prosthetic limbs, but
normal bloodsuckers still cannot.
add: When you exiit torpor as a bloodsucker, you will completely heal
any toxin and oxygen damage, as you should already be immune to it. This
is a safety feature.
add: Entering a coffin will normalize your temperature and extinguish
you if you're on fire, if you are a bloodsucker.

fix: bloodsucker vampire bats will no longer /list you when attacking
fix: Hopefully fixes some torpor loops related to frenzy
fix: Hopefully fixes bloodsuckers cryoing not removing vassal datums
fix: Bloodsuckers can no longer use veil of many faces while unconcious
fix: lizard bloodsuckers will no longer suffer from heat for a long time
fix: The ventrue objective now properly succeeds if you have sired a
vampire
fix: Livers are no longer required for bloodsuckers to live, as it
caused them toxin damage which overrode the trait that made them immune
to toxins.
fix: Entering torpor now accounts for all damage types, even when you
should be immune to them. This is to ensure you enter torpor to heal
those damage types in the case of odd behaviour.

balance: coffins should now set your temperature to normal and
extinguish you.
balance: bloodsucker level up cost is now 15% of max blood, down from
20%
balance: Caps humantiy loss at 50 humanity/500 blood to frenzy, and
makes it remove your masquerade ability.
balance: Bloodsucker bats now have human runspeed
balance: Blood cost for vassalizing was almost completely removed, now
only taking tiny amounts of blood to do so.
balance: Fortitude now gives you burn resistance, just 20% less than
brute resistance
balance: Can no longer gut bloodsuckers
balance: Bloodsuckers loosing their heart will make the vampire unable
to use their abilities or even regenerateor revive via torpor, you can
however regain your heart if you sleep in a coffin with a heart.
balance: Stakes now have different properties, the normal stake is fast
to stick in someone, but can fall out on it's own, the burnt one takes
longer to be put in, but doesn't fall out, the silver stake is the
slowest to be stuffed in someone's heart, and is the only stake to be
able to cause a Final Death to a sleeping or dead bloodsucker



del: Dullahans can't be bloodsuckers since... their head isn't attached
admin: Bloodsuckers and vassals can now be banned like other antags.

/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

<!-- By opening a pull request. You have read and understood the
repository rules located on the main README.md on this project. -->

---------

Co-authored-by: Waterpig <49160555+Majkl-J@users.noreply.github.com>
This commit is contained in:
Arturlang
2024-03-07 20:04:23 -05:00
committed by GitHub
co-authored by Waterpig
parent 0a5740f577
commit d0a4544e96
46 changed files with 777 additions and 355 deletions
+1
View File
@@ -137,6 +137,7 @@
#define ROLE_VAMPIRICACCIDENT "Bloodsucker (Midround)"
#define ROLE_BLOODSUCKERBREAKOUT "Bloodsucker (Latejoin)"
#define ROLE_MONSTERHUNTER "Monster Hunter"
#define ROLE_VASSAL "Vassal"
//BUBBER EDIT END
/// This defines the antagonists you can operate with in the settings.
+2
View File
@@ -433,6 +433,8 @@
ROLE_WIZARD,
ROLE_BORER, //SKYRAT EDIT
ROLE_ASSAULT_OPERATIVE, //SKYRAT EDIT
ROLE_BLOODSUCKER,// BUBBER EDIT,
ROLE_VASSAL, // BUBBER EDIT
),
"Skyrat Ban Options" = list(
BAN_PACIFICATION,
@@ -1,4 +1,16 @@
/mob/living/brain/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterpoof = null, message_range = 7, datum/saymode/saymode = null)
// BUBBER CHANGE: funny talking head
if(stat == DEAD) // lets you talk in deadchat
return ..()
var/datum/antagonist/bloodsucker/spooky_vampire_head = mind?.has_antag_datum(/datum/antagonist/bloodsucker)
if(!container && spooky_vampire_head)
var/obj/head = spooky_vampire_head.is_head(src)
if(!head)
return
var/animation_time = max(2, length_char(message) * 0.5)
head.Shake(duration = animation_time)
..()
// BUBBER CHANGE END
if(!(container && istype(container, /obj/item/mmi)))
return //No MMI, can't speak, bucko./N
else
@@ -4,22 +4,29 @@
/// You have special interactions with Bloodsuckers
#define TRAIT_BLOODSUCKER_HUNTER "bloodsucker_hunter"
// how much to multiply the coffin size by mob_size
#define COFFIN_ENLARGE_MULT 0.5
/**
* 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
#define TORTURE_BLOOD_HALF_COST 4
/// Cost to convert someone after successful torture, in blood
#define TORTURE_CONVERSION_COST 50
#define TORTURE_CONVERSION_COST 10
/// How much blood it costs you to make a vassal into a special vassal
#define SPECIAL_VASSAL_COST 150
/// Once blood is this low, will enter Frenzy
#define FRENZY_THRESHOLD_ENTER 25
/// Once blood is this high, will exit Frenzy
#define FRENZY_THRESHOLD_EXIT 250
/// a bloodsucker can't loose more humanity than this, and looses the masquerade ability when reaching it
#define HUMANITY_LOST_MAXIMUM 50
/// Level up blood cost define, max_blood * this = blood cost
#define BLOODSUCKER_LEVELUP_PERCENTAGE 0.2
#define BLOODSUCKER_LEVELUP_PERCENTAGE 0.15
///The level when at a bloodsucker becomes snobby about who they drink from and gain their non-fledling reputation
#define BLOODSUCKER_HIGH_LEVEL 4
@@ -73,7 +80,7 @@
/// This Power can't be used in Frenzy.
#define BP_CANT_USE_IN_FRENZY (1<<2)
/// This Power can't be used with a stake in you
#define BP_CANT_USE_WHILE_STAKED (1<<3)
#define BP_CAN_USE_WHILE_STAKED (1<<3)
/// This Power can't be used while incapacitated
#define BP_CANT_USE_WHILE_INCAPACITATED (1<<4)
/// This Power can't be used while unconscious
@@ -97,6 +104,13 @@
/// This Power doesn't cost bloot to run while unconscious
#define BP_AM_COSTLESS_UNCONSCIOUS (1<<3)
/**
* Torpor check bitflags
*/
#define TORPOR_SKIP_CHECK_ALL (1<<0)
#define TORPOR_SKIP_CHECK_FRENZY (1<<1)
#define TORPOR_SKIP_CHECK_DAMAGE (1<<2)
/**
* Bloodsucker Signals
*/
@@ -106,6 +120,8 @@
#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 bloodsucker looses their favorite vassal, cleaning up whatever they gained
#define BLOODSUCKER_LOOSE_FAVORITE "bloodsucker_loose_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.
@@ -120,6 +136,8 @@
#define BLOODSUCKER_ENTERS_FRENZY "bloodsucker_enters_frenzy"
///Called when a Bloodsucker exits Frenzy
#define BLOODSUCKER_EXITS_FRENZY "bloodsucker_exits_frenzy"
// Called when anyone enters the coffin
#define COMSIG_ENTER_COFFIN "comsig_enter_coffin"
/**
* Sol signals & Defines
@@ -164,6 +182,12 @@
*/
/// Source trait for Bloodsuckers-related traits
#define BLOODSUCKER_TRAIT "bloodsucker_trait"
#define VASSAL_TRAIT "vassal_trait"
/// Source trait for dominate related traits
#define DOMINATE_TRAIT "dominate_trait"
/// Source trait for Monster Hunter-related traits
#define HUNTER_TRAIT "monsterhunter_trait"
/// Source trait while Feeding
@@ -186,6 +210,7 @@
#define IS_MONSTERHUNTER(mob) (FALSE)
#define BLOODSUCKER_SIGHT_COLOR_CUTOFF list(25, 8, 5)
#define POLL_IGNORE_VASSAL "vassal"
// Why waste memory on a dynamic global list if we can just bake it in on compile time?
#define BLOODSUCKER_PROTECTED_ROLES list( \
@@ -214,4 +239,5 @@
/datum/species/skeleton, \
/datum/species/zombie, \
/datum/species/mutant, \
/datum/species/dullahan \
) \
+2
View File
@@ -1 +1,3 @@
#define TRAIT_MONOPHOBIA "monophobia"
#define TRAIT_COFFIN_ENLARGED "coffin_enlarged"
+3
View File
@@ -0,0 +1,3 @@
/datum/action
/// Extended description of the action, usually shown in an antag UI.
var/power_explanation = ""
@@ -38,6 +38,8 @@
/datum/dynamic_ruleset/roundstart/bloodsucker/execute()
for(var/datum/mind/candidate_minds as anything in assigned)
if(!candidate_minds.make_bloodsucker())
candidate_minds.special_role = null
candidate_minds.restricted_roles = null
message_admins("[ADMIN_LOOKUPFLW(candidate_minds)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.")
assigned -= candidate_minds
continue
@@ -0,0 +1,18 @@
/datum/antagonist
var/antag_panel_title = "Antagonist Panel"
var/antag_panel_description = "This is the antagonist panel. It contains all the abilities you have access to as an antagonist. Use them wisely."
/datum/antagonist/proc/ability_ui_data(actions = list())
var/list/data = list()
data["title"] = "[antag_panel_title]\n[antag_panel_data()]"
data["description"] = antag_panel_description
for(var/datum/action/cooldown/power as anything in actions)
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["powers"] += list(power_data)
return data
@@ -24,6 +24,8 @@
COOLDOWN_DECLARE(bloodsucker_spam_sol_burn)
///Timer between alerts for Healing messages
COOLDOWN_DECLARE(bloodsucker_spam_healing)
/// Timer between exiting torpor
COOLDOWN_DECLARE(bloodsucker_spam_torpor)
///Used for assigning your name
var/bloodsucker_name
@@ -38,10 +40,10 @@
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
/// sired by a ventrue
var/ventrue_sired
///ALL Powers currently owned
var/list/datum/action/cooldown/bloodsucker/powers = list()
@@ -91,12 +93,18 @@
TRAIT_AGEUSIA,
TRAIT_COLDBLOODED,
TRAIT_VIRUSIMMUNE,
TRAIT_TOXIMMUNE,
TRAIT_HARDLY_WOUNDED,
TRAIT_NO_MIRROR_REFLECTION,
TRAIT_DRINKS_BLOOD
TRAIT_DRINKS_BLOOD,
TRAIT_TOXIMMUNE,
// Fun fact, toxins can still be applied to you if you loose your liver even with TOXIMMUNE
TRAIT_STABLELIVER
)
var/biotype = MOB_VAMPIRIC
var/static/biotype = MOB_VAMPIRIC
/// Weakref to the owner mob's heart, without bloodsucker_life stops and they die. Handled via signals due to the fact that
/// Bloodsuckers don't take damage from lacking a heart due to TRAIT_NOBREATH
/// Saved here so we can keep a track of it and remove signals properly
var/datum/weakref/heart
/**
* Apply innate effects is everything given to the mob
@@ -110,6 +118,9 @@
RegisterSignal(current_mob, COMSIG_LIVING_LIFE, PROC_REF(LifeTick))
RegisterSignal(current_mob, COMSIG_LIVING_DEATH, PROC_REF(on_death))
RegisterSignal(current_mob, COMSIG_SPECIES_GAIN, PROC_REF(on_species_gain))
RegisterSignal(current_mob, COMSIG_QDELETING, PROC_REF(on_removal))
RegisterSignal(current_mob, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_organ_gain))
talking_head()
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_team_hud(current_mob)
@@ -117,7 +128,9 @@
on_hud_created()
else
RegisterSignal(current_mob, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
current_mob?.dna?.species.on_bloodsucker_gain(current_mob)
if(ishuman(current_mob))
current_mob?.dna?.species.on_bloodsucker_gain(current_mob)
add_signals_to_heart(current_mob)
#ifdef BLOODSUCKER_TESTING
var/turf/user_loc = get_turf(current_mob)
new /obj/structure/closet/crate/coffin(user_loc)
@@ -126,13 +139,14 @@
/**
* Remove innate effects is everything given to the mob
* When a body is tranferred, this is called on the old mob.
* When a body is transferred, 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/carbon/current_mob = mob_override || owner.current
UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_ATOM_EXAMINE, COMSIG_LIVING_DEATH, COMSIG_SPECIES_GAIN))
remove_signals_from_heart(current_mob)
UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_ATOM_EXAMINE, COMSIG_LIVING_DEATH, COMSIG_SPECIES_GAIN, COMSIG_QDELETING))
handle_clown_mutation(current_mob, removing = FALSE)
if(current_mob.hud_used)
@@ -160,17 +174,21 @@
bloodsucker_hud.show_hud(bloodsucker_hud.hud_version)
UnregisterSignal(owner.current, COMSIG_MOB_HUD_CREATED)
update_hud()
update_blood_hud()
/// Override some properties of incompatible species
/datum/antagonist/bloodsucker/proc/on_species_gain(mob/living/carbon/human/target, datum/species/current_species, datum/species/old_species)
SIGNAL_HANDLER
if(!ishuman(owner.current))
return
add_signals_to_heart(target)
var/mob/living/carbon/human/user = owner.current
user?.dna?.species.on_bloodsucker_gain(target)
/datum/antagonist/bloodsucker/get_admin_commands()
. = ..()
.["Set blood level"] = CALLBACK(src, PROC_REF(admin_set_blood))
.["Give Level"] = CALLBACK(src, PROC_REF(RankUp), TRUE)
if(bloodsucker_level_unspent >= 1)
.["Remove Level"] = CALLBACK(src, PROC_REF(RankDown))
@@ -187,13 +205,14 @@
///Called when you get the antag datum, called only ONCE per antagonist.
/datum/antagonist/bloodsucker/on_gain()
if(!owner?.current)
return ..()
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_VASSAL(owner.current)) // Vassals shouldnt be getting the same benefits as Bloodsuckers.
if(ventrue_sired) // sired bloodsuckers shouldnt be getting the same benefits as Bloodsuckers.
bloodsucker_level_unspent = 0
show_in_roundend = FALSE
else
@@ -213,13 +232,16 @@
/// 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, COMSIG_QDELETING))
free_all_vassals()
check_cancel_sunlight() //check if sunlight should end
if(!owner?.current)
return
if(ishuman(owner.current))
var/mob/living/carbon/human/user = owner.current
user?.dna?.species.regenerate_organs(user, null, TRUE)
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
free_all_vassals()
ventrue_sired = null
return ..()
/datum/antagonist/bloodsucker/on_body_transfer(mob/living/old_body, mob/living/new_body)
@@ -306,7 +328,7 @@
return finish_preview_icon(final_icon)
/datum/antagonist/bloodsucker/ui_static_data(mob/user)
var/list/data = list()
var/list/data = ability_ui_data(powers)
//we don't need to update this that much.
data["in_clan"] = !!my_clan
var/list/clan_data = list()
@@ -317,15 +339,6 @@
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["powers"] += list(power_data)
return data + ..()
/datum/antagonist/bloodsucker/ui_assets(mob/user)
@@ -343,8 +356,9 @@
if(my_clan)
return
assign_clan_and_bane()
if(ui.closing)
return
ui.send_full_update(force = TRUE)
return
/datum/antagonist/bloodsucker/roundend_report()
var/list/report = list()
@@ -403,10 +417,10 @@
var/obj/item/bodypart/user_left_arm = user.get_bodypart(BODY_ZONE_L_ARM)
var/obj/item/bodypart/user_right_arm = user.get_bodypart(BODY_ZONE_R_ARM)
user.dna?.remove_all_mutations()
user_left_arm.unarmed_damage_low += 1 //lowest possible punch damage - 0
user_left_arm.unarmed_damage_high += 1 //highest possible punch damage - 9
user_right_arm.unarmed_damage_low += 1 //lowest possible punch damage - 0
user_right_arm.unarmed_damage_high += 1 //highest possible punch damage - 9
user_left_arm.unarmed_damage_low += 1 //lowest possible punch damage - 6 now
user_left_arm.unarmed_damage_high += 1 //highest possible punch damage - 11
user_right_arm.unarmed_damage_low += 1 //lowest possible punch damage - 6
user_right_arm.unarmed_damage_high += 1 //highest possible punch damage - 11
user.mob_biotypes |= biotype
//Give Bloodsucker Traits
owner.current.add_traits(bloodsucker_traits, BLOODSUCKER_TRAIT)
@@ -417,7 +431,7 @@
if(HAS_TRAIT(owner.current, TRAIT_SKITTISH))
REMOVE_TRAIT(owner.current, TRAIT_SKITTISH, ROUNDSTART_TRAIT)
// Tongue & Language
owner.current.grant_language(/datum/language/vampiric, LANGUAGE_SPOKEN, LANGUAGE_MIND)
owner.current.grant_language(/datum/language/vampiric, ALL, LANGUAGE_MIND)
/// Clear Disabilities & Organs
heal_vampire_organs()
@@ -439,6 +453,8 @@
// Powers
for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
RemovePower(all_powers)
if(QDELETED(owner.current))
return
/// Stats
if(ishuman(owner.current))
var/mob/living/carbon/human/user = owner.current
@@ -451,6 +467,9 @@
if(right_arm)
right_arm.unarmed_damage_low = initial(right_arm.unarmed_damage_low)
right_arm.unarmed_damage_high = initial(right_arm.unarmed_damage_high)
var/obj/item/bodypart/chest/target_chest = owner.current.get_bodypart(BODY_ZONE_CHEST)
if(target_chest && target_chest.bodypart_flags & BODYPART_UNREMOVABLE)
target_chest.bodypart_flags &= ~BODYPART_UNREMOVABLE
// Remove all bloodsucker traits
owner.current.remove_traits(bloodsucker_traits, BLOODSUCKER_TRAIT)
// Language
@@ -497,7 +516,7 @@
conversion_objective.objective_name = "Optional Objective"
objectives += conversion_objective
if(2) // Heart Thief Objective
var/datum/objective/bloodsucker/heartthief/heartthief_objective = new
var/datum/objective/steal_n_of_type/hearts/heartthief_objective = new
heartthief_objective.owner = owner
heartthief_objective.objective_name = "Optional Objective"
objectives += heartthief_objective
@@ -25,7 +25,7 @@
* conversion_target - Person being vassalized
*/
/datum/antagonist/bloodsucker/proc/can_make_vassal(mob/living/conversion_target)
if(!iscarbon(conversion_target) || conversion_target.stat > UNCONSCIOUS)
if(!iscarbon(conversion_target) || (conversion_target.stat < CONSCIOUS))
return FALSE
// No Mind!
if(!conversion_target.mind)
@@ -53,7 +53,6 @@
/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)
@@ -37,45 +37,49 @@
/// 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)
vamprank_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(hud_text_color(), 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" \
)
update_sol_hud()
/datum/antagonist/bloodsucker/proc/hud_text_color()
return bloodsucker_blood_volume > BLOOD_VOLUME_SAFE ? "#FFDDDD" : "#FFAAAA"
/// Updated every time blood is changed by either
/datum/antagonist/bloodsucker/proc/update_blood_hud()
if(!blood_display)
return
blood_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(hud_text_color(), bloodsucker_blood_volume)
/datum/antagonist/bloodsucker/proc/update_sol_hud()
var/valuecolor = hud_text_color()
if(!sunlight_display)
return
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
@@ -8,20 +8,21 @@
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(exposed_mob)
if(!bloodsuckerdatum)
return ..()
bloodsuckerdatum.bloodsucker_blood_volume = min(bloodsuckerdatum.bloodsucker_blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM)
if(bloodsuckerdatum.bloodsucker_blood_volume > BLOOD_VOLUME_NORMAL)
return
bloodsuckerdatum.AdjustBloodVolume(round(reac_volume, 0.1))
/mob/living/carbon/transfer_blood_to(atom/movable/AM, amount, forced)
. = ..()
if(!mind)
return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(!bloodsuckerdatum)
return
bloodsuckerdatum.bloodsucker_blood_volume -= amount
bloodsuckerdatum.AdjustBloodVolume(-amount)
/// Prevents using a Memento Mori
// Prevents using a Memento Mori
// todo move this to it's own trait
/obj/item/clothing/neck/necklace/memento_mori/memento(mob/living/carbon/human/user)
if(IS_BLOODSUCKER(user))
to_chat(user, span_warning("The Memento notices your undead soul, and refuses to react.."))
@@ -35,6 +36,7 @@
return ..()
// Used when analyzing a Bloodsucker, Masquerade will hide brain traumas (Unless you're a Beefman)
/// todo move this to it's own trait or something
/mob/living/carbon/get_traumas()
if(!mind)
return ..()
@@ -52,7 +54,11 @@
if(bloodsuckerdatum)
. += ""
. += "Blood Drank: [bloodsuckerdatum.total_blood_drank]"
. += "Frenzy blood threshold: [bloodsuckerdatum.frenzy_enter_threshold()]"
. += "Maximum blood: [bloodsuckerdatum.max_blood_volume]"
if(bloodsuckerdatum.frenzied)
. += "Frenzy exit blood threshold: [bloodsuckerdatum.frenzy_exit_threshold()]"
else
. += "Frenzy blood threshold: [bloodsuckerdatum.frenzy_enter_threshold()]"
/datum/outfit/bloodsucker_outfit
name = "Bloodsucker outfit (Preview only)"
@@ -96,3 +102,16 @@
continue
amount += chosen_bodypart.burn_dam
return amount
/mob/living/brain/can_be_revived()
if(health <= HEALTH_THRESHOLD_DEAD)
return FALSE
if(IS_BLOODSUCKER(src))
return TRUE
. =..()
// prevents players being trapped in their brain, alive, yet limbless and voiceless
/obj/item/bodypart/head/drop_organs(mob/user, violent_removal)
var/obj/item/organ/internal/brain/brain = locate(/obj/item/organ/internal/brain) in src
brain.brainmob.death()
. = ..()
@@ -4,27 +4,27 @@
/// 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))
if(isbrain(owner.current))
INVOKE_ASYNC(src, PROC_REF(update_hud))
return
if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
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) // -.1 currently
if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_IMMOBILIZED) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
INVOKE_ASYNC(src, PROC_REF(AdjustBloodVolume), -BLOODSUCKER_PASSIVE_BLOOD_DRAIN) // -.1 currently
if(HandleHealing())
if((COOLDOWN_FINISHED(src, bloodsucker_spam_healing)) && bloodsucker_blood_volume > 0)
to_chat(owner.current, span_notice("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))
SEND_SIGNAL(src, COMSIG_BLOODSUCKER_ON_LIFETICK)
/datum/antagonist/bloodsucker/proc/on_death(mob/living/source, gibbed)
SIGNAL_HANDLER
@@ -39,16 +39,33 @@
/**
* ## BLOOD STUFF
*/
/datum/antagonist/bloodsucker/proc/AddBloodVolume(value)
/datum/antagonist/bloodsucker/proc/AdjustBloodVolume(value)
bloodsucker_blood_volume = clamp(bloodsucker_blood_volume + value, 0, max_blood_volume)
update_blood_hud()
/datum/antagonist/bloodsucker/proc/SetBloodVolume(value)
bloodsucker_blood_volume = clamp(value, 0, max_blood_volume)
update_blood_hud()
#define MASQUERADE /datum/action/cooldown/bloodsucker/masquerade
/datum/antagonist/bloodsucker/proc/AddHumanityLost(value)
// 100 humanity lost already causes you to frenzy at 25 + 100 * 10 = 1025 blood and deal 1 + 100 / 10 = 11 burn per second due to frenzy
if(humanity_lost >= 100)
to_chat(owner.current, span_warning("You hit the maximum amount of lost Humanty, you are far from Human."))
if(value == 0)
return
humanity_lost += value
to_chat(owner.current, span_warning("You feel as if you lost some of your humanity, you will now enter Frenzy at [FRENZY_THRESHOLD_ENTER + (humanity_lost * 10)] Blood."))
var/has_masq = is_path_in_list(MASQUERADE, powers)
if(value + humanity_lost >= HUMANITY_LOST_MAXIMUM)
if(has_masq)
RemovePowerByPath(MASQUERADE)
to_chat(owner.current, span_warning("You hit the maximum amount of lost Humanty, you are far from Human. You've forgotten how to pretend to be like your prey..."))
else
to_chat(owner.current, span_hypnophrase("The Beast, it yearns for Blood..."))
else if(!has_masq)
BuyPower(MASQUERADE)
to_chat(owner.current, span_hypnophrase("You've remembered, yet again, how it feels to live again."))
humanity_lost = clamp(value, 0, HUMANITY_LOST_MAXIMUM)
to_chat(owner.current, span_warning("You feel as if you [value < 0 ? "gained" : "lost" ] some of your humanity, you will now enter Frenzy at [FRENZY_THRESHOLD_ENTER + (humanity_lost * 10)] Blood."))
#undef MASQUERADE
/// mult: SILENT feed is 1/3 the amount
/datum/antagonist/bloodsucker/proc/handle_feeding(mob/living/carbon/target, mult=1, power_level)
@@ -74,7 +91,7 @@
blood_taken /= 2
//if (!iscarbon(target)) // Penalty for Animals (they're junk food)
// Apply to Volume
AddBloodVolume(blood_taken)
AdjustBloodVolume(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.
@@ -90,42 +107,49 @@
/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)))
if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)))
return FALSE
// Garlic in you? No healing for you!
if(HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT))
return FALSE
owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (actual_regen * 4) * mult) //adjustBrainLoss(-1 * (actual_regen * 4) * mult, 0)
owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * actual_regen * mult) //adjustBrainLoss(-1 * (actual_regen * 4) * mult, 0)
if(!iscarbon(owner.current)) // Damage Heal: Do I have damage to ANY bodypart?
return FALSE
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
// If you're a synth, you heal prosthetic damage.
var/bruteLoss = getBruteLoss()
var/bruteheal = min(bruteLoss, 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(amInCoffin && HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && (COOLDOWN_FINISHED(src, bloodsucker_spam_healing)))
to_chat(user, span_alert("You do not heal while your Masquerade ability is active."))
COOLDOWN_START(src, bloodsucker_spam_healing, BLOODSUCKER_SPAM_MASQUERADE)
return FALSE
fireheal = min(user.getFireLoss_nonProsthetic(), actual_regen)
fireheal = min(getFireLoss(), 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.extinguish_mob()
user.bodytemperature = user.get_body_temp_normal()
if(ishuman(user))
var/mob/living/carbon/human/humie = user
humie.set_coretemperature(humie.get_body_temp_normal(apply_change = FALSE))
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
else if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
var/fireloss = getFireLoss()
fireheal = min(fireloss, 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.
if((bruteheal + fireheal) && 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
user.adjustBruteLoss(-bruteheal * mult) // Heal BRUTE / BURN in random portions throughout the body.
user.adjustFireLoss(-fireheal * mult)
AdjustBloodVolume(((bruteheal * -0.5) + (fireheal * -1)) * costMult * mult) // Costs blood to heal
return TRUE
/datum/antagonist/bloodsucker/proc/check_limbs(costMult = 1)
@@ -136,9 +160,9 @@
return FALSE
for(var/missing_limb in missing) //Find ONE Limb and regenerate it.
user.regenerate_limb(missing_limb, FALSE)
AddBloodVolume(-limb_regen_cost)
AdjustBloodVolume(-limb_regen_cost)
var/obj/item/bodypart/missing_bodypart = user.get_bodypart(missing_limb) // 2) Limb returns Damaged
missing_bodypart.brute_dam = 60
missing_bodypart.brute_dam = missing_bodypart.max_damage
to_chat(user, span_notice("Your flesh knits as it regrows your [missing_bodypart]!"))
playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE)
return TRUE
@@ -153,18 +177,22 @@
*
* This is called on Bloodsucker's Assign, and when they end Torpor.
*/
/datum/antagonist/bloodsucker/proc/heal_vampire_organs()
/// TODO: Separate this into smaller functions
/datum/antagonist/bloodsucker/proc/heal_vampire_organs(regenerate_heart = FALSE)
var/mob/living/carbon/bloodsuckeruser = owner.current
// please don't poison or asphyxiate the immune
bloodsuckeruser.setToxLoss(0, forced = TRUE)
bloodsuckeruser.setOxyLoss(0, forced = TRUE)
if(!bloodsuckeruser)
return
if(HAS_TRAIT_FROM_ONLY(bloodsuckeruser, TRAIT_HUSK, CHANGELING_DRAIN) || bloodsuckeruser.has_status_effect(/datum/status_effect/gutted))
to_chat(bloodsuckeruser, span_danger("Your immortal blood has healed your body from near-irrecoverable damage, but has used nearly all of your blood in doing so!"))
AddHumanityLost(2)
SetBloodVolume(min(bloodsucker_blood_volume, frenzy_enter_threshold() * 2))
bloodsuckeruser.cure_husk(CHANGELING_DRAIN)
bloodsuckeruser.cure_husk(BURN)
bloodsuckeruser.regenerate_organs(regenerate_existing = FALSE)
for(var/obj/item/organ/organ as anything in bloodsuckeruser.organs)
organ.set_organ_damage(0)
if(regenerate_heart || bloodsuckeruser.get_organ_slot(ORGAN_SLOT_HEART))
bloodsuckeruser.regenerate_organs(regenerate_existing = FALSE)
if(!HAS_TRAIT(bloodsuckeruser, TRAIT_MASQUERADE))
var/obj/item/organ/internal/heart/current_heart = bloodsuckeruser.get_organ_slot(ORGAN_SLOT_HEART)
current_heart.Stop()
@@ -174,6 +202,10 @@
current_eyes.color_cutoffs = BLOODSUCKER_SIGHT_COLOR_CUTOFF
current_eyes.sight_flags = SEE_MOBS
bloodsuckeruser.update_sight()
/// Disable gutting for the chest
var/obj/item/bodypart/chest/target_chest = bloodsuckeruser.get_bodypart(BODY_ZONE_CHEST)
if(target_chest && !(target_chest.bodypart_flags & BODYPART_UNREMOVABLE))
target_chest.bodypart_flags |= BODYPART_UNREMOVABLE
// Sometimes bloodsuckers can get into a loop of reviving and dying, if they somehow get a new body without being revived.
if(_listen_lookup?[COMSIG_BLOODSUCKER_ON_LIFETICK] || bloodsuckeruser._listen_lookup?[COMSIG_LIVING_REVIVE])
on_revive()
@@ -184,7 +216,8 @@
// From [powers/panacea.dm]
var/list/bad_organs = list(
bloodsuckeruser.get_organ_by_type(/obj/item/organ/internal/body_egg),
bloodsuckeruser.get_organ_by_type(/obj/item/organ/internal/zombie_infection))
bloodsuckeruser.get_organ_by_type(/obj/item/organ/internal/zombie_infection)
)
for(var/tumors in bad_organs)
var/obj/item/organ/yucky_organs = tumors
if(!istype(yucky_organs))
@@ -214,9 +247,14 @@
FinalDeath()
return
// Temporary Death? Convert to Torpor.
if(HAS_TRAIT(owner.current, TRAIT_NODEATH))
if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT) || isbrain(owner.current))
return
check_begin_torpor(TRUE)
// Won't torpor without vital organs, as this means they'd revive without a heart
if(ishuman(owner.current))
var/mob/living/carbon/human/humie = owner.current
if(humie.dna.species.mutantheart && !owner.current.get_organ_slot(ORGAN_SLOT_HEART))
return
check_begin_torpor(TORPOR_SKIP_CHECK_ALL)
/datum/antagonist/bloodsucker/proc/HandleStarving() // I am thirsty for blood!
// Nutrition - The amount of blood is how full we are.
@@ -226,11 +264,11 @@
// 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.
var/datum/status_effect/frenzy/status_effect = owner.current.has_status_effect(/datum/status_effect/frenzy)
if(bloodsucker_blood_volume >= frenzy_exit_threshold() && frenzied && status_effect.duration == -1)
if(bloodsucker_blood_volume >= frenzy_exit_threshold() && frenzied && status_effect?.duration == -1)
status_effect.duration = world.time + 10 SECONDS
owner.current.balloon_alert(owner.current, "Frenzy ends in 10 seconds!")
// 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))
if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD && prob(0.5) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
owner.current.set_timed_status_effect(3 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE)
// BLOOD_VOLUME_SURVIVE: [122] - Blur Vision
if(bloodsucker_blood_volume < BLOOD_VOLUME_SURVIVE)
@@ -267,10 +305,25 @@
owner.current.blood_volume = bloodsucker_blood_volume
/// Turns the bloodsucker into a wacky talking head.
/datum/antagonist/bloodsucker/proc/talking_head()
var/mob/living/poor_fucker = owner.current
// Don't do anything if we're not actually inside a brain and a head
if(!is_head(poor_fucker) || poor_fucker.stat != DEAD || !poor_fucker.can_be_revived())
return
// Ensure that the HUD updates (We might have lost the heart at some point)
RegisterSignal(poor_fucker, COMSIG_LIVING_LIFE, PROC_REF(LifeTick), TRUE)
poor_fucker.revive()
poor_fucker.stat = CONSCIOUS
to_chat(poor_fucker, span_warning("Your immortal [pick(list("blood", "curse"))] keeps your head alive! Though... what will you do now?"))
// No lungs to speak, let's make it spooky
poor_fucker.speech_span = SPAN_PAPYRUS
/// Gibs the Bloodsucker, roundremoving them.
/datum/antagonist/bloodsucker/proc/FinalDeath()
/datum/antagonist/bloodsucker/proc/FinalDeath(check_organs = FALSE)
SIGNAL_HANDLER
// If we have no body, end here.
if(!owner.current)
if(!owner.current || isbrain(owner.current))
return
UnregisterSignal(src, list(
COMSIG_BLOODSUCKER_ON_LIFETICK,
@@ -36,7 +36,7 @@
)
///Returns a Title for the Bloodsucker.
/datum/antagonist/bloodsucker/proc/SelectTitle(am_fledgling = 0, forced = FALSE)
/datum/antagonist/bloodsucker/proc/SelectTitle(am_fledgling = FALSE, forced = FALSE)
// Already have Title
if(!forced && bloodsucker_title != null)
return
@@ -165,35 +165,23 @@
// NOTE: Look up /steal in objective.dm for inspiration.
/// Steal hearts. You just really wanna have some hearts.
/datum/objective/bloodsucker/heartthief
/datum/objective/steal_n_of_type/hearts
martyr_compatible = TRUE
name = "heartthief"
wanted_items = (/obj/item/organ/internal/heart)
// GENERATE!
/datum/objective/bloodsucker/heartthief/New()
target_amount = rand(2,3)
/datum/objective/steal_n_of_type/hearts/New()
amount = rand(2, 3)
explanation_text = "Steal and keep [amount] organic hearts. Must be obtained from non-monkeys. Examine hearts thoroughly to see if they are valid."
update_explanation_text()
..()
// EXPLANATION
/datum/objective/bloodsucker/heartthief/update_explanation_text()
/datum/objective/steal_n_of_type/hearts/check_if_valid_item(obj/item/organ/internal/heart/current_item)
. = ..()
explanation_text = "Steal and keep [target_amount] organic heart\s."
// WIN CONDITIONS?
/datum/objective/bloodsucker/heartthief/check_completion()
if(!owner.current)
if(current_item.type == /obj/item/organ/internal/heart/monkey || IS_ROBOTIC_ORGAN(current_item))
return FALSE
var/list/all_items = owner.current.get_all_contents()
var/heart_count = 0
for(var/obj/item/organ/internal/heart/current_hearts in all_items)
if(IS_ROBOTIC_ORGAN(current_hearts)) // No robo-hearts allowed
continue
heart_count++
if(heart_count >= target_amount)
return TRUE
return FALSE
//////////////////////////////////////////////////////////////////////////////////////
///Eat blood from a lot of people
@@ -202,7 +190,7 @@
// GENERATE!
/datum/objective/bloodsucker/gourmand/New()
target_amount = rand(450,650)
target_amount = rand(800, 3 * BLOOD_VOLUME_NORMAL)
..()
// EXPLANATION
@@ -285,10 +273,9 @@
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
for(var/datum/antagonist/bloodsucker/sired_vamp in GLOB.antagonists)
if(sired_vamp.ventrue_sired == bloodsuckerdatum)
return TRUE
return FALSE
@@ -7,6 +7,10 @@
if(vamp_examine)
examine_text += vamp_examine
/datum/antagonist/bloodsucker/proc/BuyPowers(powers = list())
for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
BuyPower(power)
///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)
@@ -25,6 +29,11 @@
powers -= power
power.Remove(owner.current)
/datum/antagonist/bloodsucker/proc/RemovePowerByPath(datum/action/cooldown/bloodsucker/power_to_remove)
for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
if(power?.type == power_to_remove)
RemovePower(power)
///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)
@@ -43,6 +52,8 @@
return
to_chat(owner.current, span_cultboldtalic("You have re-entered the Masquerade."))
broke_masquerade = FALSE
antag_hud_name = "bloodsucker"
add_team_hud(owner.current)
/datum/antagonist/bloodsucker/proc/give_masquerade_infraction()
if(broke_masquerade)
@@ -66,12 +77,6 @@
if(bloodsucker_level_unspent >= 2)
to_chat(owner, span_announce("Bloodsucker Tip: If you cannot find or steal a coffin to use, you can build one from wood or metal."))
return
if(max_vassals() >= 1 && !(/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! Vassalization unlocked!")
SpendRank()
/datum/antagonist/bloodsucker/proc/RankDown()
@@ -94,7 +99,7 @@
///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(forced || ((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)))
if(power.active)
power.DeactivatePower()
@@ -124,16 +129,16 @@
* viewer - The person examining.
*/
/datum/antagonist/bloodsucker/proc/return_vamp_examine(mob/living/viewer)
if(!viewer.mind)
if(!viewer.mind && !isobserver(viewer))
return FALSE
// Viewer is Target's Vassal?
if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in vassals)
if(!isobserver(viewer) && (viewer.mind.has_antag_datum(/datum/antagonist/vassal) in vassals))
var/returnString = "\[<span class='warning'><EM>This is your Master!</EM></span>\]"
var/returnIcon = "[icon2html('modular_zubbers/icons/misc/language.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(!isobserver(viewer) && !viewer.mind.has_antag_datum((/datum/antagonist/bloodsucker)) && !(viewer in vassals))
if(!(HAS_TRAIT(viewer.mind, TRAIT_BLOODSUCKER_HUNTER) && broke_masquerade))
return FALSE
// Default String
@@ -159,3 +164,88 @@
/datum/antagonist/bloodsucker/proc/frenzy_exit_threshold()
return FRENZY_THRESHOLD_EXIT + (humanity_lost * 10)
/datum/antagonist/bloodsucker/proc/add_signals_to_heart(mob/living/carbon/human/current_mob)
if(heart?.resolve())
remove_signals_from_heart(current_mob)
var/organ = current_mob.get_organ_slot(ORGAN_SLOT_HEART)
heart = WEAKREF(organ)
RegisterSignal(organ, COMSIG_ORGAN_REMOVED, PROC_REF(on_organ_removal))
RegisterSignal(organ, COMSIG_ORGAN_BEING_REPLACED, PROC_REF(before_organ_replace))
/datum/antagonist/bloodsucker/proc/remove_signals_from_heart(mob/living/carbon/human/current_mob)
var/organ = heart.resolve()
if(!organ)
return
UnregisterSignal(organ, COMSIG_ORGAN_REMOVED)
UnregisterSignal(organ, COMSIG_ORGAN_BEING_REPLACED)
organ = null
/datum/antagonist/bloodsucker/proc/on_organ_removal(obj/item/organ/organ, mob/living/carbon/old_owner)
SIGNAL_HANDLER
if(old_owner.get_organ_slot(ORGAN_SLOT_HEART) || organ.slot != ORGAN_SLOT_HEART || !old_owner.dna.species.mutantheart)
return
remove_signals_from_heart(old_owner)
// You don't run bloodsucker life without a heart or brain
RegisterSignal(old_owner, COMSIG_ENTER_COFFIN, PROC_REF(regain_heart))
UnregisterSignal(old_owner, COMSIG_LIVING_LIFE)
DisableAllPowers(TRUE)
if(HAS_TRAIT_FROM_ONLY(old_owner, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
torpor_end(TRUE)
to_chat(old_owner, span_userdanger("You have lost your [organ.slot]!"))
to_chat(old_owner, span_warning("This means you will no longer enter torpor nor revive from death, and you will no longer heal any damage, nor can you use your abilities."))
/datum/antagonist/bloodsucker/proc/on_organ_gain(mob/living/carbon/human/current_mob, obj/item/organ/replacement)
SIGNAL_HANDLER
if(replacement.slot != ORGAN_SLOT_HEART)
return
// Shit might get really fucked up. Let's try to fix things if it does
if(current_mob != owner.current)
UnregisterSignal(current_mob, COMSIG_CARBON_GAIN_ORGAN)
RegisterSignal(owner.current, COMSIG_CARBON_GAIN_ORGAN)
add_signals_to_heart(owner.current)
RegisterSignal(owner.current, COMSIG_LIVING_LIFE, PROC_REF(LifeTick), TRUE)
CRASH("What the fuck, somehow called on_organ_gain signal on [src] without current_mob being the antag datum's owner?")
UnregisterSignal(current_mob, COMSIG_ENTER_COFFIN)
RegisterSignal(current_mob, COMSIG_LIVING_LIFE, PROC_REF(LifeTick))
add_signals_to_heart(current_mob)
/// This handles regen_organs replacing organs, without this the bloodsucker would die for a moment due to their heart being removed for a moment
/datum/antagonist/bloodsucker/proc/before_organ_replace(obj/item/organ/old_organ, obj/item/organ/new_organ)
SIGNAL_HANDLER
if(new_organ.slot != ORGAN_SLOT_HEART)
return
remove_signals_from_heart(owner.current)
/// checks if we're a brainmob inside a brain & the brain is inside a head
/datum/antagonist/bloodsucker/proc/is_head(mob/living/poor_fucker)
if(!istype(poor_fucker?.loc, /obj/item/organ/internal/brain))
return
var/obj/brain = poor_fucker.loc
if(!istype(brain?.loc, /obj/item/bodypart/head))
return
return brain.loc
// helper procs for damage checking, just in case a synth becomes one, let's them heal thesmelves
/datum/antagonist/bloodsucker/proc/getBruteLoss()
var/mob/living/carbon/human/humie = owner.current
return issynthetic(humie) ? humie.getBruteLoss() : humie.getBruteLoss_nonProsthetic()
/datum/antagonist/bloodsucker/proc/getFireLoss()
var/mob/living/carbon/human/humie = owner.current
return issynthetic(humie) ? humie.getFireLoss() : humie.getFireLoss_nonProsthetic()
/datum/antagonist/bloodsucker/proc/admin_set_blood(mob/admin)
var/blood = tgui_input_number(admin, "What blood level to set [owner.current]'s to?", "Blood is life.", floor(bloodsucker_blood_volume), max_blood_volume, 0)
// 0 input is falsey
if(blood == null)
return
SetBloodVolume(blood)
update_hud()
/datum/antagonist/bloodsucker/proc/regain_heart(mob/coffin_dweller, obj/structure/closet/crate/coffin/coffin, mob/user)
SIGNAL_HANDLER
var/obj/item/organ/heart = locate(/obj/item/organ/internal/heart) in coffin.contents
if(heart && !coffin_dweller.get_organ_slot(ORGAN_SLOT_HEART))
to_chat(span_warning("You have regained your heart!"))
heart.Insert(coffin_dweller)
@@ -41,7 +41,7 @@
/// 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)
if(!owner || !owner.current || isbrain(owner.current))
return
if(!istype(owner.current.loc, /obj/structure))
@@ -64,10 +64,10 @@
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, span_userdanger("You are staked! Remove the offending weapon from your heart before sleeping."))
to_chat(owner.current, span_userdanger("You are staked you will keep burning until it is removed! 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)
if(!HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
check_begin_torpor(TORPOR_SKIP_CHECK_ALL)
owner.current.add_mood_event("vampsleep", /datum/mood_event/coffinsleep)
return
@@ -112,58 +112,68 @@
* - 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)
/datum/antagonist/bloodsucker/proc/check_begin_torpor(SkipChecks = NONE)
var/mob/living/carbon/user = owner.current
/// Are we entering Torpor via Sol/Death? Then entering it isnt optional!
if(SkipChecks)
to_chat(user, span_danger("Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor."))
torpor_begin()
if(SkipChecks & TORPOR_SKIP_CHECK_ALL)
if(COOLDOWN_FINISHED(src, bloodsucker_spam_torpor))
to_chat(user, span_danger("Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor."))
torpor_begin(TRUE)
return
/// Prevent Torpor whilst frenzied.
if(frenzied || (IS_DEAD_OR_INCAP(user) && bloodsucker_blood_volume == 0))
if(!(SkipChecks & TORPOR_SKIP_CHECK_FRENZY) && (frenzied || (IS_DEAD_OR_INCAP(user) && bloodsucker_blood_volume == 0)))
to_chat(user, span_userdanger("Your frenzy prevents you from entering torpor!"))
return
var/total_brute = user.getBruteLoss_nonProsthetic()
var/total_burn = user.getFireLoss_nonProsthetic()
var/total_damage = total_brute + total_burn
// sometimes you might incur these damage types when you really, should not, important to check for it here so we can heal it later
var/total_damage = getBruteLoss() + getFireLoss() + user.getToxLoss() + user.getOxyLoss()
/// 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))
if(SkipChecks & TORPOR_SKIP_CHECK_DAMAGE || !SSsunlight.sunlight_active && total_damage >= 10 && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
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_brute = getBruteLoss()
var/total_burn = getFireLoss()
// for waking up we ignore all other damage types so we don't get stuck
var/total_damage = total_brute + total_burn
if(bloodsucker_blood_volume == 0 || owner.current.am_staked() || HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT))
torpor_end()
if(total_burn >= user.maxHealth * 2)
return FALSE
if(SSsunlight.sunlight_active)
return FALSE
if(bloodsucker_blood_volume == 0 || owner.current.am_staked() || HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT))
// If you're frenzying, you need a bit more health to actually have a chance to do something
if(frenzied && total_damage >= user.maxHealth)
return FALSE
torpor_end()
// 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)
if(total_damage <= 10)
torpor_end()
return TRUE
/datum/antagonist/bloodsucker/proc/torpor_begin()
to_chat(owner.current, span_notice("You enter the horrible slumber of deathless Torpor. You will heal until you are renewed."))
/datum/antagonist/bloodsucker/proc/torpor_begin(silent = FALSE)
// slow down bucko
if(!COOLDOWN_FINISHED(src, bloodsucker_spam_torpor))
return
if(!silent)
to_chat(owner.current, span_notice("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.
owner.current.add_traits(list(TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_DEATHCOMA, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE), BLOODSUCKER_TRAIT)
owner.current.set_timed_status_effect(0 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE)
owner.current.do_jitter_animation(2)
// Disable ALL Powers
DisableAllPowers()
/datum/antagonist/bloodsucker/proc/torpor_end()
owner.current.grab_ghost()
to_chat(owner.current, span_warning("You have recovered from Torpor."))
/datum/antagonist/bloodsucker/proc/torpor_end(quiet = FALSE)
if(quiet)
owner.current.grab_ghost()
to_chat(owner.current, span_warning("You have recovered from Torpor."))
owner.current.remove_traits(list(TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_DEATHCOMA, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE), BLOODSUCKER_TRAIT)
if(!HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
if(!HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT))
ADD_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT)
heal_vampire_organs()
SEND_SIGNAL(src, BLOODSUCKER_EXIT_TORPOR)
@@ -38,7 +38,8 @@
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_MAKE_FAVORITE, PROC_REF(favorite_vassal_gain))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_LOOSE_FAVORITE, PROC_REF(favorite_vassal_loss))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, PROC_REF(on_vassal_made))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_EXIT_TORPOR, PROC_REF(on_exit_torpor))
@@ -205,7 +206,7 @@
if(cost_rank)
bloodsuckerdatum.bloodsucker_level_unspent--
if(blood_cost)
bloodsuckerdatum.AddBloodVolume(-blood_cost)
bloodsuckerdatum.AdjustBloodVolume(-blood_cost)
// Ranked up enough to get your true Reputation?
if(bloodsuckerdatum.bloodsucker_level == BLOODSUCKER_HIGH_LEVEL)
@@ -214,10 +215,19 @@
to_chat(bloodsuckerdatum.owner.current, span_notice("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 strength, 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()
bloodsuckerdatum.update_static_data_for_all_viewers()
// unlock vassalizing if we have a vassal slot
if(bloodsuckerdatum.max_vassals() >= 1 && !(/datum/crafting_recipe/vassalrack in bloodsuckerdatum.owner?.learned_recipes))
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/vassalrack)
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/candelabrum)
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/bloodthrone)
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin)
bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "new recipes learned! Vassalization unlocked!")
/**
* Called when we are trying to turn someone into a Favorite Vassal
@@ -258,7 +268,7 @@
bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "Out of Special Vassal slots!")
return
to_chat(bloodsuckerdatum.owner.current, span_notice("You can change who this Vassal is, who are they to you?"))
to_chat(bloodsuckerdatum.owner.current, span_notice("You can change who this Vassal is, who are they to you? This will cost [SPECIAL_VASSAL_COST] blood."))
var/vassal_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, radial_display)
if(!vassal_response)
return
@@ -266,7 +276,7 @@
if(QDELETED(src) || QDELETED(bloodsuckerdatum.owner.current) || QDELETED(vassaldatum.owner.current))
return FALSE
vassaldatum.make_special(vassal_response)
bloodsuckerdatum.bloodsucker_blood_volume -= 150
bloodsuckerdatum.AdjustBloodVolume(-SPECIAL_VASSAL_COST)
return TRUE
/**
@@ -275,6 +285,9 @@
* 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)
/datum/bloodsucker_clan/proc/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
SIGNAL_HANDLER
vassaldatum.BuyPower(/datum/action/cooldown/bloodsucker/targeted/brawn)
/datum/bloodsucker_clan/proc/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
SIGNAL_HANDLER
@@ -52,7 +52,7 @@
var/message = pick(strings("malkavian_revelations.json", "revelations", "modular_zubbers/strings/bloodsuckers"))
INVOKE_ASYNC(bloodsuckerdatum.owner.current, TYPE_PROC_REF(/atom/movable, say), message, forced = CLAN_MALKAVIAN)
/datum/bloodsucker_clan/malkavian/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
/datum/bloodsucker_clan/malkavian/favorite_vassal_gain(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)
@@ -61,8 +61,18 @@
psychotic_brawling.teach(vassaldatum.owner.current, TRUE)
to_chat(vassaldatum.owner.current, span_notice("Additionally, you now suffer the same fate as your Master, while also gaining the ability to tap into the madness when fighting."))
/datum/bloodsucker_clan/malkavian/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
var/mob/living/carbon/carbonowner = vassaldatum.owner.current
if(istype(carbonowner))
carbonowner.cure_trauma_type(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE)
carbonowner.cure_trauma_type(/datum/brain_trauma/special/bluespace_prophet/phobetor, TRAUMA_RESILIENCE_ABSOLUTE)
if(!istype(/datum/martial_art/psychotic_brawling, vassaldatum.owner.martial_art))
return
var/datum/martial_art/psychotic_brawling/psychotic_brawling = vassaldatum.owner.martial_art
psychotic_brawling.remove(vassaldatum.owner.current)
/datum/bloodsucker_clan/malkavian/on_exit_torpor(datum/antagonist/bloodsucker/source)
var/mob/living/carbon/carbonowner = bloodsuckerdatum.owner.current
var/mob/living/carbon/carbonowner = bloodsuckerdatum.owner.martial_art
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)
@@ -33,6 +33,12 @@
if(!HAS_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NOBLOOD))
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.add_traits(list(TRAIT_VENTCRAWLER_NUDE, TRAIT_DISFIGURED, TRAIT_TRUE_NIGHT_VISION, TRAIT_KNOW_ENGI_WIRES, TRAIT_SILENT_FOOTSTEPS), BLOODSUCKER_TRAIT)
/datum/bloodsucker_clan/nosferatu/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
var/list/traits_to_add = list(TRAIT_VENTCRAWLER_NUDE, TRAIT_DISFIGURED, TRAIT_TRUE_NIGHT_VISION, TRAIT_KNOW_ENGI_WIRES, TRAIT_SILENT_FOOTSTEPS)
vassaldatum.owner.current.add_traits(traits_to_add, VASSAL_TRAIT)
vassaldatum.traits += traits_to_add
vassaldatum.owner.current.update_sight()
to_chat(vassaldatum.owner.current, span_notice("Additionally, you can now ventcrawl while naked, and are permanently disfigured. You also have night vision, know how which wires to cut, and have silent footsteps."))
/datum/bloodsucker_clan/nosferatu/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
vassaldatum.owner.current.update_sight()
@@ -45,7 +45,7 @@
to_chat(bloodsuckerdatum.owner.current, span_notice("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)
var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Spend [round(blood_cost, 1)] blood to upgrade a power.", "Your Blood Thickens...", options)
// Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0)
return
@@ -72,10 +72,14 @@
finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
/datum/bloodsucker_clan/tremere/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
/datum/bloodsucker_clan/tremere/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
var/datum/action/cooldown/spell/shapeshift/bat/batform = new(vassaldatum.owner || vassaldatum.owner.current)
batform.Grant(vassaldatum.owner.current)
/datum/bloodsucker_clan/tremere/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
var/datum/action/cooldown/spell/shapeshift/bat/batform = locate() in vassaldatum.owner.current.actions
batform.Remove(vassaldatum.owner.current)
/datum/bloodsucker_clan/tremere/on_vassal_made(datum/antagonist/bloodsucker/source, mob/living/user, mob/living/target)
. = ..()
to_chat(bloodsuckerdatum.owner.current, span_danger("You have now gained an additional Rank to spend!"))
@@ -1,12 +1,12 @@
///The maximum level a Ventrue Bloodsucker can be, before they have to level up their vassal instead.
#define VENTRUE_MAX_LEVEL 3
#define VENTRUE_MAX_POWERS 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\
You may have up to %MAX_POWERS% powers, 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. \n\
Once you finish your embracing, the newly sired vampire will become just a vassal, and you'll be able to sire another bloodsucker."
clan_objective = /datum/objective/bloodsucker/embrace
@@ -17,19 +17,31 @@
/datum/bloodsucker_clan/ventrue/New(datum/antagonist/bloodsucker/owner_datum)
. = ..()
description = replacetext(description, "%MAX_LEVEL%", VENTRUE_MAX_LEVEL)
description = replacetext(description, "%MAX_POWERS%", VENTRUE_MAX_POWERS)
/datum/bloodsucker_clan/ventrue/proc/has_enough_abilities()
var/power_count = 0
for(var/datum/action/cooldown/bloodsucker/power in bloodsuckerdatum.powers)
if(!(power.purchase_flags & BLOODSUCKER_DEFAULT_POWER))
power_count++
if(power_count >= VENTRUE_MAX_POWERS)
return TRUE
return FALSE
/datum/bloodsucker_clan/ventrue/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
if(bloodsuckerdatum.bloodsucker_level < VENTRUE_MAX_LEVEL)
if(!target && !has_enough_abilities())
return ..()
else if(!target)
to_chat(bloodsuckerdatum.owner.current, span_danger("You can only level up to Level [VENTRUE_MAX_LEVEL], anything further will be ranks to spend on your Favorite Vassal through a Persuasion Rack."))
to_chat(bloodsuckerdatum.owner.current, span_danger("You can only have up to [VENTRUE_MAX_POWERS] powers, anything further will be ranks to spend on your Favorite Vassal through a Persuasion Rack."))
return FALSE
var/datum/antagonist/vassal/favorite/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal/favorite)
var/datum/antagonist/vassal/non_favorite = target.mind.has_antag_datum(/datum/antagonist/vassal)
if(!vassaldatum && vassaldatum.master != bloodsuckerdatum.owner.current || !IS_BLOODSUCKER(target) && !non_favorite && vassaldatum.master != bloodsuckerdatum.owner.current)
target.balloon_alert(target, "is not a sired bloodsucker nor your favorite vassal!")
return FALSE
if(!vassaldatum.owner.current.mind)
target.balloon_alert(target, "vassal must be awake!")
return FALSE
// Purchase Power Prompt
var/list/options = list()
for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
@@ -67,18 +79,19 @@
return
vassaldatum.vassal_level++
finish_spend_rank(vassaldatum, cost_rank, blood_cost)
var/traits_to_add = list()
switch(vassaldatum.vassal_level)
if(1)
target.add_traits(list(TRAIT_COLDBLOODED, TRAIT_NOBREATH, TRAIT_AGEUSIA), BLOODSUCKER_TRAIT)
traits_to_add = list(TRAIT_COLDBLOODED, TRAIT_NOBREATH, TRAIT_AGEUSIA)
to_chat(target, span_notice("Your blood begins to feel cold, and as a mote of ash lands upon your tongue, you stop breathing..."))
if(2)
target.add_traits(list(TRAIT_NOCRITDAMAGE, TRAIT_NOSOFTCRIT, TRAIT_SLEEPIMMUNE, TRAIT_VIRUSIMMUNE), BLOODSUCKER_TRAIT)
traits_to_add = list(TRAIT_NOCRITDAMAGE, TRAIT_NOSOFTCRIT, TRAIT_SLEEPIMMUNE, TRAIT_VIRUSIMMUNE)
to_chat(target, span_notice("You feel your Master's blood reinforce you, strengthening you up."))
if(ishuman(target))
var/mob/living/carbon/human/human_target = target
human_target.skin_tone = "albino"
if(3)
target.add_traits(list(TRAIT_NOHARDCRIT, TRAIT_HARDLY_WOUNDED), BLOODSUCKER_TRAIT)
traits_to_add = list(TRAIT_NOHARDCRIT, TRAIT_HARDLY_WOUNDED)
to_chat(target, span_notice("You feel yourself able to take cuts and stabbings like it's nothing."))
if(4 to INFINITY)
var/datum/antagonist/bloodsucker/bloodsucker_target = target.mind.has_antag_datum(/datum/antagonist/bloodsucker)
@@ -89,18 +102,18 @@
var/powers_to_transfer = list()
// Get rid of the favorite datum and replace with a normal vassal datum
if(target.mind.has_antag_datum(/datum/antagonist/vassal/favorite))
var/datum/antagonist/vassal/new_antag_datum = new(target.mind)
powers_to_transfer = vassaldatum.bloodsucker_powers.Copy()
vassaldatum.bloodsucker_powers.Cut(powers_to_transfer)
for(var/datum/power as anything in vassaldatum.bloodsucker_powers)
powers_to_transfer += power.type
target.mind.remove_antag_datum(/datum/antagonist/vassal/favorite)
new_antag_datum.master = vassaldatum.master
new_antag_datum.silent = TRUE
target.mind.add_antag_datum(new_antag_datum)
new_antag_datum.remove_powers(new_antag_datum.powers)
bloodsucker_target = target.mind.add_antag_datum(/datum/antagonist/bloodsucker)
bloodsucker_target?.powers += powers_to_transfer
var/datum/antagonist/bloodsucker/vamp = new()
vamp.ventrue_sired = bloodsuckerdatum
bloodsucker_target = target.mind.add_antag_datum(vamp)
bloodsucker_target.BuyPowers(powers_to_transfer)
// Check for the recuperate power and remove it if they have it
bloodsuckerdatum.owner.current.add_mood_event("madevamp", /datum/mood_event/madevamp)
if(vassaldatum && QDELETED(vassaldatum) && length(traits_to_add))
target.add_traits(traits_to_add, VASSAL_TRAIT)
vassaldatum.traits += traits_to_add
/datum/bloodsucker_clan/ventrue/proc/finish_spend_rank(datum/antagonist/vassal/vassaldatum, cost_rank, blood_cost)
finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
@@ -112,9 +125,6 @@
return TRUE
if(!istype(vassaldatum))
return FALSE
if(bloodsuckerdatum.bloodsucker_level < VENTRUE_MAX_LEVEL)
to_chat(bloodsuckerdatum.owner.current, span_danger("You are too low level to Rank up [vassaldatum.owner.current] You must at least be level [VENTRUE_MAX_LEVEL]."))
return FALSE
if(!bloodsuckerdatum.bloodsucker_level_unspent <= 0)
bloodsuckerdatum.SpendRank(vassaldatum.owner.current)
return TRUE
@@ -132,9 +142,9 @@
to_chat(bloodsuckerdatum.owner.current, span_danger("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, span_announce("* Bloodsucker Tip: You can now upgrade your Favorite Vassal by buckling them onto a persuasion rack!"))
/datum/bloodsucker_clan/ventrue/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
to_chat(source.owner.current, span_announce("* Bloodsucker Tip: You can now upgrade your Favorite Vassal by buckling them onto a persuasion rack!"))
vassaldatum.BuyPower(/datum/action/cooldown/bloodsucker/distress)
#undef BLOODSUCKER_BLOOD_RANKUP_COST
#undef VENTRUE_MAX_LEVEL
#undef VENTRUE_MAX_POWERS
@@ -17,7 +17,6 @@
base_background_icon_state = "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
@@ -25,14 +24,14 @@
/// 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
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|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
///Can increase to yield new capabilities - Each Power ranks up each Rank, with the oldest power being the highest rank.
var/level_current = 0
///The cost to ACTIVATE this Power
var/bloodcost = 0
@@ -102,6 +101,13 @@
///Called when the Power is upgraded.
/datum/action/cooldown/bloodsucker/proc/upgrade_power()
level_current++
build_all_button_icons(UPDATE_BUTTON_NAME)
// Put desc that you want to update every time build_all_button_icons is called here
/datum/action/cooldown/bloodsucker/update_button_name(atom/movable/screen/movable/action_button/button, force)
. = ..()
if((purchase_flags & BLOODSUCKER_CAN_BUY) || (purchase_flags & TREMERE_CAN_BUY))
button.desc += "<br><br><b>LEVEL:</b> [level_current]"
///Checks if the Power is available to use.
/datum/action/cooldown/bloodsucker/proc/can_use(mob/living/carbon/user, trigger_flags)
@@ -109,6 +115,12 @@
return FALSE
if(!isliving(user))
return FALSE
if(bloodsuckerdatum_power && !bloodsuckerdatum_power.heart?.resolve())
to_chat(user, span_warning("To channel your powers you need a heart!"))
return FALSE
if(isbrain(user))
to_chat(user, span_warning("What are you going to do, jump on someone and suck their blood? You're just a head."))
return FALSE
// Torpor?
if((check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(user, TRAIT_NODEATH))
to_chat(user, span_warning("Not while you're in Torpor."))
@@ -121,11 +133,11 @@
to_chat(user, span_warning("You cannot use powers while in a Frenzy!"))
return FALSE
// Stake?
if((check_flags & BP_CANT_USE_WHILE_STAKED) && user.am_staked())
if(!(check_flags & BP_CAN_USE_WHILE_STAKED) && user.am_staked())
to_chat(user, span_warning("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)
if((check_flags & BP_CANT_USE_WHILE_UNCONSCIOUS) && user.stat >= UNCONSCIOUS)
to_chat(user, span_warning("You can't do this while you are unconcious!"))
return FALSE
// Incapacitated?
@@ -133,13 +145,16 @@
to_chat(user, span_warning("Not while you're incapacitated!"))
return FALSE
// Constant Cost (out of blood)
if(constant_bloodcost > 0 && bloodsuckerdatum_power?.bloodsucker_blood_volume <= 0)
if(constant_bloodcost > 0 && !can_pay_blood(user))
to_chat(user, span_warning("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()
/datum/action/cooldown/bloodsucker/StartCooldown(override_cooldown_time, override_melee_cooldown_time)
// don't re-set the cooldown if we don't have a override time and there's already a cooldown ongoing
if(!override_cooldown_time && next_use_time > world.time)
return
// Calculate Cooldown (by power's level)
if(power_flags & BP_AM_STATIC_COOLDOWN)
cooldown_time = initial(cooldown_time)
@@ -148,6 +163,9 @@
return ..()
/datum/action/cooldown/bloodsucker/proc/can_pay_blood(mob/living/carbon/user)
return bloodsuckerdatum_power ? bloodsuckerdatum_power?.bloodsucker_blood_volume >= 0 : user.blood_volume >= 0
/datum/action/cooldown/bloodsucker/proc/can_deactivate()
return TRUE
@@ -164,8 +182,7 @@
// 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()
bloodsuckerdatum_power.AdjustBloodVolume(-bloodcost)
/datum/action/cooldown/bloodsucker/proc/ActivatePower(trigger_flags)
active = TRUE
@@ -200,7 +217,7 @@
if(power_flags & BP_AM_COSTLESS_UNCONSCIOUS && owner.stat != CONSCIOUS)
return TRUE
if(bloodsuckerdatum_power)
bloodsuckerdatum_power.AddBloodVolume(-constant_bloodcost * seconds_per_tick)
bloodsuckerdatum_power.AdjustBloodVolume(-constant_bloodcost * seconds_per_tick)
else
var/mob/living/living_owner = owner
if(!HAS_TRAIT(living_owner, TRAIT_NOBLOOD))
@@ -211,7 +228,7 @@
/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)
if(!constant_bloodcost > 0 || can_pay_blood(user))
return TRUE
/// Used to unlearn Single-Use Powers
@@ -15,8 +15,8 @@
If you are in desperate need of blood, mice can be fed off of, at a cost.\n\
You must use the ability again to stop sucking blood."
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
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS|BP_CAN_USE_WHILE_STAKED
purchase_flags = BLOODSUCKER_DEFAULT_POWER
bloodcost = 0
cooldown_time = 15 SECONDS
@@ -69,8 +69,8 @@
bloodsuckerdatum_power.AddHumanityLost(5)
target_ref = null
warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL
blood_taken = 0
warning_target_bloodvol = initial(warning_target_bloodvol)
blood_taken = initial(blood_taken)
REMOVE_TRAIT(user, TRAIT_IMMOBILIZED, FEED_TRAIT)
REMOVE_TRAIT(user, TRAIT_MUTE, FEED_TRAIT)
return ..()
@@ -84,7 +84,7 @@
var/mob/living/user = owner
user.add_mood_event("drankblood", /datum/mood_event/drankblood_bad)
bloodsuckerdatum_power.AddHumanityLost(1)
bloodsuckerdatum_power.AddBloodVolume(25)
bloodsuckerdatum_power.AdjustBloodVolume(25)
DeactivatePower()
feed_target.death()
return
@@ -111,7 +111,7 @@
// Only people who AREN'T the target will notice this action.
var/dead_message = feed_target.stat != DEAD ? " <i>[feed_target.p_they(TRUE)] looks dazed, and will not remember this.</i>" : ""
owner.visible_message(
span_notice("[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth."), \
span_warning("[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth."), \
span_notice("You slip your fangs into [feed_target]'s wrist.[dead_message]"), \
vision_distance = FEED_NOTICE_RANGE, ignored_mobs = feed_target)
@@ -4,7 +4,7 @@
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\
You will additionally gain resistance to both physical and stamina damage, 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."
@@ -29,6 +29,7 @@
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.burn_mod *= fortitude_resist + 0.2
bloodsucker_user.physiology.stamina_mod *= fortitude_resist
was_running = (bloodsucker_user.move_intent == MOVE_INTENT_RUN)
@@ -58,8 +59,8 @@
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
bloodsucker_user.physiology.burn_mod /= fortitude_resist + 0.2
bloodsucker_user.physiology.stamina_mod /= fortitude_resist
// Remove Traits & Effects
owner.remove_traits(list(TRAIT_PIERCEIMMUNE, TRAIT_NODISMEMBER, TRAIT_PUSHIMMUNE, TRAIT_STUNIMMUNE), BLOODSUCKER_TRAIT)
@@ -20,11 +20,12 @@
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
check_flags = BP_CANT_USE_IN_FRENZY
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.
@@ -16,14 +16,14 @@
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\
- You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus, gutting 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."
At the end of a Masquerade, you will re-gain your Vampiric abilities, as well as lose any diseases 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
purchase_flags = BLOODSUCKER_DEFAULT_POWER
bloodcost = 10
cooldown_time = 5 SECONDS
constant_bloodcost = 0.1
@@ -40,7 +40,11 @@
// Handle Traits
user.remove_traits(bloodsuckerdatum_power.bloodsucker_traits, BLOODSUCKER_TRAIT)
ADD_TRAIT(user, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT)
var/obj/item/bodypart/chest/target_chest = user.get_bodypart(BODY_ZONE_CHEST)
if(target_chest)
target_chest.bodypart_flags &= ~BODYPART_UNREMOVABLE
// Handle organs
var/obj/item/organ/internal/heart/vampheart = user.get_organ_slot(ORGAN_SLOT_HEART)
if(vampheart)
@@ -52,6 +56,7 @@
eyes.sight_flags = initial(eyes.sight_flags)
user.update_sight()
/// todo, make bloodsuckerification into it's own proc, ie, eyes, traits, and such
/datum/action/cooldown/bloodsucker/masquerade/DeactivatePower()
. = ..() // activate = FALSE
var/mob/living/carbon/user = owner
@@ -59,14 +64,15 @@
// 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
user.add_traits(bloodsuckerdatum_power.bloodsucker_traits, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(user, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT)
var/obj/item/bodypart/chest/target_chest = user.get_bodypart(BODY_ZONE_CHEST)
if(target_chest)
target_chest.bodypart_flags |= BODYPART_UNREMOVABLE
// Handle organs
var/obj/item/organ/internal/heart/vampheart = user.get_organ_slot(ORGAN_SLOT_HEART)
if(vampheart)
@@ -5,10 +5,11 @@
/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."
desc = "Force yourself to stand up if you're down and 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\
At level 3, if you are lying down, you will get up and regain your stamina, but the resulting dash will not knock down those nearby.\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.\n\
@@ -34,8 +35,8 @@
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!")
if(level_current < 3 && user.body_position == LYING_DOWN)
user.balloon_alert(user, "you must be standing to dash!")
return FALSE
return TRUE
@@ -49,8 +50,17 @@
/// 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/stuns_mobs = TRUE
if(level_current >= 3 && user.body_position == LYING_DOWN)
to_chat(user, span_danger("Your heart takes a beat, and you force yourself to stand up!"))
user.SetKnockdown(0)
user.setStaminaLoss(0)
user.set_resting(FALSE, FALSE, TRUE)
stuns_mobs = FALSE
StartCooldown(cooldown_time * 3)
if(stuns_mobs)
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
var/turf/targeted_turf = isturf(target_atom) ? target_atom : get_turf(target_atom)
// Pulled? Not anymore.
user.pulledby?.stop_pulling()
@@ -113,7 +113,7 @@
/*if(IS_MONSTERHUNTER(mesmerized_target))
to_chat(mesmerized_target, span_notice("You feel your eyes burn for a while, but it passes."))
return*/
if(HAS_TRAIT_FROM(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT))
if(HAS_TRAIT_FROM_ONLY(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT))
owner.balloon_alert(owner, "[mesmerized_target] is already in a hypnotic gaze.")
return
if(iscarbon(mesmerized_target))
@@ -68,12 +68,12 @@
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."
desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged or dead 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\
If you use this on a currently dead normal Vassal, they will will not suddenly cease to live as if a temporary Vassal.\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"
@@ -83,14 +83,14 @@
/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."
desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged or dead 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\
If you use this on a currently dead normal Vassal, they will will not suddenly cease to live as if a temporary Vassal.\n\
They will have complete loyalty to you, until their death in 8 minutes upon use."
bloodcost = 100
cooldown_time = 2 MINUTES
@@ -110,8 +110,11 @@
. = ..()
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)
if(target.stat != CONSCIOUS && level_current >= 4)
if(user.Adjacent(target))
attempt_vassalize(target, user)
else
owner.balloon_alert(owner, "too far to vassalize!")
return
else if(IS_VASSAL(target))
owner.balloon_alert(owner, "vassal cant be revived")
@@ -128,7 +131,8 @@
/*if(IS_MONSTERHUNTER(target))
to_chat(target, span_notice("You feel you something crawling under your skin, but it passes."))
return*/
if(HAS_TRAIT_FROM(target, TRAIT_MUTE, BLOODSUCKER_TRAIT))
// todo replace with status effect so we don't only check chainstunning above lvl 2
if(HAS_TRAIT_FROM_ONLY(target, TRAIT_MUTE, BLOODSUCKER_TRAIT))
owner.balloon_alert(owner, "[target] is already in some form of hypnotic gaze.")
return
if(iscarbon(target))
@@ -155,37 +159,50 @@
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)
var/datum/antagonist/vassal/is_vassal = IS_VASSAL(target)
if(!bloodsuckerdatum_power.can_make_vassal(target))
owner.balloon_alert(owner, "not a valid target for vassalization!.")
return FALSE
owner.balloon_alert(owner, "attempting to vassalize.")
if(!do_after(user, 6 SECONDS, target, NONE, TRUE))
return
return FALSE
if(IS_VASSAL(target))
if(is_vassal?.master == bloodsuckerdatum_power)
if(target.stat != DEAD)
owner.balloon_alert(owner, "not dead!")
return FALSE
power_activated_sucessfully()
to_chat(user, span_warning("We revive [target]!"))
owner.balloon_alert(owner, "successfully revived!")
target.mind.grab_ghost()
target.revive(ADMIN_HEAL_ALL)
return FALSE
if(!bloodsuckerdatum_power.make_vassal(target))
owner.balloon_alert(owner, "not a valid target for vassalization!.")
return
/*if(IS_MONSTERHUNTER(target))
to_chat(target, span_notice("Their body refuses to react..."))
return*/
if(!bloodsuckerdatum_power.make_vassal(target))
return
power_activated_sucessfully()
to_chat(user, span_warning("We revive [target]!"))
target.mind.grab_ghost()
target.revive(ADMIN_HEAL_ALL)
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
target.add_traits(list(TRAIT_MUTE, TRAIT_DEAF), BLOODSUCKER_TRAIT)
else if(level_current == 5)
living_time = 8 MINUTES
var/living_time = (1 MINUTES) * level_current
if(level_current <= 4)
target.add_traits(list(TRAIT_MUTE, TRAIT_DEAF), DOMINATE_TRAIT)
addtimer(CALLBACK(src, PROC_REF(end_possession), target), living_time)
RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(end_possession))
return TRUE
/datum/action/cooldown/bloodsucker/targeted/tremere/proc/end_possession(mob/living/user)
user.remove_traits(list(TRAIT_MUTE, TRAIT_DEAF), BLOODSUCKER_TRAIT)
if(!IS_VASSAL(user) && !HAS_TRAIT_FROM_ONLY(user, TRAIT_MUTE, DOMINATE_TRAIT) && !HAS_TRAIT_FROM_ONLY(user, TRAIT_MUTE, DOMINATE_TRAIT))
return
user.remove_traits(list(TRAIT_MUTE, TRAIT_DEAF), DOMINATE_TRAIT)
user.mind.remove_antag_datum(/datum/antagonist/vassal)
to_chat(user, span_warning("You feel the Blood of your Master run out!"))
user.death()
@@ -153,7 +153,7 @@
if(bloodsucker_power.level_current >= 5)
var/mob/living/person_hit = target
person_hit.blood_volume -= 60
bloodsucker_power.bloodsuckerdatum_power.AddBloodVolume(60)
bloodsucker_power.bloodsuckerdatum_power.AdjustBloodVolume(60)
qdel(src)
return BULLET_ACT_HIT
. = ..()
@@ -182,5 +182,5 @@
/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)
bloodsuckerdatum.AdjustBloodVolume(-15)
return ..()
@@ -7,9 +7,9 @@
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."
The power will cancel out if you are dead or unconcious."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
check_flags = BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = NONE
bloodcost = 1.5
cooldown_time = 10 SECONDS
@@ -38,7 +38,7 @@
return
var/mob/living/carbon/user = owner
var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(user)
vassaldatum.master.AddBloodVolume(-1)
vassaldatum.master.AdjustBloodVolume(-1)
user.set_timed_status_effect(5 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE)
user.adjustStaminaLoss(bloodcost * 1.1)
user.adjustBruteLoss(-2.5)
@@ -55,7 +55,7 @@
/datum/action/cooldown/bloodsucker/recuperate/ContinueActive(mob/living/user, mob/living/target)
if(user.stat >= DEAD)
return FALSE
if(user.incapacitated())
if(user.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB))
owner.balloon_alert(owner, "too exhausted...")
return FALSE
return TRUE
@@ -7,7 +7,7 @@
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
check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_DEFAULT_POWER
bloodcost = 15
constant_bloodcost = 0.1
@@ -29,6 +29,7 @@
if(user == resident)
. += span_cult("This is your Claimed Coffin.")
. += span_cult("Rest in it while injured to enter Torpor. Entering it with unspent Ranks will allow you to spend one.")
. += span_cult("Going inside while it contains a heart will put it in your chest, letting you regain your might.")
. += span_cult("Alt-Click while inside the Coffin to Lock/Unlock.")
. += span_cult("Alt-Click while outside of your Coffin to Unclaim it, unwrenching it and all your other structures as a result.")
@@ -133,6 +134,8 @@
resident = claimant
anchored = TRUE
START_PROCESSING(SSprocessing, src)
return TRUE
return FALSE
/obj/structure/closet/crate/coffin/Destroy()
unclaim_coffin()
@@ -168,6 +171,7 @@
anchored = FALSE
if(!resident || !resident.mind)
return
un_enlarge(resident)
// Unclaiming
var/datum/antagonist/bloodsucker/bloodsuckerdatum = resident.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(bloodsuckerdatum && bloodsuckerdatum.coffin == src)
@@ -198,33 +202,61 @@
/obj/structure/closet/crate/coffin/close(mob/living/user)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user?.mind?.has_antag_datum(/datum/antagonist/bloodsucker)
if(bloodsuckerdatum && user.mob_size > max_mob_size)
if(!HAS_TRAIT_FROM_ONLY(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin"))
if(prompt_coffin_claim(bloodsuckerdatum))
enlarge(user)
else
user.balloon_alert(user, "already claimed by another!")
. = ..()
if(!.)
return FALSE
for(var/atom/thing as anything in contents)
SEND_SIGNAL(thing, COMSIG_ENTER_COFFIN, src, user)
// 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)
if(!resident && !prompt_coffin_claim(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)
user.balloon_alert("enter a clan!")
to_chat(user, span_notice("You must enter a Clan to rank up. Do it in the antag menu, which you can see by pressing the action button in the top left."))
else
// Level ups cost 30% of your max blood volume, which scales with your rank.
bloodsuckerdatum.SpendRank(blood_cost = bloodsuckerdatum.max_blood_volume * BLOODSUCKER_LEVELUP_PERCENTAGE)
// 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()
if(!bloodsuckerdatum.frenzied)
bloodsuckerdatum.SpendRank(blood_cost = bloodsuckerdatum.max_blood_volume * BLOODSUCKER_LEVELUP_PERCENTAGE)
// You're in a Coffin, everything else is done, you're likely here to heal. Let's offer them the opportunity to do so.
bloodsuckerdatum.check_begin_torpor(TORPOR_SKIP_CHECK_DAMAGE)
return TRUE
#undef BLOODSUCKER_LEVELUP_PERCENTAGE
/obj/structure/closet/crate/coffin/proc/prompt_coffin_claim(datum/antagonist/bloodsucker/dracula)
if(!dracula)
return FALSE
var/area/current_area = get_area(src)
if(!dracula.coffin && !resident)
switch(tgui_alert(dracula.owner.current, "Do you wish to claim this as your coffin? [current_area] will be your lair.", "Claim Lair", list("Yes", "No")))
if("Yes")
return claim_coffin(dracula.owner.current, current_area)
return FALSE
// some fatass bloodsucker is trying to fit in a too-small coffin, how about we make some room?
/obj/structure/closet/crate/proc/enlarge(mob/living/user)
ADD_TRAIT(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin")
max_mob_size = user.mob_size
to_chat(user, span_warning("The coffin creaks and squeaks as you try to squeeze into it. It's a tight fit but you manage it make it fit you."))
playsound(src, 'modular_skyrat/modules/aesthetics/airlock/sound/creaking.ogg')
animate(src, 1 SECONDS, FALSE, BOUNCE_EASING, transform = transform.Scale(user.mob_size * COFFIN_ENLARGE_MULT))
/obj/structure/closet/crate/proc/un_enlarge(mob/living/user)
if(!HAS_TRAIT_FROM_ONLY(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin"))
return
REMOVE_TRAIT(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin")
max_mob_size = initial(max_mob_size)
var/matrix/normal
// transform.Scale(user.mob_size * (COFFIN_ENLARGE_MULT + 1)
animate(src, 1 SECONDS, FALSE, transform = normal)
/// 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)
@@ -296,10 +296,10 @@
// Conversion Process
if(convert_progress)
balloon_alert(user, "spilling blood...")
bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST)
bloodsuckerdatum.AdjustBloodVolume(-TORTURE_BLOOD_HALF_COST)
if(!do_torture(user, target))
return FALSE
bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST)
bloodsuckerdatum.AdjustBloodVolume(-TORTURE_BLOOD_HALF_COST)
// Prevent them from unbuckling themselves as long as we're torturing.
target.Paralyze(1 SECONDS)
convert_progress--
@@ -329,7 +329,7 @@
balloon_alert(user, "interrupted!")
return
// Convert to Vassal!
bloodsuckerdatum.AddBloodVolume(-TORTURE_CONVERSION_COST)
bloodsuckerdatum.AdjustBloodVolume(-TORTURE_CONVERSION_COST)
if(bloodsuckerdatum.make_vassal(target))
remove_loyalties(target)
SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, user, target)
@@ -381,7 +381,9 @@
/obj/structure/bloodsucker/vassalrack/proc/do_disloyalty(mob/living/user, mob/living/target)
if(disloyalty_offered)
return FALSE
// Can't willingly join if you're banned from it. It'll just ghost you anyways.
if(is_banned_from(target.ckey, ROLE_BLOODSUCKER))
return TRUE
disloyalty_offered = TRUE
to_chat(user, span_notice("[target] has been given the opportunity for servitude. You await their decision..."))
var/alert_response = tgui_alert(
@@ -124,9 +124,15 @@
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 3)
///Time it takes to embed the stake into someone's chest.
var/staketime = 12 SECONDS
var/staketime = 5 SECONDS
var/kills_blodsuckers = FALSE
/obj/item/stake/examine_more(mob/user)
. = ..()
. += span_notice("You can use [src] to stake someone in the chest, if they are laying down or grabbed by the neck.")
if(IS_BLOODSUCKER(user))
. += span_warning("You feel a sense of dread as you look at the [src]...")
/obj/item/stake/attack(mob/living/target, mob/living/user, params)
. = ..()
if(.)
@@ -161,7 +167,7 @@
return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = target.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(bloodsuckerdatum)
// If DEAD or TORPID... Kill Bloodsucker!
// If silver stake and DEAD or TORPOR... Kill the Bloodsucker!
if(target.StakeCanKillMe())
bloodsuckerdatum.FinalDeath()
else
@@ -173,7 +179,7 @@
return FALSE
/mob/living/carbon/can_be_staked()
if(!(mobility_flags & MOBILITY_MOVE))
if(body_position == LYING_DOWN)
return TRUE
return FALSE
@@ -185,8 +191,12 @@
force = 8
throwforce = 12
armour_penetration = 10
embedding = list("embed_chance" = 35)
staketime = 10 SECONDS
embedding = list("embed_chance" = 35, "fall_chance" = 0)
staketime = 12 SECONDS
/obj/item/stake/hardened/examine_more(mob/user)
. = ..()
. += span_notice("The [src] won't fall out by itself, if embedded in someone.")
/obj/item/stake/hardened/silver
name = "silver stake"
@@ -197,10 +207,15 @@
force = 9
armour_penetration = 25
custom_materials = list(/datum/material/silver = SHEET_MATERIAL_AMOUNT)
embedding = list("embed_chance" = 65)
staketime = 8 SECONDS
embedding = list("embed_chance" = 65, "fall_chance" = 0)
staketime = 15 SECONDS
kills_blodsuckers = TRUE
/obj/item/stake/hardened/silver/examine_more(mob/user)
. = ..()
if(HAS_TRAIT(user.mind, TRAIT_BLOODSUCKER_HUNTER))
. += span_notice("You know that the [src] can cause a Final Death to a vile Bloodsucker if they are asleep or dead.")
//////////////////////
// ARCHIVES //
//////////////////////
@@ -233,13 +248,6 @@
COOLDOWN_DECLARE(bloodsucker_check_cooldown)
var/cooldown_time = 1 MINUTES
/obj/item/book/kindred/station_loving
/obj/item/book/kindred/station_loving/Initialize()
. = ..()
SSpoints_of_interest.make_point_of_interest(src)
AddComponent(/datum/component/stationloving, FALSE, TRUE)
/obj/item/book/kindred/try_carve(obj/item/carving_item, mob/living/user, params)
to_chat(user, span_notice("You feel the gentle whispers of a Librarian telling you not to cut [starting_title]."))
return FALSE
@@ -304,5 +312,22 @@
/obj/structure/displaycase/curator
desc = "This book was found inside a coffin of a long dead Curator. It is said to be able to reveal the true nature of those who feed upon mankind."
start_showpiece_type = /obj/item/book/kindred/station_loving
start_showpiece_type = /obj/item/book/kindred
req_access = list(ACCESS_LIBRARY)
/// just a typepath to specify that it's monkey-owned, used for the heart thief objective
/obj/item/organ/internal/heart/monkey
/obj/item/organ/internal/heart/examine_more(mob/user)
. = ..()
var/datum/antagonist/bloodsucker/vampire = IS_BLOODSUCKER(user)
if(!vampire)
return
var/datum/objective/steal_n_of_type/heart_thief = locate() in vampire?.objectives
if(!heart_thief)
return
if(heart_thief.check_if_valid_item(src))
. += span_notice("This [src.name] will do for your purposes...")
else
. += span_notice("This [src.name] is of lesser quality, it won't do...")
@@ -6,14 +6,14 @@
/datum/action/cooldown/spell/shapeshift/bat
name = "Bat Form"
desc = "Take on the shape of a space bat. You can fly and see in the dark, and you heal from doing melee attacks."
power_explanation = "You can fly and see in the dark, and you heal from doing melee attacks."
invocation = "Master, grant me your might, BAT FORM!!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
convert_damage = TRUE
cooldown_time = 2 MINUTES
cooldown_time = 1 MINUTES
possible_shapes = list(/mob/living/basic/bat/bloodsucker)
var/bats_to_spawn = 3
var/powers_stored
/datum/action/cooldown/spell/shapeshift/bat/cast(mob/living/cast_on)
. = ..()
@@ -22,6 +22,7 @@
return
for(var/i in 1 to bats_to_spawn)
var/mob/living/basic/bat/bat = new(get_turf(cast_on))
QDEL_IN(bat, cooldown_time)
random_step(bat, 2, 50)
/mob/living/basic/bat/bloodsucker
@@ -29,20 +30,24 @@
desc = "A bat with a thirst for blood."
maxHealth = 100
health = 100
speed = 0
melee_damage_lower = 10
melee_damage_upper = 15
wound_bonus = 10
obj_damage = 10
speed = -0.1
lighting_cutoff_red = 25
lighting_cutoff_green = 8
lighting_cutoff_blue = 5
melee_attack_cooldown = CLICK_CD_MELEE
basic_mob_flags = FLAMMABLE_MOB
lighting_color_cutoffs = BLOODSUCKER_SIGHT_COLOR_CUTOFF
damage_coeff = list(BRUTE = 0.9, BURN = 1.25, TOX = 1, STAMINA = 1, OXY = 1)
attack_verb_simple = list("bite", "drain", "drain blood", "nip")
attack_verb_continuous = list("bites", "drains", "drains blood", "nips")
attack_verb_simple = "drain blood from"
attack_verb_continuous = "drains blood from"
/mob/living/basic/bat/bloodsucker/Initialize(mapload)
. = ..()
// Go as fast as people can run
AddElement(/datum/element/lifesteal, melee_damage_lower)
// Too fat to fit through vents
REMOVE_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
@@ -7,48 +7,39 @@
name = "\improper Vassal"
roundend_category = "vassals"
antagpanel_category = "Bloodsucker"
job_rank = ROLE_BLOODSUCKER
job_rank = ROLE_VASSAL
antag_hud_name = "vassal"
show_in_roundend = FALSE
hud_icon = 'modular_zubbers/icons/mob/huds/bloodsucker.dmi'
ui_name = "AntagInfoVassal"
antag_panel_description = "You are a Vassal, a mortal servant of a Bloodsucker."
/// The Master Bloodsucker's antag datum.
var/datum/antagonist/bloodsucker/master
/// List of all Purchased Powers, like Bloodsuckers.
/// List of all Purchased Powers, to be cleaned up on antag removal.
var/list/datum/action/powers = list()
///Whether this vassal is already a special type of Vassal.
var/special_type = FALSE
/// The first textblock text in the antag panel.
var/antag_panel_title
var/antag_panel_description
///Description of what this Vassal does.
/// It's shown to the bloodsucker in the radial for setting vassal type
var/vassal_description
/// inherent traits that are removed and addded on antag datum loss and gain
var/list/traits = list()
/datum/antagonist/vassal/antag_panel_data()
return "Master : [master.owner.name]"
return "Master : [master?.owner.name ? master.owner.name : "Gone"]"
// todo make this into a shared proc that bloodsuckers and vassals share
/datum/antagonist/vassal/ui_static_data(mob/user)
var/list/data = list()
data["title"] = "[antag_panel_title]\n[antag_panel_data()]"
data["description"] = antag_panel_description
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["powers"] += list(power_data)
return data + ..()
return ability_ui_data(powers) + ..()
/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_team_hud(current_mob)
if(length(traits))
current_mob.add_traits(traits, VASSAL_TRAIT)
/datum/antagonist/vassal/add_team_hud(mob/target)
QDEL_NULL(team_hud_ref)
@@ -76,6 +67,8 @@
. = ..()
var/mob/living/current_mob = mob_override || owner.current
current_mob.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
if(length(traits))
current_mob.remove_traits(traits, VASSAL_TRAIT)
/datum/antagonist/vassal/pre_mindshield(mob/implanter, mob/living/mob_override)
return COMPONENT_MINDSHIELD_PASSED
@@ -114,7 +107,7 @@
objectives += vassal_objective
/// Give Vampire Language & Hud
owner.current.grant_all_languages(FALSE, FALSE, TRUE)
owner.current.grant_language(/datum/language/vampiric, LANGUAGE_SPOKEN, LANGUAGE_MIND)
owner.current.grant_language(/datum/language/vampiric, ALL, LANGUAGE_MIND)
return ..()
/datum/antagonist/vassal/on_removal()
@@ -128,14 +121,14 @@
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_TRAIT(owner.current, all_status_traits, VASSAL_TRAIT)
//Remove Recuperate Power
remove_powers(powers)
//Remove Language & Hud
owner.current.remove_language(/datum/language/vampiric, LANGUAGE_SPOKEN, LANGUAGE_MIND)
owner.current.remove_language(/datum/language/vampiric, ALL, LANGUAGE_MIND)
return ..()
/datum/antagonist/vassal/proc/remove_powers(var/list/removing_powers)
/datum/antagonist/vassal/proc/remove_powers(list/removing_powers)
for(var/datum/action/cooldown/bloodsucker/power as anything in removing_powers)
removing_powers -= power
if(!(power in owner.current.actions))
@@ -187,12 +180,12 @@
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!")
message_admins("[key_name_admin(admin)] 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
var/datum/mind/choice = tgui_input_list(admin, "Which bloodsucker should this vassal belong to?", "Bloodsucker", 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)]!")
log_admin("[key_name_admin(admin)] 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)
@@ -9,7 +9,7 @@
* viewer - The person examining.
*/
/datum/antagonist/vassal/proc/return_vassal_examine(mob/living/viewer)
if(!viewer.mind || !iscarbon(owner.current))
if((!viewer.mind && !isobserver(viewer)) || !iscarbon(owner.current))
return FALSE
var/mob/living/carbon/carbon_current = owner.current
// Target must be a Vassal
@@ -17,14 +17,14 @@
var/returnString = "\[<span class='warning'>"
var/returnIcon = ""
// Vassals and Bloodsuckers recognize eachother, while Monster Hunters can see Vassals.
if(!IS_BLOODSUCKER(viewer) && !IS_VASSAL(viewer) && !IS_MONSTERHUNTER(viewer))
if(!IS_BLOODSUCKER(viewer) && !IS_VASSAL(viewer) && !IS_MONSTERHUNTER(viewer) && !isobserver(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('modular_zubbers/icons/misc/language.dmi', world, "vassal")]"
// Am I someone ELSE'S Vassal?
else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer))
else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer) || isobserver(viewer))
returnString += "This [carbon_current.dna.species.name] bears the mark of <span class='boldwarning'>[master.return_full_name()][master.broke_masquerade ? " who has broken the Masquerade" : ""]</span>"
returnIcon = "[icon2html('modular_zubbers/icons/misc/language.dmi', world, "vassal_grey")]"
// Are you serving the same master as I am?
@@ -40,7 +40,7 @@
return returnIcon + returnString
/// Used when your Master teaches you a new Power.
/datum/antagonist/vassal/proc/BuyPower(datum/action/cooldown/bloodsucker/power, list_to_add_to = powers)
/datum/antagonist/vassal/proc/BuyPower(datum/action/cooldown/power, list_to_add_to = powers)
for(var/datum/action/current_powers as anything in list_to_add_to)
if(current_powers.type == power.type)
return FALSE
@@ -5,7 +5,7 @@
name = "\improper Ex-Vassal"
roundend_category = "vassals"
antagpanel_category = "Bloodsucker"
job_rank = ROLE_BLOODSUCKER
job_rank = ROLE_VASSAL
antag_hud_name = "vassal_grey"
show_in_roundend = FALSE
show_in_antagpanel = FALSE
@@ -5,7 +5,6 @@
*/
/datum/antagonist/vassal/favorite
name = "\improper Favorite Vassal"
show_in_antagpanel = FALSE
antag_hud_name = "vassal6"
special_type = FAVORITE_VASSAL
vassal_description = "The Favorite Vassal gets unique abilities over other Vassals depending on your Clan \
@@ -21,7 +20,7 @@
SEND_SIGNAL(master, BLOODSUCKER_MAKE_FAVORITE, src)
/datum/antagonist/vassal/favorite/on_removal()
remove_powers(bloodsucker_powers)
SEND_SIGNAL(master, BLOODSUCKER_LOOSE_FAVORITE, src)
. = ..()
/datum/antagonist/vassal/favorite/pre_mindshield(mob/implanter, mob/living/mob_override)
@@ -6,28 +6,29 @@
return null
/// Replaces a couple organs to normal variants to not cause issues. Not super happy with this, alternative is disallowing vampiric races from being bloodsuckers
/datum/species/proc/humanize_organs(mob/living/carbon/human/target)
var/list/organs_to_handle = list(
ORGAN_SLOT_HEART = /obj/item/organ/internal/heart,
ORGAN_SLOT_LIVER = /obj/item/organ/internal/liver,
ORGAN_SLOT_STOMACH = /obj/item/organ/internal/stomach,
ORGAN_SLOT_TONGUE = /obj/item/organ/internal/tongue,
)
mutantheart = organs_to_handle[ORGAN_SLOT_HEART]
mutantliver = organs_to_handle[ORGAN_SLOT_LIVER]
mutantstomach = organs_to_handle[ORGAN_SLOT_STOMACH]
mutanttongue = organs_to_handle[ORGAN_SLOT_TONGUE]
for(var/organ_slot in organs_to_handle)
/datum/species/proc/humanize_organs(mob/living/carbon/human/target, organs = list())
if(!organs || !length(organs))
organs = list(
ORGAN_SLOT_HEART = /obj/item/organ/internal/heart,
ORGAN_SLOT_LIVER = /obj/item/organ/internal/liver,
ORGAN_SLOT_STOMACH = /obj/item/organ/internal/stomach,
ORGAN_SLOT_TONGUE = /obj/item/organ/internal/tongue,
)
mutantheart = organs[ORGAN_SLOT_HEART]
mutantliver = organs[ORGAN_SLOT_LIVER]
mutantstomach = organs[ORGAN_SLOT_STOMACH]
mutanttongue = organs[ORGAN_SLOT_TONGUE]
for(var/organ_slot in organs)
var/obj/item/organ/old_organ = target.get_organ_slot(organ_slot)
var/organ_path = organs_to_handle[organ_slot]
var/organ_path = organs[organ_slot]
if(old_organ?.type == organ_path)
continue
var/obj/item/organ/new_organ = SSwardrobe.provide_type(organ_path)
new_organ.Insert(target, FALSE, FALSE)
new_organ.Insert(target, FALSE, DELETE_IF_REPLACED)
/datum/species/proc/normalize_organs(mob/living/carbon/human/target)
mutantheart = initial(mutantheart)
mutantliver = initial(mutantliver)
mutantstomach = initial(mutantstomach)
mutanttongue = initial(mutanttongue)
regenerate_organs(target)
regenerate_organs(target, replace_current = TRUE)
@@ -0,0 +1,7 @@
/datum/species/jelly/on_bloodsucker_gain(mob/living/carbon/human/target)
humanize_organs(target)
/datum/species/jelly/on_bloodsucker_loss(mob/living/carbon/human/target)
// regenerate_organs with replace doesn't seem to automatically remove invalid organs unfortunately
normalize_organs()
@@ -0,0 +1,8 @@
/datum/species/lizard/on_bloodsucker_gain(mob/living/carbon/human/target, datum/species/current_species)
bodytemp_heat_damage_limit = BODYTEMP_HEAT_DAMAGE_LIMIT
bodytemp_cold_damage_limit = BODYTEMP_COLD_DAMAGE_LIMIT
/datum/species/lizard/on_bloodsucker_loss(mob/living/carbon/human/target)
bodytemp_heat_damage_limit = initial(bodytemp_heat_damage_limit)
bodytemp_cold_damage_limit = initial(bodytemp_cold_damage_limit)
@@ -0,0 +1,3 @@
/datum/species/monkey
mutantheart = /obj/item/organ/internal/heart/monkey
+6 -1
View File
@@ -8148,7 +8148,7 @@
#include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\sentinel.dm"
#include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\spitter.dm"
#include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\warrior.dm"
#include "modular_zubbers\code\__DEFINES\bloodsucker.dm"
#include "modular_zubbers\code\__DEFINES\bloodsucker_defines.dm"
#include "modular_zubbers\code\__DEFINES\moonstation_defines.dm"
#include "modular_zubbers\code\__DEFINES\send2relay.dm"
#include "modular_zubbers\code\__DEFINES\traits.dm"
@@ -8162,6 +8162,7 @@
#include "modular_zubbers\code\_globalvars\lists\~maintenance_loot.dm"
#include "modular_zubbers\code\controllers\subsystem\air.dm"
#include "modular_zubbers\code\controllers\subsystem\sol_subsystem.dm"
#include "modular_zubbers\code\datums\action.dm"
#include "modular_zubbers\code\datums\armor_overrides.dm"
#include "modular_zubbers\code\datums\department_security.dm"
#include "modular_zubbers\code\datums\ert.dm"
@@ -8270,6 +8271,7 @@
#include "modular_zubbers\code\modules\_defines.dm"
#include "modular_zubbers\code\modules\admin\verbs\debug.dm"
#include "modular_zubbers\code\modules\alternative_job_titles\code\alt_job_titles.dm"
#include "modular_zubbers\code\modules\antagonists\_common\antag_datum.dm"
#include "modular_zubbers\code\modules\antagonists\ashwalker\ashwalker.dm"
#include "modular_zubbers\code\modules\antagonists\bloodsucker\bloodsuckers\assets.dm"
#include "modular_zubbers\code\modules\antagonists\bloodsucker\bloodsuckers\bloodsucker.dm"
@@ -8439,6 +8441,9 @@
#include "modular_zubbers\code\modules\mob\living\carbon\human\_species.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\human.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\abductor.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\jellypeople.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\lizardpeople.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\monkeys.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\shadekin.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\snail.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\vampire.dm"