Bloodsucker power refactor (#1458)

<!-- 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
Refactors bloodsuckers powers heavily and changes around a lot of few
powers work and even removes a lot of copypasta from tremere powers.
fixes #1384
<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

<!-- Please make sure to actually test your PRs. If you have not tested
your PR mention it. -->

## Why It's Good For The Game
Currently Tremere is pretty meh compared to other bloodsuckers, and
bloodsucker powers use a lot of code that actions now implement
themselves.
<!-- 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
I basically test almost every change, as part of how I work, obviously
not completely thoroughly thus testmerges,.
<!-- 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. -->

🆑
refactor: Heavily refactors how bloodsucker powers function.
refactor: Tremere powers no longer have subtypes for each level.
add: Completely redoes how bloodsucker ability descriptions, both the
longer antag panel and the action button hover over, they will actually
tell you actual values that the game itself uses for damage, cooldowns,
and effect durations
balance: Tremere powers can now level up as far as you want.
balance: Mesmerize is heavily reworked, now it no longer forces both
parties to stand still for it to work, but it is obvious to the victim
when used, however the victim is muted for the duration of the spool up,
and will stun and mute normally if the do_after completes.
add: Mesmerize now has a secondary that allows you to mute and confuse
your victim that happens on a right-click,
dominate, the tremere's version instead knockdowns.
add: Tremere's dominate now has a visual timer for temporary vassals,
visible only to the master's vassals and the master themselves.
balance: Tremere's dominate now only requires level 2 to create
temporary vassals, but the duration now scales with the level. It now
requires the potential vassal to have more than 336 blood, and will use
it all up once the duration ends, effectively making it only usable once
per person.
balance: All tremere powers can now level up past level 4
balance: Thaumaturgy is heavily reworked, it now has a charge system,
and while the projectile deal less damage, you can shoot a lot more of
them, and the projectiles will seek towards any non-vassal mob near
where you cast it.
refactor: Separates bloodsucker ability bitfields and action bitfields
to avoid overriding eachother
balance: Bloodsuckers going into torpor outside of coffins will always
die, but will wake out of torpor at 20% maxhealth(not counting critical
health)
balance: A coffin being opened during sol will wake you up, given you
are LITERALL BURNING UN-ALIVE.

/🆑

<!-- 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>
Co-authored-by: The Sharkening <95130227+StrangeWeirdKitten@users.noreply.github.com>
This commit is contained in:
Arturlang
2024-09-20 20:00:32 +00:00
committed by GitHub
co-authored by Waterpig The Sharkening
parent a99c2be5cf
commit abea882914
60 changed files with 2018 additions and 1159 deletions
@@ -4,3 +4,5 @@
#define COMSIG_NAME_CHANGED "name_changed"
///from [/mob/living/carbon/human/Move]: ()
#define COMSIG_NECK_STEP_ACTION "neck_step_action"
/// from /obj/item/organ/proc/on_bodypart_remove(obj/item/bodypart/limb, movement_flags)
#define COMSIG_ORGAN_BODYPART_REMOVED "organ_bodypart_removed"
@@ -17,5 +17,8 @@
//basic security hud
#define TRAIT_BASIC_SECURITY_HUD "basic_security_hud"
#define TRAIT_TORPOR "torpor"
//lipstick trait
#define TRAIT_HYPNO_KISS "hypno kiss"
+1
View File
@@ -574,6 +574,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_TRANSFORM_ACTIVE" = TRAIT_TRANSFORM_ACTIVE,
"TRAIT_UNCATCHABLE" = TRAIT_UNCATCHABLE,
"TRAIT_WIELDED" = TRAIT_WIELDED,
"TRAIT_TORPOR" = TRAIT_TORPOR, // BUBBER EDIT
),
/obj/item/ammo_casing = list(
"TRAIT_DART_HAS_INSERT" = TRAIT_DART_HAS_INSERT,
@@ -15,16 +15,6 @@
if(prob(10)) //10% chance to drop the message entirely
return
message = Gibberish(message, emp_damage >= 12)//scrambles the message, gets worse when emp_damage is higher
// BUBBER CHANGE: funny talking head
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
return ..()
@@ -210,6 +210,7 @@
item_flags &= ~ABSTRACT
REMOVE_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT)
interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP
SEND_SIGNAL(src, COMSIG_ORGAN_BODYPART_REMOVED, limb, movement_flags) // BUBBER CHANGE, added COMSIG_ORGAN_BODYPART_REMOVED to on_bodypart_remove
/// In space station videogame, nothing is sacred. If somehow an organ is removed unexpectedly, handle it properly
/obj/item/organ/proc/forced_removal()
@@ -223,7 +224,7 @@
stack_trace("Force removed an already removed organ!")
/**
* Proc that gets called when the organ is surgically removed by someone, can be used for special effects
* Proc that gets called when the organ is surgic d for special effects
*/
/obj/item/organ/proc/on_surgical_removal(mob/living/user, mob/living/carbon/old_owner, target_zone, obj/item/tool)
SHOULD_CALL_PARENT(TRUE)
@@ -1,11 +1,14 @@
///Uncomment this to enable testing of Bloodsucker features (such as vassalizing people with a mind instead of a client).
// #define BLOODSUCKER_TESTING
// #define BLOODSUCKER_TESTING // if this isn't commented out, someone is a dumbfuck
/// 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
/// At what health to burn damage ratio you Final Death
#define FINAL_DEATH_HEALTH_TO_BURN 2.5
/**
* Blood-level defines
*/
@@ -17,6 +20,7 @@
#define TORTURE_CONVERSION_COST 10
/// How much blood it costs you to make a vassal into a special vassal
#define SPECIAL_VASSAL_COST 150
/// Minimum and maximum frenzy blood thresholds
/// Once blood is this low, will enter Frenzy
#define FRENZY_THRESHOLD_ENTER 25
/// Once blood is this high, will exit Frenzy
@@ -75,6 +79,7 @@
///Span prevention for Sol messages.
#define BLOODSUCKER_SPAM_SOL (30 SECONDS)
/**
* Clan defines
*/
@@ -97,16 +102,14 @@
*/
/// This Power can't be used in Torpor
#define BP_CANT_USE_IN_TORPOR (1<<0)
/// This Power can't be used while transformed, for example by the shapeshift spell
#define BP_CAN_USE_TRANSFORMED (1<<1)
/// 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_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
#define BP_CANT_USE_WHILE_UNCONSCIOUS (1<<5)
#define BP_CANT_USE_IN_FRENZY (1<<1)
/// This Power can be used while transformed, for example by the shapeshift spell
#define BP_CAN_USE_TRANSFORMED (1<<2)
/// This Power can be used with a stake in you
#define BP_CAN_USE_WHILE_STAKED (1<<4)
/// This Power can be used while heartless
#define BP_CAN_USE_HEARTLESS (1<<5)
/// This Power can be purchased by Bloodsuckers
#define BLOODSUCKER_CAN_BUY (1<<0)
@@ -117,8 +120,8 @@
/// This Power can be purchased by Vassals
#define VASSAL_CAN_BUY (1<<3)
/// This Power is a Toggled Power
#define BP_AM_TOGGLE (1<<0)
/// This Power is a Continuous Effect, processing every tick
#define BP_CONTINUOUS_EFFECT (1<<0)
/// This Power is a Single-Use Power
#define BP_AM_SINGLEUSE (1<<1)
/// This Power has a Static cooldown
@@ -126,6 +129,16 @@
/// This Power doesn't cost bloot to run while unconscious
#define BP_AM_COSTLESS_UNCONSCIOUS (1<<3)
#define DEACTIVATE_POWER_DO_NOT_REMOVE (1<<0)
#define DEACTIVATE_POWER_NO_COOLDOWN (1<<1)
// ability levels that are used cross-file
#define DOMINATE_VASSALIZE_LEVEL 2
#define TREMERE_OBJECTIVE_POWER_LEVEL 4
#define COFFIN_HEAL_COST_MULT 0.5
/**
* Torpor check bitflags
*/
@@ -137,29 +150,33 @@
* Bloodsucker Signals
*/
///Called when a Bloodsucker ranks up: (datum/bloodsucker_datum, mob/owner, mob/target)
#define BLOODSUCKER_RANK_UP "bloodsucker_rank_up"
#define COMSIG_BLOODSUCKER_RANK_UP "bloodsucker_rank_up"
///Called when a Bloodsucker interacts with a Vassal on their persuasion rack.
#define BLOODSUCKER_INTERACT_WITH_VASSAL "bloodsucker_interact_with_vassal"
#define COMSIG_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"
#define COMSIG_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"
#define COMSIG_BLOODSUCKER_LOOSE_FAVORITE "bloodsucker_loose_favorite"
///Called when a new Vassal is successfully made: (datum/bloodsucker_datum)
#define BLOODSUCKER_MADE_VASSAL "bloodsucker_made_vassal"
#define COMSIG_BLOODSUCKER_MADE_VASSAL "bloodsucker_made_vassal"
///Called when a Bloodsucker exits Torpor.
#define BLOODSUCKER_EXIT_TORPOR "bloodsucker_exit_torpor"
#define COMSIG_BLOODSUCKER_EXIT_TORPOR "bloodsucker_exit_torpor"
///Called when a Bloodsucker reaches Final Death.
#define BLOODSUCKER_FINAL_DEATH "bloodsucker_final_death"
#define COMSIG_BLOODSUCKER_FINAL_DEATH "bloodsucker_final_death"
///Whether the Bloodsucker should not be dusted when arriving Final Death
#define DONT_DUST (1<<0)
///Called when a Bloodsucker breaks the Masquerade
#define COMSIG_BLOODSUCKER_BROKE_MASQUERADE "comsig_bloodsucker_broke_masquerade"
///Called when a Bloodsucker enters Frenzy
#define BLOODSUCKER_ENTERS_FRENZY "bloodsucker_enters_frenzy"
#define COMSIG_BLOODSUCKER_ENTERS_FRENZY "bloodsucker_enters_frenzy"
///Called when a Bloodsucker exits Frenzy
#define BLOODSUCKER_EXITS_FRENZY "bloodsucker_exits_frenzy"
#define COMSIG_BLOODSUCKER_EXITS_FRENZY "bloodsucker_exits_frenzy"
/// COMSIG_ATOM_EXAMINE that correctly updates when the bloodsucker datum is moved
#define COMSIG_BLOODSUCKER_EXAMINE "bloodsucker_examine"
// Called when anyone enters the coffin
#define COMSIG_ENTER_COFFIN "comsig_enter_coffin"
#define COMSIG_ENTER_COFFIN "enter_coffin"
#define COMSIG_MOB_STAKED "staked"
#define COMSIG_BODYPART_STAKED "staked"
/**
* Sol signals & Defines
@@ -172,6 +189,9 @@
#define COMSIG_SOL_WARNING_GIVEN "comsig_sol_warning_given"
///Called on a Bloodsucker's Lifetick.
#define COMSIG_BLOODSUCKER_ON_LIFETICK "comsig_bloodsucker_on_lifetick"
/// Called when a Bloodsucker's blood is updated
#define BLOODSUCKER_UPDATE_BLOOD "bloodsucker_update_blood"
#define BLOODSUCKER_UPDATE_BLOOD_DISABLED (1<<0)
#define DANGER_LEVEL_FIRST_WARNING 1
#define DANGER_LEVEL_SECOND_WARNING 2
@@ -208,6 +228,7 @@
#define VASSAL_TRAIT "vassal_trait"
/// Source trait for dominate related traits
#define MESMERIZE_TRAIT "meserize_trait"
#define DOMINATE_TRAIT "dominate_trait"
/// Source trait for Monster Hunter-related traits
@@ -225,6 +246,7 @@
#define IS_FAVORITE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/favorite))
///Whether a mob is a Revenge Vassal
#define IS_REVENGE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/revenge))
#define IS_EX_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/ex_vassal))
///Whether a mob is a Monster Hunter-NOT NEEDED RIGHT NOW
// #define IS_MONSTERHUNTER(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/monsterhunter))
+1
View File
@@ -0,0 +1 @@
#define span_center(str) ("<span class='center'>[str]</span>")
@@ -0,0 +1,207 @@
/// UI obj holders for all your maptext needs
/atom/movable/screen/text
name = null
icon = null
icon_state = null
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
screen_loc = "CENTER-7,CENTER-7"
maptext_height = 480
maptext_width = 480
/// A screen object that shows the time left on a timer
/atom/movable/screen/text/screen_timer
screen_loc = "CENTER-7,CENTER-7"
/// The actual displayed content of the maptext, use ${timer}, and it'll be replaced with formatted time left
var/maptext_string
/// Timer ID that we're tracking, the time left of this is displayed as maptext
var/timer_id
/// The list of mobs in whose client.screens we are added to
var/list/timer_mobs = list()
/atom/movable/screen/text/screen_timer/Initialize(
mapload,
list/mobs,
timer,
text,
offset_x = 150,
offset_y = -70,
)
. = ..(mapload, null)
if(!islist(mobs) && mobs)
mobs = list(mobs)
// Copy the list just in case the arguments list is a list we don't want to modify
if(length(mobs))
mobs = mobs.Copy()
if(!timer)
return INITIALIZE_HINT_QDEL
maptext_string = text
timer_id = timer
maptext_x = offset_x
maptext_y = offset_y
update_maptext()
if(length(mobs))
apply_to(mobs)
/atom/movable/screen/text/screen_timer/process()
if(!timeleft(timer_id))
qdel(src)
return
update_maptext()
/// Adds the object to the client.screen of all mobs in the list, and registers the needed signals
/atom/movable/screen/text/screen_timer/proc/apply_to(list/mobs)
if(!mobs)
return
if(!islist(mobs))
mobs = list(mobs)
if(!length(timer_mobs) && length(mobs))
START_PROCESSING(SSprocessing, src)
for(var/player in mobs)
if(player in timer_mobs)
continue
if(istype(player, /datum/weakref))
var/datum/weakref/ref = player
player = ref.resolve()
attach(player)
RegisterSignal(player, COMSIG_MOB_LOGIN, PROC_REF(attach)) // doesn't currently cleanup properly
timer_mobs += WEAKREF(player)
/// Removes the object from the client.screen of all mobs in the list, and unregisters the needed signals, while also stopping processing if there's no more mobs in the screen timers mob list
/atom/movable/screen/text/screen_timer/proc/remove_from(list/mobs)
if(!mobs)
return
if(!islist(mobs))
mobs = list(mobs)
for(var/player in mobs)
// when the player is a weakref, assume it's the same pointer that we use in the timer_mobs list
var/datum/weakref/found_weakref
if(istype(player, /datum/weakref))
var/datum/weakref/ref = player
found_weakref = ref
// otherwise we have to search through and resolve each one and compare it
else
for(var/datum/weakref/possible_match as anything in timer_mobs)
if(player == possible_match.resolve())
found_weakref = possible_match
break
timer_mobs -= found_weakref
var/found_player = found_weakref.resolve()
if(!found_player)
return
UnregisterSignal(found_player, COMSIG_MOB_LOGIN)
de_attach(found_player)
if(!length(timer_mobs))
STOP_PROCESSING(SSprocessing, src)
/// Updates the maptext to show the current time left on the timer
/atom/movable/screen/text/screen_timer/proc/update_maptext()
var/time_formatted = time2text(timeleft(timer_id), "mm:ss")
var/timer_text = replacetextEx(maptext_string, "${timer}", time_formatted)
// If we don't find ${timer} in the string, just use the time formatted
var/result_text = MAPTEXT("[timer_text]")
apply_change(result_text)
/atom/movable/screen/text/screen_timer/proc/apply_change(result_text)
maptext = result_text
/// Adds the object to the client.screen of the mob, or removes it if add_to_screen is FALSE
/atom/movable/screen/text/screen_timer/proc/attach(mob/source, add_to_screen = TRUE)
SIGNAL_HANDLER
if(!source?.client)
return
var/client/client = source.client
// this checks if the screen is already added or removed
if(!can_attach(client, add_to_screen))
return
if(!ismob(source))
CRASH("Invalid source passed to screen_timer/attach()!")
do_attach(client, add_to_screen)
/atom/movable/screen/text/screen_timer/proc/can_attach(client/client, add_to_screen)
return add_to_screen == (src in client.screen)
/atom/movable/screen/text/screen_timer/proc/do_attach(client/client, add_to_screen)
if(add_to_screen)
client.screen += src
else
client.screen -= src
/// Signal handler to run attach with specific args
/atom/movable/screen/text/screen_timer/proc/de_attach(mob/source)
SIGNAL_HANDLER
attach(source, FALSE)
/atom/movable/screen/text/screen_timer/Destroy()
. = ..()
if(length(timer_mobs))
remove_from(timer_mobs)
STOP_PROCESSING(SSprocessing, src)
/atom/movable/screen/text/screen_timer/attached
maptext_x = 0
maptext_y = 16
maptext_height = 32
maptext_width = 32
var/following_object
var/image/text_image
/atom/movable/screen/text/screen_timer/attached/Initialize(
mapload,
list/mobs,
timer,
text,
offset_x,
offset_y,
following_object,
)
if(following_object && get_turf(following_object))
attach_self_to(following_object, offset_x, offset_y)
else
return INITIALIZE_HINT_QDEL
. = ..()
/atom/movable/screen/text/screen_timer/attached/can_attach(client/client)
return !(src in client.images)
// attached screen timers are a visible timer in the gameworld that are only visible to the mobs listed in the timer_mobs list
/atom/movable/screen/text/screen_timer/attached/do_attach(client/client, add_to_screen)
if(add_to_screen)
client.images += text_image
else
client.images -= text_image
/atom/movable/screen/text/screen_timer/attached/proc/attach_self_to(atom/movable/target, maptext_x, maptext_y)
text_image = image(src, target)
text_image.maptext_x = maptext_x
text_image.maptext_y = maptext_y
text_image.maptext_height = maptext_height
text_image.maptext_width = maptext_width
SET_PLANE_EXPLICIT(text_image, ABOVE_HUD_PLANE, target)
/atom/movable/screen/text/screen_timer/attached/apply_change(result_text)
..()
text_image?.maptext = result_text
/atom/movable/screen/text/screen_timer/attached/proc/hide_timer(atom/movable/target)
unregister_follower()
/atom/movable/screen/text/screen_timer/attached/proc/unregister_follower()
following_object = null
text_image = null
/atom/movable/screen/text/screen_timer/attached/proc/update_glide_speed(atom/movable/tracked)
set_glide_size(tracked.glide_size)
/atom/movable/screen/text/screen_timer/attached/proc/timer_follow(atom/movable/tracked, atom/mover, atom/oldloc, direction)
abstract_move(get_turf(tracked))
/atom/movable/screen/text/screen_timer/attached/Destroy()
. = ..()
if(following_object)
unregister_follower()
@@ -13,7 +13,7 @@ PROCESSING_SUBSYSTEM_DEF(sunlight)
var/time_til_cycle = TIME_BLOODSUCKER_NIGHT_MAX
///If Bloodsucker levels for the night has been given out yet.
var/issued_XP = FALSE
/// Mobs that make use of the sunlight system.
/// Mobs that make use of the sunlight system, doesn't use weakrefs as that makes removing them a pain, and we already cleanup on qdel.
var/list/sun_sufferers = list()
/datum/controller/subsystem/processing/sunlight/fire(resumed = FALSE)
@@ -72,31 +72,33 @@ PROCESSING_SUBSYSTEM_DEF(sunlight)
/datum/controller/subsystem/processing/sunlight/proc/warn_daylight(danger_level, vampire_warning_message, vassal_warning_message)
SEND_SIGNAL(src, COMSIG_SOL_WARNING_GIVEN, danger_level, vampire_warning_message, vassal_warning_message)
/datum/controller/subsystem/processing/sunlight/proc/add_sun_sufferer(mob/victim)
var/sufferer_list = is_sufferer(victim)
if(sufferer_list)
if(is_sufferer(victim))
return FALSE
var/atom/movable/screen/bloodsucker/sunlight_counter/sun_hud = new(null, victim.hud_used)
victim.hud_used.infodisplay += sun_hud
victim.hud_used.show_hud(victim.hud_used.hud_version)
sun_hud.update_sol_hud()
sun_sufferers += list(list("victim" = WEAKREF(victim), "sun_hud" = sun_hud))
RegisterSignal(victim, COMSIG_QDELETING, PROC_REF(remove_sun_sufferer), victim)
RegisterSignal(sun_hud, COMSIG_QDELETING, PROC_REF(remove_sun_sufferer), victim)
sun_sufferers[victim] = sun_hud
if(length(sun_sufferers))
can_fire = TRUE
return TRUE
/datum/controller/subsystem/processing/sunlight/proc/signal_remove_sun_sufferer(subsystem, mob/victim)
remove_sun_sufferer(victim)
/datum/controller/subsystem/processing/sunlight/proc/remove_sun_sufferer(mob/victim)
var/sufferer_list = is_sufferer(victim)
if(!sufferer_list)
if(!is_sufferer(victim))
return FALSE
var/atom/movable/screen/bloodsucker/sunlight_counter/sun_hud = sufferer_list["sun_hud"]
var/atom/movable/screen/bloodsucker/sunlight_counter/sun_hud = sun_sufferers[victim]
if(sun_hud)
victim?.hud_used.infodisplay -= sun_hud
UnregisterSignal(sun_hud, COMSIG_QDELETING)
qdel(sun_hud)
// We have to use the index here as -= won't work with two lists
sun_sufferers.Cut(sufferer_list["index"])
sun_sufferers -= victim
UnregisterSignal(victim, COMSIG_QDELETING)
if(!length(sun_sufferers))
can_fire = FALSE
sunlight_active = initial(sunlight_active)
@@ -121,18 +123,12 @@ PROCESSING_SUBSYSTEM_DEF(sunlight)
if(DANGER_LEVEL_SOL_ENDED)
target.playsound_local(null, 'sound/misc/ghosty_wind.ogg', 90, TRUE)
/datum/controller/subsystem/processing/sunlight/proc/is_sufferer(mob/victim)
for(var/i in 1 to length(sun_sufferers))
var/list/sufferers_original = sun_sufferers[i]
var/list/sufferer_list = sufferers_original?.Copy()
if(!sufferer_list)
continue
sufferer_list["index"] = i
var/datum/weakref/sufferer_ref = sufferer_list["victim"]
if(!sufferer_ref)
continue
var/sufferer = sufferer_ref.resolve()
if(sufferer == victim)
return sufferer_list
return null
if(!sun_sufferers)
CRASH("Sol subsystem sun_sufferers list is null, when it should never be.")
if(isnull(victim) || !length(sun_sufferers))
return FALSE
if(sun_sufferers[victim])
return TRUE
return FALSE
@@ -0,0 +1,7 @@
/datum/material/silver/on_applied(atom/source, amount, material_flags)
. = ..()
source.AddElement(/datum/element/bane, mob_biotypes = MOB_VAMPIRIC, damage_multiplier = 0.5)
/datum/material/silver/on_removed(atom/source, amount, material_flags)
. = ..()
source.RemoveElement(/datum/element/bane, mob_biotypes = MOB_VAMPIRIC, damage_multiplier = 0.5)
@@ -51,3 +51,11 @@
description = "<span class='boldwarning'>Something is making your mind feel... loose.</span>\n"
mood_change = -15
timeout = 5 MINUTES
/datum/mood_event/nosferatu_examined
mood_change = -10
timeout = 5 MINUTES
/datum/mood_event/nosferatu_examined/add_effects(target, level = 0)
description = span_danger("You feel a deep sense of revulsion at the sight of [target].")
mood_change = level * -5
@@ -11,8 +11,10 @@
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
if(istype(power, /datum/action/cooldown/bloodsucker))
var/datum/action/cooldown/bloodsucker/bloodsucker_power = power
power_data["power_explanation"] = bloodsucker_power.get_power_explanation()
data["powers"] += list(power_data)
return data
@@ -55,7 +55,7 @@
///How many ranks we have, don't modify this directly, use AdjustRank() and use GetRank() to get the current value.
VAR_PRIVATE/bloodsucker_level = 0
/// Unspent ranks, don't modify this directly, use AdjustUnspentRanks() and use GetUnspentRanks() to get the current value.
/// Unspent ranks, don't modify this directly, use AdjustUnspentRank() and use GetUnspentRank() to get the current value.
VAR_PRIVATE/bloodsucker_level_unspent = 1
var/additional_regen
var/blood_over_cap = 0
@@ -106,10 +106,6 @@
TRAIT_STABLELIVER
)
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
@@ -123,8 +119,10 @@
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))
RegisterSignal(current_mob, COMSIG_QDELETING, PROC_REF(on_owner_deletion))
RegisterSignal(current_mob, COMSIG_ENTER_COFFIN, PROC_REF(on_enter_coffin))
RegisterSignal(current_mob, COMSIG_MOB_STAKED, PROC_REF(on_staked))
RegisterSignal(current_mob, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_organ_removal))
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)
@@ -136,9 +134,6 @@
RegisterSignal(current_mob, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
if(ishuman(current_mob))
current_mob?.dna?.species.on_bloodsucker_gain(current_mob)
add_signals_to_heart(current_mob)
// check if we already somehow don't have a heart, if this is possible, something is fucked up.
on_organ_removal(null, current_mob)
#ifdef BLOODSUCKER_TESTING
var/turf/user_loc = get_turf(current_mob)
new /obj/structure/closet/crate/coffin(user_loc)
@@ -153,8 +148,7 @@
/datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override)
. = ..()
var/mob/living/carbon/current_mob = mob_override || owner.current
remove_signals_from_heart(current_mob)
UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_ATOM_EXAMINE, COMSIG_LIVING_DEATH, COMSIG_SPECIES_GAIN, COMSIG_QDELETING))
unregister_body_signals()
handle_clown_mutation(current_mob, removing = FALSE)
if(current_mob.hud_used)
var/datum/hud/hud_used = current_mob.hud_used
@@ -164,7 +158,8 @@
QDEL_NULL(vamprank_display)
SSsunlight.remove_sun_sufferer(owner.current) //check if sunlight should end
current_mob.dna?.species?.on_bloodsucker_loss(current_mob)
if(iscarbon(current_mob))
current_mob?.dna.species.on_bloodsucker_loss(current_mob)
if(current_mob.client)
// We need to let the bloodsucker antag datum get removed before we can re-add quirks
addtimer(CALLBACK(SSquirks, TYPE_PROC_REF(/datum/controller/subsystem/processing/quirks, AssignQuirks), current_mob, current_mob.client), 1 SECONDS)
@@ -191,14 +186,17 @@
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)
.["Give Level"] = CALLBACK(src, PROC_REF(admin_rankup))
// I know admins can technically do it via VV's dropdown, but it's super inconvenient.
.["Give Power"] = CALLBACK(src, PROC_REF(admin_give_power))
.["Remove Power"] = CALLBACK(src, PROC_REF(admin_remove_power))
.["Set Power Level"] = CALLBACK(src, PROC_REF(admin_set_power_level))
if(bloodsucker_level_unspent >= 1)
.["Remove Level"] = CALLBACK(src, PROC_REF(RankDown))
@@ -221,9 +219,8 @@
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(ventrue_sired) // sired bloodsuckers shouldnt be getting the same benefits as Bloodsuckers.
if(ventrue_sired) // sired bloodsuckers shouldnt be getting the same benefits as roundstart Bloodsuckers.
bloodsucker_level_unspent = 0
show_in_roundend = FALSE
else
// Start Sunlight if first Bloodsucker
// Name and Titles
@@ -243,11 +240,15 @@
free_all_vassals()
if(!owner?.current)
return
unregister_sol_signals()
if(is_head(owner.current))
cleanup_talking_head()
if(ishuman(owner.current))
var/mob/living/carbon/human/user = owner.current
user?.dna?.species.regenerate_organs(user, null, TRUE)
clear_powers_and_stats()
ventrue_sired = null
coffin?.unclaim_coffin(FALSE, TRUE)
return ..()
/datum/antagonist/bloodsucker/on_body_transfer(mob/living/old_body, mob/living/new_body)
@@ -317,7 +318,7 @@
// Called when using admin tools to give antag status
/datum/antagonist/bloodsucker/admin_add(datum/mind/new_owner, mob/admin)
var/levels = input("How many unspent Ranks would you like [new_owner] to have?","Bloodsucker Rank", bloodsucker_level_unspent) as null | num
var/levels = tgui_input_number(admin, "How many unspent Ranks would you like [new_owner] to have?","Bloodsucker Rank", GetUnspentRank(), 100, 0)
var/msg = " made [key_name_admin(new_owner)] into \a [name]"
if(levels > 1)
bloodsucker_level_unspent = levels
@@ -454,8 +455,8 @@
*/
/datum/antagonist/bloodsucker/proc/clear_powers_and_stats()
// Remove clan first
if(my_clan)
QDEL_NULL(my_clan)
// if(my_clan)
// QDEL_NULL(my_clan)
// Powers
for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
RemovePower(all_powers)
@@ -494,6 +495,8 @@
/datum/antagonist/bloodsucker/proc/remove_invalid_quirks()
var/datum/quirk/bad_quirk = owner.current.get_quirk(/datum/quirk/sol_weakness)
if(!bad_quirk)
return
// silently remove the quirk if it's not valid
bad_quirk.remove_from_current_holder(TRUE)
owner.current.remove_quirk(/datum/quirk/sol_weakness)
@@ -509,6 +512,15 @@
return "<font color=red>Final Death</font>"
return ..()
/datum/antagonist/bloodsucker/proc/considered_alive(datum/mind/player_mind, enforce_human)
if(!player_mind?.current) // no owner.current means there is no body, thus we final-death'd
return FALSE
if(is_head(player_mind.current))
return FALSE
if(am_staked())
return FALSE
return TRUE
/datum/antagonist/bloodsucker/proc/forge_bloodsucker_objectives()
// Claim a Lair Objective
var/datum/objective/bloodsucker/lair/lair_objective = new
@@ -37,7 +37,7 @@
var/mob/living/master = conversion_target.mind.enslaved_to?.resolve()
if(!master || (master == owner.current))
return TRUE
var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(master)
if(bloodsuckerdatum && bloodsuckerdatum.broke_masquerade)
//vassal stealing
return TRUE
@@ -51,14 +51,16 @@
* conversion_target - The person converted.
*/
/datum/antagonist/bloodsucker/proc/make_vassal(mob/living/conversion_target)
#ifndef BLOODSUCKER_TESTING
if(!can_make_vassal(conversion_target))
return FALSE
#endif
//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)
var/datum/antagonist/vassal/old_vassal = IS_VASSAL(conversion_target)
if(old_vassal)
conversion_target.mind.remove_antag_datum(/datum/antagonist/vassal)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner.current)
bloodsuckerdatum.SelectTitle(am_fledgling = FALSE)
//set the master, then give the datum.
@@ -24,7 +24,7 @@
. = ..()
if(!mind)
return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(src)
if(!bloodsuckerdatum)
return
bloodsuckerdatum.AdjustBloodVolume(-amount)
@@ -58,7 +58,7 @@
. = ..()
if(!mind)
return ..()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(src)
if(bloodsuckerdatum)
. += ""
. += "Blood Drank: [bloodsuckerdatum.total_blood_drank]"
@@ -118,9 +118,3 @@
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,25 +4,31 @@
/// 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(!owner)
if(isnull(owner) || isnull(owner.current))
INVOKE_ASYNC(src, PROC_REF(HandleDeath))
return
if(isbrain(owner.current))
return
if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
life_always()
var/is_head = is_head(owner.current)
if(!is_head && owner.current.get_organ_slot(ORGAN_SLOT_HEART) && !am_staked())
life_active(is_head)
SEND_SIGNAL(src, COMSIG_BLOODSUCKER_ON_LIFETICK, seconds_per_tick, times_fired)
/datum/antagonist/bloodsucker/proc/life_always(is_head = FALSE)
if(is_in_torpor())
check_end_torpor()
// Deduct Blood
if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_IMMOBILIZED) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
if(is_head)
return
if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_IMMOBILIZED) && !is_in_torpor())
INVOKE_ASYNC(src, PROC_REF(AdjustBloodVolume), -BLOODSUCKER_PASSIVE_BLOOD_DRAIN) // -.1 currently
INVOKE_ASYNC(src, PROC_REF(update_blood))
INVOKE_ASYNC(src, PROC_REF(HandleStarving))
/datum/antagonist/bloodsucker/proc/life_active()
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
INVOKE_ASYNC(src, PROC_REF(HandleStarving))
INVOKE_ASYNC(src, PROC_REF(update_blood))
SEND_SIGNAL(src, COMSIG_BLOODSUCKER_ON_LIFETICK, seconds_per_tick, times_fired)
/datum/antagonist/bloodsucker/proc/on_death(mob/living/source, gibbed)
SIGNAL_HANDLER
@@ -117,9 +123,9 @@
/// Constantly runs on Bloodsucker's LifeTick, and is increased by being in Torpor/Coffins
/datum/antagonist/bloodsucker/proc/HandleHealing(mult = 1)
// Don't heal if I'm staked or on Masquerade (+ not in a Coffin). Masqueraded Bloodsuckers in a Coffin however, will heal.
// Don't heal if I'm staked or on Masquerade.
var/actual_regen = bloodsucker_regen_rate + additional_regen
if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)))
if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE)))
return FALSE
// Garlic in you? No healing for you!
if(HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT))
@@ -134,17 +140,17 @@
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)
var/amInCoffin = is_valid_coffin()
if (blood_over_cap > 0)
costMult += round(blood_over_cap / 1000, 0.1) // effectively 1 (normal) + 0.1 for every 100 blood you are over cap
if(amInCoffin && HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
if(amInCoffin && is_in_torpor())
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(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.
costMult *= COFFIN_HEAL_COST_MULT // Decrease cost if we're sleeping in a coffin.
user.extinguish_mob()
user.bodytemperature = user.get_body_temp_normal()
if(ishuman(user))
@@ -154,7 +160,7 @@
if(check_limbs(costMult))
return TRUE
// In Torpor, but not in a Coffin? Heal faster anyways.
else if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
else if(is_in_torpor())
var/fireloss = getFireLoss()
fireheal = min(fireloss, actual_regen) / 1.2 // 20% slower than being in a coffin
mult *= 3
@@ -206,42 +212,49 @@
*
* This is called on Bloodsucker's Assign, and when they end Torpor.
*/
/// TODO: Separate this into smaller functions
/datum/antagonist/bloodsucker/proc/heal_vampire_organs(regenerate_heart = FALSE)
// TODO: Separate this into smaller functions
/datum/antagonist/bloodsucker/proc/heal_vampire_organs()
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)
if(regenerate_heart || bloodsuckeruser.get_organ_slot(ORGAN_SLOT_HEART))
if(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()
current_heart?.Stop()
var/obj/item/organ/internal/eyes/current_eyes = bloodsuckeruser.get_organ_slot(ORGAN_SLOT_EYES)
if(current_eyes)
if(current_eyes && !(current_eyes.organ_flags & ORGAN_ROBOTIC))
current_eyes.flash_protect = max(initial(current_eyes.flash_protect) - 1, FLASH_PROTECTION_SENSITIVE)
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()
if(bloodsuckeruser.stat == DEAD)
bloodsuckeruser.revive()
for(var/datum/wound/iter_wound as anything in bloodsuckeruser.all_wounds)
. = bloodsuckeruser.revive()
for(var/datum/wound/iter_wound in bloodsuckeruser.all_wounds)
iter_wound.remove_wound()
// From [powers/panacea.dm]
var/list/bad_organs = list(
bloodsuckeruser.get_organ_by_type(/obj/item/organ/internal/body_egg),
@@ -263,12 +276,13 @@
/// FINAL DEATH
/datum/antagonist/bloodsucker/proc/HandleDeath()
// Not "Alive"?
if(!owner.current)
FinalDeath()
if(isnull(owner.current))
if(length(vassals))
free_all_vassals()
vassals = list()
return
// Fire Damage? (above double health)
if(owner.current.getFireLoss() >= owner.current.maxHealth * 2.5)
if(owner.current.getFireLoss() >= owner.current.maxHealth * FINAL_DEATH_HEALTH_TO_BURN) // 337.5 burn with 135 maxHealth
FinalDeath()
return
// Staked with a silver stake while "Temp Death" or Asleep
@@ -276,13 +290,8 @@
FinalDeath()
return
// Temporary Death? Convert to Torpor.
if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT) || isbrain(owner.current))
if(is_in_torpor() || isbrain(owner.current))
return
// 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!
@@ -297,7 +306,7 @@
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_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD && prob(0.5) && !is_in_torpor() && !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)
@@ -322,6 +331,8 @@
/// Makes your blood_volume look like your bloodsucker blood, unless you're Masquerading.
/datum/antagonist/bloodsucker/proc/update_blood()
if(SEND_SIGNAL(src, BLOODSUCKER_UPDATE_BLOOD) & BLOODSUCKER_UPDATE_BLOOD_DISABLED)
return
if(HAS_TRAIT(owner.current, TRAIT_NOBLOOD))
return
//If we're on Masquerade, we appear to have full blood, unless we are REALLY low, in which case we don't look as bad.
@@ -341,35 +352,51 @@
/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())
var/obj/item/bodypart/head/head = is_head(poor_fucker)
if(!head || 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)
if(istype(poor_fucker.loc, /obj/item/organ/internal/brain))
RegisterSignal(poor_fucker.loc, COMSIG_QDELETING, PROC_REF(on_brain_remove))
RegisterSignal(poor_fucker.loc, COMSIG_ORGAN_BODYPART_REMOVED, PROC_REF(on_brain_remove))
RegisterSignal(poor_fucker, COMSIG_MOB_TRY_SPEECH, PROC_REF(allow_head_to_talk))
RegisterSignal(poor_fucker, COMSIG_MOB_SAY, PROC_REF(shake_head_on_talk))
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
/datum/antagonist/bloodsucker/proc/cleanup_talking_head(obj/item/organ/brain)
var/mob/living/poor_fucker = owner.current
if(brain)
UnregisterSignal(brain, list(COMSIG_QDELETING, COMSIG_ORGAN_BODYPART_REMOVED))
// fucked up if this happens, but we're probably final deathed at this point
if(!poor_fucker)
return
UnregisterSignal(poor_fucker, list(COMSIG_MOB_TRY_SPEECH, COMSIG_MOB_SAY, COMSIG_QDELETING))
poor_fucker.death()
poor_fucker.speech_span = initial(poor_fucker.speech_span)
/datum/antagonist/bloodsucker/proc/on_brain_remove(obj/item/organ/brain)
SIGNAL_HANDLER
cleanup_talking_head(brain)
/datum/antagonist/bloodsucker/proc/on_brainmob_qdel()
SIGNAL_HANDLER
if(istype(owner.current.loc, /obj/item/organ/internal/brain))
cleanup_talking_head(owner.current.loc)
else
cleanup_talking_head()
/// Gibs the Bloodsucker, roundremoving them.
/datum/antagonist/bloodsucker/proc/FinalDeath(check_organs = FALSE)
SIGNAL_HANDLER
// If we have no body, end here.
if(!owner.current || isbrain(owner.current))
return
UnregisterSignal(src, list(
COMSIG_BLOODSUCKER_ON_LIFETICK,
COMSIG_LIVING_REVIVE,
COMSIG_LIVING_LIFE,
COMSIG_LIVING_DEATH,
))
UnregisterSignal(SSsunlight, list(
COMSIG_SOL_RANKUP_BLOODSUCKERS,
COMSIG_SOL_NEAR_START,
COMSIG_SOL_END,
COMSIG_SOL_RISE_TICK,
COMSIG_SOL_WARNING_GIVEN,
))
unregister_body_signals()
unregister_sol_signals()
free_all_vassals()
DisableAllPowers(forced = TRUE)
if(!iscarbon(owner.current))
@@ -382,7 +409,7 @@
user.remove_all_embedded_objects()
playsound(owner.current, 'sound/effects/tendril_destroyed.ogg', 40, TRUE)
var/unique_death = SEND_SIGNAL(src, BLOODSUCKER_FINAL_DEATH)
var/unique_death = SEND_SIGNAL(src, COMSIG_BLOODSUCKER_FINAL_DEATH)
if(unique_death & DONT_DUST)
return
@@ -31,7 +31,7 @@
/// Check Vassals and get their occupations
/datum/objective/bloodsucker/proc/get_vassal_occupations()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner.current)
if(!bloodsuckerdatum || !bloodsuckerdatum.vassals.len)
return FALSE
var/list/all_vassal_jobs = list()
@@ -70,7 +70,7 @@
// WIN CONDITIONS?
/datum/objective/bloodsucker/lair/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner.current)
if(bloodsuckerdatum && bloodsuckerdatum.coffin && bloodsuckerdatum.bloodsucker_lair_area)
return TRUE
return FALSE
@@ -83,8 +83,15 @@
name = "bloodsuckersurvive"
explanation_text = "Survive the entire shift without succumbing to Final Death."
// WIN CONDITIONS?
// Handled by parent
/datum/objective/survive/bloodsucker/check_completion()
var/list/datum/mind/owners = get_owners()
for(var/datum/mind/mind in owners)
var/datum/antagonist/bloodsucker/vamp = IS_BLOODSUCKER(mind.current)
if(!vamp)
return FALSE
if(!vamp.considered_alive(mind))
return FALSE
return TRUE
//////////////////////////////////////////////////////////////////////////////////////
@@ -200,7 +207,7 @@
// WIN CONDITIONS?
/datum/objective/bloodsucker/gourmand/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner.current)
if(!bloodsuckerdatum)
return FALSE
var/stolen_blood = bloodsuckerdatum.total_blood_drank
@@ -229,7 +236,7 @@
/datum/objective/bloodsucker/kindred/check_completion()
if(!owner.current)
return FALSE
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner.current)
if(!bloodsuckerdatum)
return FALSE
@@ -244,16 +251,19 @@
/// Max out a Tremere Power - Tremere Clan objective
/datum/objective/bloodsucker/tremere_power
name = "tremerepower"
var/power_level = TREMERE_OBJECTIVE_POWER_LEVEL
// EXPLANATION
/datum/objective/bloodsucker/tremere_power/update_explanation_text()
explanation_text = "Upgrade a Blood Magic power to the maximum level, remember that Vassalizing gives more Ranks!"
explanation_text = "Upgrade a Blood Magic power to level [power_level], remember that Vassalizing gives more Ranks!"
// WIN CONDITIONS?
/datum/objective/bloodsucker/tremere_power/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
for(var/datum/action/cooldown/bloodsucker/targeted/tremere/tremere_powers in bloodsuckerdatum.powers)
if(tremere_powers.level_current >= 5)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner.current)
if(!bloodsuckerdatum)
return FALSE
for(var/datum/action/cooldown/bloodsucker/tremere_powers in bloodsuckerdatum.powers)
if(tremere_powers.purchase_flags & TREMERE_CAN_BUY && tremere_powers.level_current >= power_level)
return TRUE
return FALSE
@@ -270,7 +280,7 @@
// WIN CONDITIONS?
/datum/objective/bloodsucker/embrace/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner.current)
if(!bloodsuckerdatum)
return FALSE
for(var/datum/antagonist/bloodsucker/sired_vamp in GLOB.antagonists)
@@ -6,6 +6,7 @@
var/vamp_examine = return_vamp_examine(examiner)
if(vamp_examine)
examine_text += vamp_examine
SEND_SIGNAL(src, COMSIG_BLOODSUCKER_EXAMINE, source, examiner, examine_text)
/datum/antagonist/bloodsucker/proc/BuyPowers(powers = list())
for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
@@ -15,12 +16,12 @@
/datum/antagonist/bloodsucker/proc/BuyPower(datum/action/cooldown/bloodsucker/power)
for(var/datum/action/cooldown/bloodsucker/current_powers as anything in powers)
if(current_powers.type == power.type)
return FALSE
return null
power = new power()
powers += power
power.Grant(owner.current)
log_uplink("[key_name(owner.current)] purchased [power].")
return TRUE
return power
///Called when a Bloodsucker loses a power: (power)
/datum/antagonist/bloodsucker/proc/RemovePower(datum/action/cooldown/bloodsucker/power)
@@ -72,7 +73,7 @@
to_chat(owner.current, span_notice("You have gained a rank. Join a Clan to spend it."))
return
// Spend Rank Immediately?
if(!istype(owner.current.loc, /obj/structure/closet/crate/coffin))
if(!is_valid_coffin())
to_chat(owner, span_notice("<EM>You have grown more ancient! Sleep in a coffin (or put your Favorite Vassal on a persuasion rack for Ventrue) that you have claimed to thicken your blood and become more powerful.</EM>"))
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."))
@@ -99,14 +100,14 @@
///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_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)))
if(forced || ((power.check_flags & BP_CANT_USE_IN_TORPOR) && is_in_torpor()))
if(power.active)
power.DeactivatePower()
/datum/antagonist/bloodsucker/proc/SpendRank(mob/living/carbon/human/target, cost_rank = TRUE, blood_cost)
if(!owner || !owner.current || !owner.current.client || (cost_rank && bloodsucker_level_unspent <= 0))
return
SEND_SIGNAL(src, BLOODSUCKER_RANK_UP, target, cost_rank, blood_cost)
SEND_SIGNAL(src, COMSIG_BLOODSUCKER_RANK_UP, target, cost_rank, blood_cost)
/datum/antagonist/bloodsucker/proc/GetRank()
return bloodsucker_level
@@ -128,10 +129,10 @@
/datum/antagonist/bloodsucker/proc/free_all_vassals()
for(var/datum/antagonist/vassal/all_vassals in vassals)
// Skip over any Bloodsucker Vassals, they're too far gone to have all their stuff taken away from them
if(all_vassals.owner.has_antag_datum(/datum/antagonist/bloodsucker))
if(IS_BLOODSUCKER(all_vassals.owner.current))
all_vassals.owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
continue
if(all_vassals.special_type == REVENGE_VASSAL)
if(all_vassals.special_type == REVENGE_VASSAL || !all_vassals.owner)
continue
all_vassals.owner.add_antag_datum(/datum/antagonist/ex_vassal)
all_vassals.owner.remove_antag_datum(/datum/antagonist/vassal)
@@ -196,58 +197,16 @@
/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)
heart = null
/datum/antagonist/bloodsucker/proc/on_organ_removal(obj/item/organ/organ, mob/living/carbon/old_owner)
/datum/antagonist/bloodsucker/proc/on_organ_removal(mob/living/carbon/old_owner, obj/item/organ/organ, special)
SIGNAL_HANDLER
if(old_owner.get_organ_slot(ORGAN_SLOT_HEART) || organ?.slot != ORGAN_SLOT_HEART || !old_owner.dna.species.mutantheart)
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 ? organ.slot : "heart"]!"))
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), TRUE) // overriding here due to the fact this can without removing the signal due to before_organ_replace()
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))
@@ -273,9 +232,152 @@
return
SetBloodVolume(blood)
/datum/antagonist/bloodsucker/proc/regain_heart(mob/coffin_dweller, obj/structure/closet/crate/coffin/coffin, mob/user)
SIGNAL_HANDLER
/datum/antagonist/bloodsucker/proc/admin_rankup(mob/admin)
to_chat(admin, span_notice("[owner.current] has been given a free level"))
RankUp()
/datum/antagonist/bloodsucker/proc/admin_give_power(mob/admin)
var/power_type = tgui_input_list(admin, "What power to give [owner.current]?", "Might is right.", all_bloodsucker_powers)
if(!power_type)
return
var/datum/action/cooldown/bloodsucker/power = BuyPower(power_type)
power.upgrade_power()
/datum/antagonist/bloodsucker/proc/admin_remove_power(mob/admin)
var/datum/action/cooldown/bloodsucker/power = tgui_input_list(admin, "What power to remove from [owner.current]?", "Might is right.", powers)
if(!power)
return
RemovePower(power)
/datum/antagonist/bloodsucker/proc/admin_set_power_level(mob/admin)
var/list/valid_powers = list()
for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
if(power.purchase_flags & BLOODSUCKER_DEFAULT_POWER)
continue
valid_powers += power
var/datum/action/cooldown/bloodsucker/power = tgui_input_list(admin, "What power to set the level of for [owner.current]?", "Might is right.", valid_powers)
if(!power)
return
var/level = tgui_input_number(admin, "What level to set [power] to?", "Might is right.", power.level_current, 30, 0)
if(level == null)
return
power.level_current = level
power.on_power_upgrade()
/datum/antagonist/bloodsucker/proc/regain_heart(mob/living/carbon/target, obj/structure/closet/crate/coffin/coffin)
var/obj/item/organ/heart = locate(/obj/item/organ/internal/heart) in coffin.contents
if(heart && !coffin_dweller.get_organ_slot(ORGAN_SLOT_HEART))
if(heart && !target.get_organ_slot(ORGAN_SLOT_HEART) && heart.Insert(target))
to_chat(span_warning("You have regained your heart!"))
heart.Insert(coffin_dweller)
/datum/antagonist/bloodsucker/proc/allow_head_to_talk(mob/speaker, message, ignore_spam, forced)
SIGNAL_HANDLER
if(!is_head(speaker) || speaker.stat >= UNCONSCIOUS)
return
return COMPONENT_IGNORE_CAN_SPEAK
/datum/antagonist/bloodsucker/proc/shake_head_on_talk(mob/speaker, speech_args)
SIGNAL_HANDLER
var/obj/head = is_head(speaker)
if(!head)
return
var/animation_time = max(2, length_char(speech_args[SPEECH_MESSAGE]) * 0.5)
head.Shake(duration = animation_time)
/datum/antagonist/bloodsucker/proc/stake_can_kill()
if(owner.current.IsSleeping() || owner.current.stat >= UNCONSCIOUS || is_in_torpor())
for(var/stake in get_stakes())
var/obj/item/stake/killin_stake = stake
if(killin_stake?.kills_blodsuckers)
return TRUE
return FALSE
/datum/antagonist/bloodsucker/proc/am_staked()
var/obj/item/bodypart/chosen_bodypart = owner.current.get_bodypart(BODY_ZONE_CHEST)
if(!chosen_bodypart)
return FALSE
var/obj/item/stake/stake = locate() in chosen_bodypart.embedded_objects
return stake
/datum/antagonist/bloodsucker/proc/get_stakes()
var/obj/item/bodypart/chosen_bodypart = owner.current.get_bodypart(BODY_ZONE_CHEST)
if(!chosen_bodypart)
return FALSE
var/list/stakes = list()
for(var/obj/item/embedded_stake in chosen_bodypart.embedded_objects)
if(istype(embedded_stake, /obj/item/stake))
stakes += list(embedded_stake)
return stakes
/datum/antagonist/bloodsucker/proc/on_staked(atom/target, forced)
SIGNAL_HANDLER
if(stake_can_kill())
FinalDeath()
else
to_chat(target, span_userdanger("You have been staked! Your powers are useless, your death forever, while it remains in place."))
target.balloon_alert(target, "you have been staked!")
/// is it something that is close enough to a coffin to let us heal/level up in it?
/datum/antagonist/bloodsucker/proc/is_valid_coffin()
if(istype(owner.current.loc, /obj/structure/closet/crate/coffin))
return TRUE
// if(istype(owner.current.loc, /obj/structure/closet/crate/grave))
// return TRUE
return FALSE
/datum/antagonist/bloodsucker/proc/on_enter_coffin(mob/living/carbon/target, obj/structure/closet/crate/coffin/coffin, mob/living/carbon/user)
SIGNAL_HANDLER
check_limbs(COFFIN_HEAL_COST_MULT)
regain_heart(target, coffin)
if(!check_begin_torpor())
heal_vampire_organs()
if(user == owner.current && (user in coffin))
if(can_claim_coffin(coffin, get_area(coffin)))
INVOKE_ASYNC(src, PROC_REF(try_claim_coffin), coffin)
else
INVOKE_ASYNC(src, PROC_REF(try_coffin_level_up))
/datum/antagonist/bloodsucker/proc/try_claim_coffin(obj/structure/closet/crate/coffin/coffin)
if(coffin.prompt_coffin_claim(src))
try_coffin_level_up()
/datum/antagonist/bloodsucker/proc/try_coffin_level_up()
var/mob/living/carbon/user = owner.current
//Level up if possible.
if(!my_clan)
user.balloon_alert(user, "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 if(!frenzied)
if(GetUnspentRank() < 1)
blood_level_gain()
// Level ups cost 30% of your max blood volume, which scales with your rank.
SpendRank(blood_cost = max_blood_volume * BLOODSUCKER_LEVELUP_PERCENTAGE)
/datum/antagonist/bloodsucker/proc/on_owner_deletion(mob/living/deleted_mob)
SIGNAL_HANDLER
free_all_vassals()
if(deleted_mob != owner.current)
return
if(is_head(deleted_mob))
on_brainmob_qdel()
/datum/antagonist/bloodsucker/proc/unregister_body_signals()
UnregisterSignal(owner.current, list(
COMSIG_LIVING_LIFE,
COMSIG_ATOM_EXAMINE,
COMSIG_LIVING_DEATH,
COMSIG_SPECIES_GAIN,
COMSIG_QDELETING,
COMSIG_ENTER_COFFIN,
COMSIG_MOB_STAKED,
COMSIG_CARBON_LOSE_ORGAN
))
/datum/antagonist/bloodsucker/proc/unregister_sol_signals()
UnregisterSignal(SSsunlight, list(
COMSIG_SOL_RANKUP_BLOODSUCKERS,
COMSIG_SOL_NEAR_START,
COMSIG_SOL_END,
COMSIG_SOL_RISE_TICK,
COMSIG_SOL_WARNING_GIVEN
))
@@ -1,23 +1,5 @@
///a malkavian bloodsucker that has entered final death. does nothing, other than signify they are dead
/datum/antagonist/shaded_bloodsucker
name = "\improper Shaded Bloodsucker"
antagpanel_category = "Bloodsucker"
show_in_roundend = FALSE
job_rank = ROLE_BLOODSUCKER
antag_hud_name = "bloodsucker"
/obj/item/soulstone/bloodsucker
theme = THEME_WIZARD
required_role = /datum/antagonist/vassal //vassals can free their master
/obj/item/soulstone/bloodsucker/init_shade(mob/living/carbon/human/victim, mob/user, message_user = FALSE, mob/shade_controller)
. = ..()
for(var/mob/shades in contents)
shades.mind.add_antag_datum(/datum/antagonist/shaded_bloodsucker)
/obj/item/soulstone/bloodsucker/capture_soul(mob/living/carbon/victim, mob/user, forced = FALSE, datum/antagonist/bloodsucker/bloodsuckerdatum)
. = ..()
for(var/mob/shades in contents)
var/datum/antagonist/shaded_bloodsucker/shaded_datum = shades.mind.has_antag_datum(/datum/antagonist/shaded_bloodsucker)
shaded_datum.objectives = bloodsuckerdatum.objectives
required_role = null
@@ -47,11 +47,11 @@
owner.current.add_mood_event("vampsleep", /datum/mood_event/daylight_sun_scorched)
return
if(istype(owner.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection
if(is_valid_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 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_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
if(!is_in_torpor())
check_begin_torpor(TORPOR_SKIP_CHECK_ALL)
owner.current.add_mood_event("vampsleep", /datum/mood_event/coffinsleep)
return
@@ -76,75 +76,90 @@
*
* Torpor is triggered by:
* - Being in a Coffin while Sol is on, dealt with by Sol
* - Entering a Coffin with more than 10 combined Brute/Burn damage, dealt with by /closet/crate/coffin/close() [bloodsucker_coffin.dm]
* - Entering a Coffin with more than 10 combined Brute/Burn damage, dealt with by /datum/antagonist/bloodsucker/on_enter_coffin() [procs.dm]
* - Death, dealt with by /HandleDeath()
* Torpor is ended by:
* - Having less than 10 Brute damage while OUTSIDE of your Coffin while it isnt Sol.
* - Having less than 10 Brute & Burn Combined while INSIDE of your Coffin while it isnt Sol.
* - Sol being over, dealt with by /sunlight/process() [bloodsucker_daylight.dm]
* - Having less than maxHealth * 0.8 damage while OUTSIDE of your Coffin while it isnt Sol.
* - Having less than 10 Damage Combined while INSIDE of your Coffin while it isnt Sol.
* - Sol being over, dealt with by /datum/controller/subsystem/processing/sunlight/process() [sol_subsystem.dm]
*/
/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!
// do not skip checking organs for torpor
if(ishuman(user))
var/mob/living/carbon/human/humie = user
if(humie.dna.species.mutantheart && !user.get_organ_slot(ORGAN_SLOT_HEART))
return FALSE
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
return TRUE
/// Prevent Torpor whilst frenzied.
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
return FALSE
// 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(SkipChecks & TORPOR_SKIP_CHECK_DAMAGE || !SSsunlight.sunlight_active && total_damage >= 10 && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))
if(SkipChecks & TORPOR_SKIP_CHECK_DAMAGE || !SSsunlight.sunlight_active && total_damage >= 10 && !is_in_torpor())
torpor_begin()
return TRUE
return FALSE
/datum/antagonist/bloodsucker/proc/check_end_torpor()
/datum/antagonist/bloodsucker/proc/is_in_torpor()
return owner.current && HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_TORPOR, BLOODSUCKER_TRAIT)
/datum/antagonist/bloodsucker/proc/check_end_torpor(early_end = FALSE)
var/mob/living/carbon/user = owner.current
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
var/is_in_coffin = is_valid_coffin()
if(total_burn >= user.maxHealth * 2)
return FALSE
if(SSsunlight.sunlight_active)
if(SSsunlight.sunlight_active && is_in_coffin)
return FALSE
if(bloodsucker_blood_volume == 0 || owner.current.am_staked() || HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT))
if(bloodsucker_blood_volume == 0 || early_end || (SSsunlight.sunlight_active && !is_in_coffin))
// 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))
var/damage_to_revive = owner.current.maxHealth * 0.8
if(is_valid_coffin())
if(total_damage <= 10)
torpor_end()
else
if(total_damage <= 10)
if(total_damage <= damage_to_revive)
torpor_end()
return TRUE
/datum/antagonist/bloodsucker/proc/torpor_begin(silent = FALSE)
/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)
// Force them to go to "sleep"
if(!is_valid_coffin())
owner.current.death()
else
owner.current.add_traits(list(TRAIT_FAKEDEATH, TRAIT_DEATHCOMA), 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.add_traits(list(TRAIT_TORPOR, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE, TRAIT_DEATHCOMA), BLOODSUCKER_TRAIT)
owner.current.do_jitter_animation(2)
// Disable ALL Powers
DisableAllPowers()
/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_FROM_ONLY(owner.current, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT))
ADD_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT)
/datum/antagonist/bloodsucker/proc/torpor_end(message = FALSE)
if(!owner.current)
return
owner.current.grab_ghost()
owner.current.remove_traits(list(TRAIT_TORPOR, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE, TRAIT_FAKEDEATH, TRAIT_DEATHCOMA), BLOODSUCKER_TRAIT)
heal_vampire_organs()
SEND_SIGNAL(src, BLOODSUCKER_EXIT_TORPOR)
if(message)
to_chat(owner.current, span_warning("You have recovered from Torpor."))
SEND_SIGNAL(src, COMSIG_BLOODSUCKER_EXIT_TORPOR)
@@ -11,7 +11,7 @@
var/name = CLAN_NONE
///Description of what the clan is, given when joining and through your antag UI.
var/description = "The Caitiff is as basic as you can get with Bloodsuckers. \n\
Entirely Clan-less, they are blissfully unaware of who they really are. \n\
Entirely without the help of a formal Clan, they are blissfully unaware of who they really are. \n\
No additional abilities is gained, nothing is lost, if you want a plain Bloodsucker, this is it. \n\
The Favorite Vassal will gain the Brawn ability, to help in combat."
///The clan objective that is required to greentext.
@@ -29,38 +29,40 @@
var/blood_drink_type = BLOODSUCKER_DRINK_NORMAL
/// How much stamina armor we get in frenzy
var/frenzy_stamina_mod = 0.4
var/buy_power_flags = BLOODSUCKER_CAN_BUY
var/level_up_percentage_cost = 0.1
/datum/bloodsucker_clan/New(datum/antagonist/bloodsucker/owner_datum)
. = ..()
src.bloodsuckerdatum = owner_datum
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_ON_LIFETICK, PROC_REF(handle_clan_life))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_RANK_UP, PROC_REF(on_spend_rank))
RegisterSignal(bloodsuckerdatum, COMSIG_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(favorite_vassal_gain))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_LOOSE_FAVORITE, PROC_REF(favorite_vassal_loss))
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_INTERACT_WITH_VASSAL, PROC_REF(on_interact_with_vassal))
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_MAKE_FAVORITE, PROC_REF(favorite_vassal_gain))
RegisterSignal(bloodsuckerdatum, COMSIG_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))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_FINAL_DEATH, PROC_REF(on_final_death))
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_MADE_VASSAL, PROC_REF(on_vassal_made))
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_EXIT_TORPOR, PROC_REF(on_exit_torpor))
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_FINAL_DEATH, PROC_REF(on_final_death))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_ENTERS_FRENZY, PROC_REF(on_enter_frenzy))
RegisterSignal(bloodsuckerdatum, BLOODSUCKER_EXITS_FRENZY, PROC_REF(on_exit_frenzy))
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_ENTERS_FRENZY, PROC_REF(on_enter_frenzy))
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_EXITS_FRENZY, PROC_REF(on_exit_frenzy))
give_clan_objective()
/datum/bloodsucker_clan/Destroy(force)
UnregisterSignal(bloodsuckerdatum, list(
COMSIG_BLOODSUCKER_ON_LIFETICK,
BLOODSUCKER_RANK_UP,
BLOODSUCKER_INTERACT_WITH_VASSAL,
BLOODSUCKER_MAKE_FAVORITE,
BLOODSUCKER_MADE_VASSAL,
BLOODSUCKER_EXIT_TORPOR,
BLOODSUCKER_FINAL_DEATH,
BLOODSUCKER_ENTERS_FRENZY,
BLOODSUCKER_EXITS_FRENZY,
COMSIG_BLOODSUCKER_RANK_UP,
COMSIG_BLOODSUCKER_INTERACT_WITH_VASSAL,
COMSIG_BLOODSUCKER_MAKE_FAVORITE,
COMSIG_BLOODSUCKER_MADE_VASSAL,
COMSIG_BLOODSUCKER_EXIT_TORPOR,
COMSIG_BLOODSUCKER_FINAL_DEATH,
COMSIG_BLOODSUCKER_ENTERS_FRENZY,
COMSIG_BLOODSUCKER_EXITS_FRENZY,
))
remove_clan_objective()
bloodsuckerdatum = null
@@ -148,10 +150,7 @@
/datum/bloodsucker_clan/proc/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
// Purchase Power Prompt
var/list/options = list()
for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
if(initial(power.purchase_flags) & BLOODSUCKER_CAN_BUY && !(locate(power) in bloodsuckerdatum.powers))
options[initial(power.name)] = power
var/list/options = level_up_options()
var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
if(options.len < 1)
to_chat(bloodsuckerdatum.owner.current, span_notice("You grow more ancient by the night!"))
@@ -169,25 +168,23 @@
if(!choice || !options[choice])
to_chat(human_user, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
return
// Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
if(locate(options[choice]) in bloodsuckerdatum.powers)
to_chat(human_user, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
return
// Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
if(!istype(human_user.loc, /obj/structure/closet/crate/coffin))
if(!bloodsuckerdatum.is_valid_coffin())
to_chat(human_user, span_warning("You must be in your Coffin to purchase Powers."))
return
// Good to go - Buy Power!
var/datum/action/cooldown/bloodsucker/purchased_power = options[choice]
bloodsuckerdatum.BuyPower(purchased_power)
if(!purchase_choice(bloodsuckerdatum, options[choice]))
to_chat(human_user, span_warning("Cannot buy duplicate powers!"))
return
human_user.balloon_alert(human_user, "learned [choice]!")
to_chat(human_user, span_notice("You have learned how to use [choice]!"))
finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
/datum/bloodsucker_clan/proc/finalize_spend_rank(datum/antagonist/bloodsucker/source, cost_rank = TRUE, blood_cost)
bloodsuckerdatum.LevelUpPowers()
level_up_powers(source)
bloodsuckerdatum.bloodsucker_regen_rate += 0.05
bloodsuckerdatum.max_blood_volume += 100
@@ -229,6 +226,22 @@
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin)
bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "new recipes learned! Vassalization unlocked!")
/datum/bloodsucker_clan/proc/level_up_options()
var/list/options = list()
for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
if(initial(power.purchase_flags) & buy_power_flags && !(locate(power) in bloodsuckerdatum.powers))
options[initial(power.name)] = power
return options
/datum/bloodsucker_clan/proc/level_up_powers(datum/antagonist/bloodsucker/source)
bloodsuckerdatum.LevelUpPowers()
/datum/bloodsucker_clan/proc/purchase_choice(datum/antagonist/bloodsucker/source, datum/action/cooldown/bloodsucker/purchased_power)
if(purchased_power in bloodsuckerdatum.powers)
to_chat(source.owner.current, span_notice("You already have this power!"))
return
return bloodsuckerdatum.BuyPower(purchased_power)
/**
* Called when we are trying to turn someone into a Favorite Vassal
* args:
@@ -83,7 +83,10 @@
/datum/bloodsucker_clan/malkavian/on_final_death(datum/antagonist/bloodsucker/source)
var/obj/item/soulstone/bloodsucker/stone = new /obj/item/soulstone/bloodsucker(get_turf(bloodsuckerdatum.owner.current))
ASYNC stone.capture_soul(bloodsuckerdatum.owner.current, forced = TRUE, bloodsuckerdatum = bloodsuckerdatum)
if(!bloodsuckerdatum.owner.current.ckey)
return
ASYNC
stone.capture_soul(bloodsuckerdatum.owner.current, forced = TRUE)
return DONT_DUST
/datum/bloodsucker_clan/malkavian/proc/on_bloodsucker_broke_masquerade(datum/antagonist/bloodsucker/masquerade_breaker)
@@ -1,7 +1,7 @@
/datum/bloodsucker_clan/nosferatu
name = CLAN_NOSFERATU
description = "The Nosferatu Clan is unable to blend in with the crew, with no abilities such as Masquerade and Veil. \n\
Additionally, has a permanent bad back and looks like a Bloodsucker upon a simple examine, and is entirely unidentifiable, \n\
Additionally, has a permanent bad back and looks like a Bloodsucker upon a simple examine, and their face is disfigured \n\
they can fit in the vents regardless of their form and equipment. \n\
The Favorite Vassal is also permanently disfigured, and can also ventcrawl, but only while entirely nude. \n\
They also have night vision, know what each wire does, and have silent footsteps."
@@ -19,20 +19,27 @@
if(!bloodsuckerdatum.owner.current.has_quirk(/datum/quirk/badback))
bloodsuckerdatum.owner.current.add_quirk(/datum/quirk/badback)
bloodsuckerdatum.owner.current.add_traits(list(TRAIT_VENTCRAWLER_ALWAYS, TRAIT_DISFIGURED), BLOODSUCKER_TRAIT)
RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_EXAMINE, PROC_REF(on_mob_examine))
/datum/bloodsucker_clan/nosferatu/proc/on_mob_examine(datum/antagonist/bloodsucker/owner_datum, datum/source, mob/examiner, examine_text)
SIGNAL_HANDLER
var/mob/living/carbon/human/ogled = owner_datum.owner.current
var/mob/living/ogler = examiner
if(isliving(examiner) && examiner != ogled && !ogler.mob_mood.has_mood_of_category("nosferatu_examine"))
ogler.add_mood_event("nosferatu_examine", /datum/mood_event/nosferatu_examined, ogled, owner_datum.GetRank())
ogler.adjust_disgust(owner_datum.GetRank() * 5)
examine_text += span_danger("[ogled.p_They()] look[ogled.p_s()] horrifically disfigured and grotesque, pale as a corpse, and [ogled.p_their()] body is covered in scars and burns.")
/datum/bloodsucker_clan/nosferatu/Destroy(force)
for(var/datum/action/cooldown/bloodsucker/power in bloodsuckerdatum.powers)
bloodsuckerdatum.RemovePower(power)
var/datum/action/cooldown/bloodsucker/feed/suck = locate() in bloodsuckerdatum.powers
if(suck)
bloodsuckerdatum.RemovePower(suck)
bloodsuckerdatum.give_starting_powers()
bloodsuckerdatum.owner.current.remove_quirk(/datum/quirk/badback)
bloodsuckerdatum.owner.current.remove_traits(list(TRAIT_VENTCRAWLER_ALWAYS, TRAIT_DISFIGURED), BLOODSUCKER_TRAIT)
UnregisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_EXAMINE)
return ..()
/datum/bloodsucker_clan/nosferatu/handle_clan_life(datum/antagonist/bloodsucker/source, seconds_per_tick, times_fired)
. = ..()
if(!HAS_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NOBLOOD))
bloodsuckerdatum.owner.current.blood_volume = BLOOD_VOLUME_SURVIVE
/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)
@@ -8,17 +8,18 @@
join_icon_state = "tremere"
join_description = "You will burn if you enter the Chapel, lose all default powers, \
but gain Blood Magic instead, powers you level up overtime."
buy_power_flags = TREMERE_CAN_BUY
/datum/bloodsucker_clan/tremere/New(mob/living/carbon/user)
. = ..()
bloodsuckerdatum.remove_nondefault_powers(return_levels = TRUE)
for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
if((initial(power.purchase_flags) & TREMERE_CAN_BUY) && initial(power.level_current) == 1)
if((initial(power.purchase_flags) & buy_power_flags) && initial(power.level_current) == 1)
bloodsuckerdatum.BuyPower(power)
/datum/bloodsucker_clan/tremere/Destroy(force)
for(var/datum/action/cooldown/bloodsucker/power in bloodsuckerdatum.powers)
if(power.purchase_flags & TREMERE_CAN_BUY)
if(power.purchase_flags & buy_power_flags)
bloodsuckerdatum.RemovePower(power)
return ..()
@@ -31,46 +32,21 @@
bloodsuckerdatum.owner.current.adjust_fire_stacks(2)
bloodsuckerdatum.owner.current.ignite_mob()
/datum/bloodsucker_clan/tremere/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
// Purchase Power Prompt
/datum/bloodsucker_clan/tremere/level_up_powers(datum/antagonist/bloodsucker/source)
return
/datum/bloodsucker_clan/tremere/level_up_options(datum/antagonist/bloodsucker/source)
var/list/options = list()
for(var/datum/action/cooldown/bloodsucker/targeted/tremere/power as anything in bloodsuckerdatum.powers)
if(!(power.purchase_flags & TREMERE_CAN_BUY))
continue
if(isnull(power.upgraded_power))
// get current powers to upgrade
for(var/datum/action/cooldown/bloodsucker/targeted/power as anything in bloodsuckerdatum.powers)
if(!(power.purchase_flags & buy_power_flags))
continue
options[initial(power.name)] = power
return options
if(options.len < 1)
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. 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.GetUnspentRank() <= 0)
return
// Did you choose a power?
if(!choice || !options[choice])
to_chat(bloodsuckerdatum.owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
return
// Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
if(!istype(bloodsuckerdatum.owner.current.loc, /obj/structure/closet/crate/coffin))
to_chat(bloodsuckerdatum.owner.current, span_warning("You must be in your Coffin to purchase Powers."))
return
// Good to go - Buy Power!
var/datum/action/cooldown/bloodsucker/purchased_power = options[choice]
var/datum/action/cooldown/bloodsucker/targeted/tremere/tremere_power = purchased_power
if(isnull(tremere_power.upgraded_power))
bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "cannot upgrade [choice]!")
to_chat(bloodsuckerdatum.owner.current, span_notice("[choice] is already at max level!"))
return
bloodsuckerdatum.BuyPower(tremere_power.upgraded_power)
bloodsuckerdatum.RemovePower(tremere_power)
bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "upgraded [choice]!")
to_chat(bloodsuckerdatum.owner.current, span_notice("You have upgraded [choice]!"))
finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
/datum/bloodsucker_clan/tremere/purchase_choice(datum/antagonist/bloodsucker/source, datum/action/cooldown/bloodsucker/purchased_power)
return purchased_power.upgrade_power()
/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)
@@ -94,7 +94,7 @@
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)
var/datum/antagonist/bloodsucker/bloodsucker_target = IS_BLOODSUCKER(target)
if(!bloodsucker_target)
to_chat(target, span_notice("You feel your heart stop pumping for the last time as you begin to thirst for blood, you feel... dead."))
// Unfavorites you, so the ventrue isn't stuck with you forever
@@ -1,6 +1,5 @@
/datum/action/cooldown/bloodsucker
name = "Vampiric Gift"
desc = "A vampiric gift."
background_icon = 'modular_zubbers/icons/mob/actions/bloodsucker.dmi'
background_icon_state = "vamp_power_off"
button_icon = 'modular_zubbers/icons/mob/actions/bloodsucker.dmi'
@@ -22,9 +21,10 @@
// FLAGS //
/// The effects on this Power (Toggled/Single Use/Static Cooldown)
var/power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN|BP_AM_COSTLESS_UNCONSCIOUS
var/power_flags = 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_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS|AB_CHECK_PHASED
var/bloodsucker_check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY
/// Who can purchase the Power
var/purchase_flags = NONE // BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER|TREMERE_CAN_BUY|VASSAL_CAN_BUY
@@ -37,18 +37,17 @@
var/bloodcost = 0
///The cost to MAINTAIN this Power - Only used for Constant Cost Powers
var/constant_bloodcost = 0
///Most powers happen the moment you click. Some, like Mesmerize, require time and shouldn't cost you if they fail.
var/power_activates_immediately = TRUE
// Modify description to add cost.
/datum/action/cooldown/bloodsucker/New(Target)
SHOULD_CALL_PARENT(TRUE)
. = ..()
if(bloodcost > 0)
desc += "<br><br><b>COST:</b> [bloodcost] Blood"
if(constant_bloodcost > 0)
desc += "<br><br><b>CONSTANT COST:</b><i> [name] costs [constant_bloodcost] blood per second to keep it active.</i>"
if(power_flags & BP_AM_SINGLEUSE)
desc += "<br><br><b>SINGLE USE:</br><i> [name] can only be used once per night.</i>"
desc = get_power_desc()
/datum/action/cooldown/bloodsucker/Destroy()
SHOULD_CALL_PARENT(TRUE)
if(active)
DeactivatePower()
bloodsuckerdatum_power = null
@@ -63,18 +62,25 @@
if(bloodsuckerdatum)
bloodsuckerdatum_power = bloodsuckerdatum
//This is when we CLICK on the ability Icon, not USING.
/datum/action/cooldown/bloodsucker/Trigger(trigger_flags, atom/target)
if(trigger_flags & TRIGGER_SECONDARY_ACTION)
RightClickActivate(trigger_flags)
return
. = ..()
//This is when we CLICK on the ability Icon, not USING.
/datum/action/cooldown/bloodsucker/PreActivate(atom/target)
if(!owner)
return FALSE
if(active && can_deactivate()) // Active? DEACTIVATE AND END!
DeactivatePower()
return FALSE
if(!can_pay_cost() || !can_use(owner, trigger_flags))
if(!can_pay_cost() || !can_use(owner))
return FALSE
pay_cost()
ActivatePower(trigger_flags)
if(!(power_flags & BP_AM_TOGGLE) || !active)
StartCooldown()
return TRUE
. = ..()
// base type returns true? Pay costs
if(!click_to_activate && power_activates_immediately)
pay_cost()
/datum/action/cooldown/bloodsucker/proc/can_pay_cost()
if(!owner || !owner.mind)
@@ -85,7 +91,7 @@
return FALSE
if(!bloodsuckerdatum_power)
var/mob/living/living_owner = owner
if(!HAS_TRAIT(living_owner, TRAIT_NOBLOOD) && living_owner.blood_volume < bloodcost)
if(!HAS_TRAIT(living_owner, TRAIT_NOBLOOD) && living_owner.blood_volume <= bloodcost)
to_chat(owner, span_warning("You need at least [bloodcost] blood to activate [name]"))
return FALSE
return TRUE
@@ -100,50 +106,50 @@
///Called when the Power is upgraded.
/datum/action/cooldown/bloodsucker/proc/upgrade_power()
SHOULD_CALL_PARENT(TRUE)
DeactivatePower()
if(level_current == -1) // -1 means it doesn't rank up ever
return FALSE
level_current++
build_all_button_icons(UPDATE_BUTTON_NAME)
on_power_upgrade()
return TRUE
// 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]"
/datum/action/cooldown/bloodsucker/proc/on_power_upgrade()
SHOULD_CALL_PARENT(TRUE)
desc = get_power_desc()
if(purchase_flags & TREMERE_CAN_BUY && level_current >= TREMERE_OBJECTIVE_POWER_LEVEL)
background_icon_state = "tremere_power_gold_off"
active_background_icon_state = "tremere_power_gold_on"
base_background_icon_state = "tremere_power_gold_off"
build_all_button_icons(ALL)
///Checks if the Power is available to use.
/datum/action/cooldown/bloodsucker/proc/can_use(mob/living/carbon/user, trigger_flags)
/datum/action/cooldown/bloodsucker/proc/can_use(mob/living/carbon/user)
if(!owner)
return FALSE
if(!isliving(user))
return FALSE
if(bloodsuckerdatum_power && !bloodsuckerdatum_power.heart?.resolve())
if(!(bloodsucker_check_flags & BP_CAN_USE_HEARTLESS) && bloodsuckerdatum_power && !owner.get_organ_slot(ORGAN_SLOT_HEART))
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))
if((bloodsucker_check_flags & BP_CANT_USE_IN_TORPOR) && bloodsuckerdatum_power?.is_in_torpor())
to_chat(user, span_warning("Not while you're in Torpor."))
return FALSE
if(!(check_flags & BP_CAN_USE_TRANSFORMED) && user.has_status_effect(/datum/status_effect/shapechange_mob/from_spell))
if(!(bloodsucker_check_flags & BP_CAN_USE_TRANSFORMED) && (user.has_status_effect(/datum/status_effect/shapechange_mob/from_spell) || user.has_status_effect(/datum/status_effect/shapechange_mob)))
to_chat(user, span_warning("You can't do this while transformed!"))
return FALSE
// Frenzy?
if((check_flags & BP_CANT_USE_IN_FRENZY) && (bloodsuckerdatum_power?.frenzied))
if((bloodsucker_check_flags & BP_CANT_USE_IN_FRENZY) && (bloodsuckerdatum_power?.frenzied))
to_chat(user, span_warning("You cannot use powers while in a Frenzy!"))
return FALSE
// Stake?
if(!(check_flags & BP_CAN_USE_WHILE_STAKED) && user.am_staked())
if(!(bloodsucker_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 >= UNCONSCIOUS)
to_chat(user, span_warning("You can't do this while you are unconcious!"))
return FALSE
// Incapacitated?
if((check_flags & BP_CANT_USE_WHILE_INCAPACITATED) && (user.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)))
to_chat(user, span_warning("Not while you're incapacitated!"))
return FALSE
// Constant Cost (out of blood)
if(constant_bloodcost > 0 && !can_pay_blood(user))
to_chat(user, span_warning("You don't have the blood to upkeep [src]."))
@@ -152,15 +158,11 @@
/// NOTE: With this formula, you'll hit half cooldown at level 8 for that power.
/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)
else
cooldown_time = max(initial(cooldown_time) / 2, initial(cooldown_time) - (initial(cooldown_time) / 16 * (level_current-1)))
if(!(power_flags & BP_AM_STATIC_COOLDOWN))
var/custom_cooldown = max(initial(cooldown_time) / 2, initial(cooldown_time) - (initial(cooldown_time) / 16 * (level_current-1)))
// calling parent proc with custom args
return ..(custom_cooldown, override_melee_cooldown_time)
return ..()
/datum/action/cooldown/bloodsucker/proc/can_pay_blood(mob/living/carbon/user)
@@ -172,43 +174,64 @@
/datum/action/cooldown/bloodsucker/is_action_active()
return active
/datum/action/cooldown/bloodsucker/proc/pay_cost()
/datum/action/cooldown/bloodsucker/proc/pay_cost(cost_override)
// Non-bloodsuckers will pay in other ways.
if(!bloodsuckerdatum_power)
var/mob/living/living_owner = owner
if(!HAS_TRAIT(living_owner, TRAIT_NOBLOOD))
living_owner.blood_volume -= bloodcost
var/blood_cost = cost_override ? cost_override : bloodcost
if(HAS_TRAIT(living_owner, TRAIT_NOBLOOD))
living_owner.adjustBruteLoss(blood_cost * 0.1)
else
living_owner.blood_volume = max(0, living_owner.blood_volume - blood_cost)
return
// Bloodsuckers in a Frenzy don't have enough Blood to pay it, so just don't.
if(bloodsuckerdatum_power.frenzied)
return
bloodsuckerdatum_power.AdjustBloodVolume(-bloodcost)
bloodsuckerdatum_power.AdjustBloodVolume(cost_override ? -cost_override : -bloodcost)
/datum/action/cooldown/bloodsucker/proc/ActivatePower(trigger_flags)
// TODO refactor the Trigger -> PreActivate -> Activate -> ActivatePower / set_click_ability -> Activate -> ActivatePower chain
/datum/action/cooldown/bloodsucker/Activate(atom/target)
if(active)
return FALSE
active = TRUE
if(power_flags & BP_AM_TOGGLE)
. = ActivatePower(target)
if(!.)
DeactivatePower(DEACTIVATE_POWER_NO_COOLDOWN)
return FALSE
if(power_flags & BP_CONTINUOUS_EFFECT || constant_bloodcost)
START_PROCESSING(SSprocessing, src)
owner.log_message("used [src][bloodcost != 0 ? " at the cost of [bloodcost]" : ""].", LOG_ATTACK, color="red")
build_all_button_icons()
build_all_button_icons(UPDATE_BUTTON_BACKGROUND)
/datum/action/cooldown/bloodsucker/proc/DeactivatePower()
/datum/action/cooldown/bloodsucker/proc/RightClickActivate(trigger_flags)
if(!owner)
return FALSE
/// return TRUE if you want the ability to be considered activated, put your ability activate logic here
/datum/action/cooldown/bloodsucker/proc/ActivatePower(atom/target)
return TRUE
/datum/action/cooldown/bloodsucker/proc/DeactivatePower(deactivate_flags)
SHOULD_CALL_PARENT(TRUE)
if(!active) //Already inactive? Return
return
if(power_flags & BP_AM_TOGGLE)
return FALSE
if(power_flags & BP_CONTINUOUS_EFFECT || constant_bloodcost)
STOP_PROCESSING(SSprocessing, src)
if(power_flags & BP_AM_SINGLEUSE)
if(power_flags & BP_AM_SINGLEUSE && !(deactivate_flags & DEACTIVATE_POWER_DO_NOT_REMOVE))
remove_after_use()
return
return FALSE
active = FALSE
StartCooldown()
build_all_button_icons()
if(!click_to_activate && !(deactivate_flags & DEACTIVATE_POWER_NO_COOLDOWN))
StartCooldown()
build_all_button_icons(UPDATE_BUTTON_BACKGROUND)
return TRUE
///Used by powers that are continuously active (That have BP_AM_TOGGLE flag)
///Used by powers that are scontinuously active (That have BP_CONTINUOUS_EFFECT flag)
/datum/action/cooldown/bloodsucker/process(seconds_per_tick)
SHOULD_CALL_PARENT(TRUE) //Need this to call parent so the cooldown system works
. = ..()
if(!active || !(power_flags & BP_AM_TOGGLE))
if(!active || !(power_flags & BP_CONTINUOUS_EFFECT))
return FALSE
if(!ContinueActive(owner)) // We can't afford the Power? Deactivate it.
DeactivatePower()
@@ -216,12 +239,12 @@
// We can keep this up (For now), so Pay Cost!
if(power_flags & BP_AM_COSTLESS_UNCONSCIOUS && owner.stat != CONSCIOUS)
return TRUE
if(constant_bloodcost < 0)
return TRUE
if(bloodsuckerdatum_power)
bloodsuckerdatum_power.AdjustBloodVolume(-constant_bloodcost * seconds_per_tick)
else
var/mob/living/living_owner = owner
if(!HAS_TRAIT(living_owner, TRAIT_NOBLOOD))
living_owner.blood_volume -= constant_bloodcost * seconds_per_tick
pay_cost(constant_bloodcost * seconds_per_tick)
return TRUE
/// Checks to make sure this power can stay active
@@ -235,3 +258,36 @@
/datum/action/cooldown/bloodsucker/proc/remove_after_use()
bloodsuckerdatum_power?.powers -= src
Remove(owner)
/datum/action/cooldown/bloodsucker/proc/get_power_explanation()
SHOULD_CALL_PARENT(TRUE)
. = list()
if(level_current != -1)
. += "LEVEL: [level_current] [name]:"
else
. += "(Inherent Power) [name]:"
. += get_power_explanation_extended()
/datum/action/cooldown/bloodsucker/proc/get_power_explanation_extended()
return list()
/datum/action/cooldown/bloodsucker/proc/get_power_desc()
SHOULD_CALL_PARENT(TRUE)
var/new_desc = ""
if(level_current != -1)
new_desc += "<br><b>LEVEL:</b> [level_current]"
else
new_desc += "<br><b>(Inherent Power)</b>"
if(bloodcost > 0)
new_desc += "<br><br><b>COST:</b> [bloodcost] Blood"
if(constant_bloodcost > 0)
new_desc += "<br><br><b>CONSTANT COST:</b><i> [name] costs [constant_bloodcost] blood per second to keep it active.</i>"
if(power_flags & BP_AM_SINGLEUSE)
new_desc += "<br><br><br>SINGLE USE:</br><i> [name] can only be used once per night.</i>"
new_desc += "<br><br><b>DESCRIPTION:</b> [get_power_desc_extended()]"
new_desc += "<br>"
return new_desc
/datum/action/cooldown/bloodsucker/proc/get_power_desc_extended()
return initial(desc)
@@ -1,3 +1,7 @@
#define USE_SEEN_CLOAK_LEVEL 2
#define USE_RUN_CLOAK_LEVEL 4
/datum/action/cooldown/bloodsucker/cloak
name = "Cloak of Darkness"
desc = "Blend into the shadows and become invisible to the untrained and Artificial eye."
@@ -6,9 +10,11 @@
Activate this Power in the shadows and you will slowly turn nearly invisible.\n\
While using Cloak of Darkness, attempting to run will crush you.\n\
Additionally, while Cloak is active, you are completely invisible to the AI.\n\
Higher levels will increase how invisible you are."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
Higher levels will increase how invisible you are.\n\
At level 2, you will no longer need to be unseen to activate this power.\n\
At level 4, you will be able to run while cloaked."
power_flags = BP_CONTINUOUS_EFFECT
check_flags = AB_CHECK_CONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 5
constant_bloodcost = 0.2
@@ -20,19 +26,26 @@
. = ..()
if(!.)
return FALSE
for(var/mob/living/watchers in view(9, owner) - owner)
owner.balloon_alert(owner, "you can only vanish unseen.")
return FALSE
if(level_current < USE_SEEN_CLOAK_LEVEL)
for(var/mob/living/watcher in oviewers(9, owner))
if(!watcher.mind)
continue
if(!can_see(watcher, owner))
continue
if(IS_BLOODSUCKER(watcher) || IS_VASSAL(watcher))
continue
owner.balloon_alert(owner, "you can only vanish unseen.")
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/cloak/ActivatePower(trigger_flags)
. = ..()
/datum/action/cooldown/bloodsucker/cloak/ActivatePower(atom/target)
var/mob/living/user = owner
was_running = (user.move_intent == MOVE_INTENT_RUN)
if(was_running)
if(level_current < USE_RUN_CLOAK_LEVEL && was_running)
user.toggle_move_intent()
user.AddElement(/datum/element/digitalcamo)
user.balloon_alert(user, "cloak turned on.")
return TRUE
/datum/action/cooldown/bloodsucker/cloak/process(seconds_per_tick)
// Checks that we can keep using this.
@@ -44,10 +57,10 @@
var/mob/living/user = owner
animate(user, alpha = max(25, owner.alpha - min(75, 10 + 5 * level_current)), time = 1.5 SECONDS)
// Prevents running while on Cloak of Darkness
if(user.move_intent != MOVE_INTENT_WALK)
if(level_current < USE_RUN_CLOAK_LEVEL && user.move_intent != MOVE_INTENT_WALK)
owner.balloon_alert(owner, "you attempt to run, crushing yourself.")
user.toggle_move_intent()
user.adjustBruteLoss(rand(5,15))
user.adjustBruteLoss(rand(5, 15))
/datum/action/cooldown/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
. = ..()
@@ -59,11 +72,13 @@
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/cloak/DeactivatePower()
/datum/action/cooldown/bloodsucker/cloak/DeactivatePower(deactivate_flags)
. = ..()
if(!.)
return
var/mob/living/user = owner
animate(user, alpha = 255, time = 1 SECONDS)
user.RemoveElement(/datum/element/digitalcamo)
if(was_running && user.move_intent == MOVE_INTENT_WALK)
if(level_current < USE_RUN_CLOAK_LEVEL && was_running && user.move_intent == MOVE_INTENT_WALK)
user.toggle_move_intent()
user.balloon_alert(user, "cloak turned off.")
return ..()
@@ -5,17 +5,19 @@
name = "Feed"
desc = "Feed blood off of a living creature."
button_icon_state = "power_feed"
power_explanation = "Feed:\n\
Activate Feed while next to someone and you will begin to feed blood off of them.\n\
The time needed before you start feeding speeds up the higher level you are.\n\
Feeding off of someone while you have them aggressively grabbed will put them to sleep.\n\
While feeding, you can't speak, as your mouth is covered.\n\
Feeding while nearby (2 tiles away from) a mortal who is unaware of Bloodsuckers' existence, will cause a Masquerade Infraction\n\
If you get too many Masquerade Infractions, you will break the Masquerade.\n\
If you are in desperate need of blood, mice can be fed off of, at a cost.\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_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS|BP_CAN_USE_WHILE_STAKED
power_explanation = list(
"Activate Feed while next to someone and you will begin to feed blood off of them.",
"The time needed before you start feeding speeds up the higher level you are.",
"Feeding off of someone while you have them aggressively grabbed will put them to sleep.",
"While feeding, you can't speak, as your mouth is covered.",
"Feeding while nearby (2 tiles away from) a mortal who is unaware of Bloodsuckers' existence, will cause a Masquerade Infraction",
"If you get too many Masquerade Infractions, you will break the Masquerade.",
"If you are in desperate need of blood, mice can be fed off of, at a cost.",
"You must use the ability again to stop sucking blood.",
)
level_current = -1 // scales itself based on your actual level, since you always have it
power_flags = BP_CONTINUOUS_EFFECT|BP_AM_STATIC_COOLDOWN
bloodsucker_check_flags = BP_CANT_USE_IN_TORPOR|BP_CAN_USE_WHILE_STAKED|BP_CAN_USE_HEARTLESS
purchase_flags = BLOODSUCKER_DEFAULT_POWER
bloodcost = 0
cooldown_time = 15 SECONDS
@@ -32,12 +34,27 @@
///Have we notified you already that you are at maximum blood?
var/notified_overfeeding = FALSE
/datum/action/cooldown/bloodsucker/feed/get_power_explanation_extended()
. = list()
. += "Activate Feed while next to someone and you will begin to feed blood off of them."
. += "The time needed before you start feeding is [DisplayTimeText(FEED_DEFAULT_TIMER)]."
. += "Feeding off of someone while you have them aggressively grabbed will put them to sleep for [DisplayTimeText(get_sleep_time())]."
. += "While feeding, you can't speak, as you are using your mouth to drink blood."
. += "Feeding while nearby ([FEED_NOTICE_RANGE] tiles away from) a mortal who is unaware of Bloodsuckers' existence, will cause a Masquerade Infraction"
. += "If you get too many Masquerade Infractions, you will break the Masquerade."
. += "If you are in desperate need of blood, mice can be fed off of, at a cost to your humanity."
. += "You must use the ability again to stop sucking blood."
/datum/action/cooldown/bloodsucker/feed/can_use(mob/living/carbon/user, trigger_flags)
. = ..()
if(!.)
return FALSE
if(target_ref) //already sucking blood.
return FALSE
if(!ContinueActive(user, target_ref?.resolve(), !silent_feed, !silent_feed))
target_ref = null
else
owner.balloon_alert(owner, "already feeding!")
return FALSE
if(user.is_mouth_covered() && !isplasmaman(user))
owner.balloon_alert(owner, "mouth covered!")
return FALSE
@@ -57,11 +74,20 @@
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/feed/DeactivatePower()
/datum/action/cooldown/bloodsucker/feed/DeactivatePower(deactivate_flags)
// run before parent checks just to ensure that this always gets cleaned up
UnregisterSignal(owner, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE)
REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, FEED_TRAIT)
REMOVE_TRAIT(owner, TRAIT_MUTE, FEED_TRAIT)
. = ..()
if(!.)
return
var/mob/living/user = owner
var/mob/living/feed_target = target_ref?.resolve()
UnregisterSignal(user, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE)
if(isnull(feed_target))
if(!blood_taken)
return
if(isnull(feed_target) && blood_taken)
log_combat(user, user, "fed on blood (target not found)", addition="(and took [blood_taken] blood)")
else
log_combat(user, feed_target, "fed on blood", addition="(and took [blood_taken] blood)")
@@ -73,14 +99,17 @@
target_ref = null
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)
notified_overfeeding = initial(notified_overfeeding)
return ..()
/datum/action/cooldown/bloodsucker/feed/ActivatePower(trigger_flags)
/datum/action/cooldown/bloodsucker/feed/ActivatePower(atom/target)
// if this happens this means that we didn't properly deactivate the power
if(HAS_TRAIT_FROM(owner, TRAIT_IMMOBILIZED, FEED_TRAIT) || HAS_TRAIT_FROM(owner, TRAIT_MUTE, FEED_TRAIT))
DeactivatePower()
silent_feed = TRUE
var/mob/living/feed_target = target_ref.resolve()
var/mob/living/feed_target = target_ref?.resolve()
if(!feed_target)
DeactivatePower()
return FALSE
if(istype(feed_target, /mob/living/basic/mouse))
to_chat(owner, span_notice("You recoil at the taste of a lesser lifeform."))
if(bloodsuckerdatum_power.my_clan && bloodsuckerdatum_power.my_clan.blood_drink_type != BLOODSUCKER_DRINK_INHUMANELY)
@@ -88,22 +117,22 @@
user.add_mood_event("drankblood", /datum/mood_event/drankblood_bad)
bloodsuckerdatum_power.AddHumanityLost(1)
bloodsuckerdatum_power.AdjustBloodVolume(25)
DeactivatePower()
feed_target.death()
return
var/feed_timer = clamp(round(FEED_DEFAULT_TIMER / (1.25 * (level_current || 1))), 1, FEED_DEFAULT_TIMER)
StartCooldown()
return FALSE
var/feed_timer = get_feed_start_time()
if(bloodsuckerdatum_power.frenzied)
feed_timer = 2 SECONDS
feed_timer = min(2 SECONDS, feed_timer)
owner.balloon_alert(owner, "feeding off [feed_target]...")
owner.face_atom(feed_target)
if(!do_after(owner, feed_timer, feed_target, hidden = TRUE))
owner.balloon_alert(owner, "feed stopped")
DeactivatePower()
return
target_ref = null
return FALSE
if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
if(!IS_BLOODSUCKER(feed_target) && !IS_VASSAL(feed_target) && !IS_MONSTERHUNTER(feed_target))
feed_target.Unconscious((5 + level_current) SECONDS)
feed_target.Unconscious(get_sleep_time())
if(!feed_target.density)
feed_target.Move(owner.loc)
owner.visible_message(
@@ -137,13 +166,16 @@
ADD_TRAIT(owner, TRAIT_MUTE, FEED_TRAIT)
ADD_TRAIT(owner, TRAIT_IMMOBILIZED, FEED_TRAIT)
RegisterSignal(owner, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, PROC_REF(notify_move_block))
return ..()
return TRUE
/datum/action/cooldown/bloodsucker/feed/process(seconds_per_tick)
if(!active) //If we aren't active (running on SSfastprocess)
return ..() //Manage our cooldown timers
var/mob/living/user = owner
var/mob/living/feed_target = target_ref.resolve()
var/mob/living/feed_target = target_ref?.resolve()
if(!feed_target)
DeactivatePower()
return
if(!ContinueActive(user, feed_target, !silent_feed, !silent_feed))
if(!silent_feed)
user.visible_message(
@@ -261,14 +293,23 @@
if(give_warnings)
owner.balloon_alert(owner, "cant drink from mindless!")
return FALSE
if(target_user.has_reagent(/datum/reagent/consumable/garlic, 20))
if(target_user.has_reagent(/datum/reagent/consumable/garlic, 5))
if(give_warnings)
owner.balloon_alert(owner, "too much garlic!")
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/feed/proc/get_sleep_time()
return (5 + bloodsuckerdatum_power?.GetRank() || 1) SECONDS
/datum/action/cooldown/bloodsucker/feed/proc/get_feed_start_time()
return clamp(round(FEED_DEFAULT_TIMER / (1.25 * (bloodsuckerdatum_power?.GetRank() || 1))), 1, FEED_DEFAULT_TIMER)
/datum/action/cooldown/bloodsucker/feed/proc/notify_move_block()
SIGNAL_HANDLER
if(!active)
DeactivatePower()
return
if (!COOLDOWN_FINISHED(src, feed_movement_notify_cooldown))
return
COOLDOWN_START(src, feed_movement_notify_cooldown, 3 SECONDS)
@@ -1,15 +1,10 @@
#define FORTITUDE_STUN_IMMUNITY_LEVEL 4
/datum/action/cooldown/bloodsucker/fortitude
name = "Fortitude"
desc = "Withstand egregious physical wounds and walk away from attacks that would stun, pierce, and dismember lesser beings."
button_icon_state = "power_fortitude"
power_explanation = "Fortitude:\n\
Activating Fortitude will provide pierce, stun and dismember immunity.\n\
You will additionally gain resistance to 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."
power_flags = BP_AM_TOGGLE|BP_AM_COSTLESS_UNCONSCIOUS
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY
power_flags = BP_CONTINUOUS_EFFECT|BP_AM_COSTLESS_UNCONSCIOUS
check_flags = NONE
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 30
cooldown_time = 8 SECONDS
@@ -17,24 +12,39 @@
var/was_running
var/fortitude_resist // So we can raise and lower your brute resist based on what your level_current WAS.
/datum/action/cooldown/bloodsucker/fortitude/ActivatePower(trigger_flags)
. = ..()
/datum/action/cooldown/bloodsucker/fortitude/get_power_explanation_extended()
. = list()
. += "Fortitude will provide pierce, stun and dismember immunity."
. += "You will additionally gain resistance to both brute, burn and stamina damage, scaling with level."
. += "Fortitude will make you receive [GetFortitudeResist() * 10]% less brute and and stamina and [GetBurnResist() * 10]% less burn damage."
. += "While using Fortitude, attempting to run will crush you."
. += "At level [FORTITUDE_STUN_IMMUNITY_LEVEL], you gain complete stun immunity."
. += "Higher levels will increase Brute and Stamina resistance."
/datum/action/cooldown/bloodsucker/fortitude/ActivatePower(atom/target)
owner.balloon_alert(owner, "fortitude turned on.")
to_chat(owner, span_notice("Your flesh, skin, and muscles become as steel."))
// Traits & Effects
owner.add_traits(list(TRAIT_PIERCEIMMUNE, TRAIT_NODISMEMBER, TRAIT_PUSHIMMUNE), BLOODSUCKER_TRAIT)
if(level_current >= 4)
ADD_TRAIT(owner, TRAIT_STUNIMMUNE, BLOODSUCKER_TRAIT) // They'll get stun resistance + this, who cares.
if(level_current >= FORTITUDE_STUN_IMMUNITY_LEVEL)
ADD_TRAIT(owner, TRAIT_STUNIMMUNE, BLOODSUCKER_TRAIT)
var/mob/living/carbon/human/bloodsucker_user = owner
if(IS_BLOODSUCKER(owner) || IS_VASSAL(owner))
fortitude_resist = max(0.3, 0.7 - level_current * 0.1)
fortitude_resist = GetFortitudeResist()
bloodsucker_user.physiology.brute_mod *= fortitude_resist
bloodsucker_user.physiology.burn_mod *= fortitude_resist + 0.2
bloodsucker_user.physiology.burn_mod *= GetBurnResist()
bloodsucker_user.physiology.stamina_mod *= fortitude_resist
was_running = (bloodsucker_user.move_intent == MOVE_INTENT_RUN)
if(was_running)
bloodsucker_user.toggle_move_intent()
return TRUE
/datum/action/cooldown/bloodsucker/fortitude/proc/GetFortitudeResist()
return max(0.3, 0.7 - level_current * 0.1)
/datum/action/cooldown/bloodsucker/fortitude/proc/GetBurnResist()
return GetFortitudeResist() + 0.2
/datum/action/cooldown/bloodsucker/fortitude/process(seconds_per_tick)
// Checks that we can keep using this.
@@ -53,11 +63,12 @@
if(user.buckled && istype(user.buckled, /obj/vehicle))
user.buckled.unbuckle_mob(src, force=TRUE)
/datum/action/cooldown/bloodsucker/fortitude/DeactivatePower()
if(!ishuman(owner))
/datum/action/cooldown/bloodsucker/fortitude/DeactivatePower(deactivate_flags)
. = ..()
if(!. || !ishuman(owner))
return
var/mob/living/carbon/human/bloodsucker_user = owner
if(IS_BLOODSUCKER(owner) || IS_VASSAL(owner))
if(IS_BLOODSUCKER(owner) || IS_VASSAL(owner) && fortitude_resist)
bloodsucker_user.physiology.brute_mod /= fortitude_resist
bloodsucker_user.physiology.burn_mod /= fortitude_resist + 0.2
bloodsucker_user.physiology.stamina_mod /= fortitude_resist
@@ -67,4 +78,3 @@
if(was_running && bloodsucker_user.move_intent == MOVE_INTENT_WALK)
bloodsucker_user.toggle_move_intent()
owner.balloon_alert(owner, "fortitude turned off.")
return ..()
@@ -14,17 +14,14 @@
button_icon_state = "power_gohome"
active_background_icon_state = "vamp_power_off_oneshot"
base_background_icon_state = "vamp_power_off_oneshot"
power_explanation = "Vanishing Act: \n\
Activating Vanishing Act will, after a short delay, teleport the user to their Claimed Coffin. \n\
The power will cancel out if the Claimed Coffin is somehow destroyed. \n\
Immediately after activating, lights around the user will begin to flicker. \n\
Once the user teleports to their coffin, in their place will be a Rat or Bat."
power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN
check_flags = BP_CANT_USE_IN_FRENZY
power_flags = BP_CONTINUOUS_EFFECT|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN
bloodsucker_check_flags = BP_CANT_USE_IN_FRENZY
check_flags = NONE
purchase_flags = NONE
bloodcost = 100
constant_bloodcost = 2
cooldown_time = 100 SECONDS
cooldown_time = 10 SECONDS
power_activates_immediately = FALSE
level_current = -1
///What stage of the teleportation are we in
var/teleporting_stage = GOHOME_START
@@ -34,6 +31,14 @@
/mob/living/basic/bat = 1,
)
/datum/action/cooldown/bloodsucker/gohome/get_power_explanation_extended()
. = list()
. += "Vanishing Act will, after a short delay, teleport the user to their Claimed Coffin."
. += "The user will drop all belongings if seen by a mortal."
. += "The power will cancel out if the Claimed Coffin is somehow destroyed."
. += "Immediately after activating, lights around the user will begin to flicker."
. += "Once the user teleports to their coffin, in their place will be a Rat or Bat."
/datum/action/cooldown/bloodsucker/gohome/can_use(mob/living/carbon/user, trigger_flags)
. = ..()
if(!.)
@@ -44,9 +49,16 @@
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/gohome/ActivatePower(trigger_flags)
. = ..()
/datum/action/cooldown/bloodsucker/gohome/ActivatePower(atom/target)
owner.balloon_alert(owner, "preparing to teleport...")
return TRUE
/datum/action/cooldown/bloodsucker/gohome/DeactivatePower(deactivate_flags)
if(active && teleporting_stage != GOHOME_TELEPORT)
owner.balloon_alert(owner, "teleportation cancelled.")
teleporting_stage = GOHOME_START
return . = ..(DEACTIVATE_POWER_DO_NOT_REMOVE)
. = ..()
/datum/action/cooldown/bloodsucker/gohome/process(seconds_per_tick)
. = ..()
@@ -122,6 +134,7 @@
bloodsuckerdatum_power.coffin.force_enter(user)
DeactivatePower()
pay_cost()
/datum/effect_system/steam_spread/bloodsucker
effect_type = /obj/effect/particle_effect/fluid/smoke/vampsmoke
@@ -14,22 +14,25 @@
name = "Masquerade"
desc = "Feign the vital signs of a mortal, and escape both casual and medical notice as the monster you truly are."
button_icon_state = "power_human"
power_explanation = "Masquerade:\n\
Activating Masquerade will forge your identity to be practically identical to that of a human;\n\
- You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus, 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 diseases you might have."
power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN|BP_AM_COSTLESS_UNCONSCIOUS
check_flags = BP_CANT_USE_IN_FRENZY
power_flags = BP_CONTINUOUS_EFFECT|BP_AM_STATIC_COOLDOWN|BP_AM_COSTLESS_UNCONSCIOUS
check_flags = NONE
bloodsucker_check_flags = BP_CANT_USE_IN_FRENZY
purchase_flags = BLOODSUCKER_DEFAULT_POWER
bloodcost = 10
level_current = -1
cooldown_time = 5 SECONDS
constant_bloodcost = 0.1
/datum/action/cooldown/bloodsucker/masquerade/ActivatePower(trigger_flags)
. = ..()
/datum/action/cooldown/bloodsucker/masquerade/get_power_explanation_extended()
. = list()
. += "Masquerade will forge your identity to be practically identical to that of a human."
. += "- You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus, gutting and cold immunity."
. += "- Your eyes turn to that of a regular human as your heart begins to beat."
. += "- You gain a Genetic sequence, and appear to have 100% blood when scanned by a Health Analyzer."
. += "- You will not appear as Pale when examined. Anything further than Pale, however, will not be hidden."
. += "At the end of a Masquerade, you will re-gain your Vampiric abilities, as well as lose any diseases you might have."
/datum/action/cooldown/bloodsucker/masquerade/ActivatePower(atom/target)
var/mob/living/carbon/user = owner
owner.balloon_alert(owner, "masquerade turned on.")
to_chat(user, span_notice("Your heart beats falsely within your lifeless chest, and your eyes are no longer sensitive to the light. You may yet pass for a mortal."))
@@ -40,7 +43,7 @@
// 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)
@@ -55,10 +58,13 @@
eyes.color_cutoffs = initial(eyes.color_cutoffs)
eyes.sight_flags = initial(eyes.sight_flags)
user.update_sight()
return TRUE
/// todo, make bloodsuckerification into it's own proc, ie, eyes, traits, and such
/datum/action/cooldown/bloodsucker/masquerade/DeactivatePower()
/datum/action/cooldown/bloodsucker/masquerade/DeactivatePower(deactivate_flags)
. = ..() // activate = FALSE
if(!.)
return
var/mob/living/carbon/user = owner
owner.balloon_alert(owner, "masquerade turned off.")
@@ -1,54 +1,55 @@
// NOTE: All Targeted spells are Toggles! We just don't bother checking here.
/datum/action/cooldown/bloodsucker/targeted
power_flags = BP_AM_TOGGLE
power_flags = NONE
click_to_activate = TRUE
///If set, how far the target has to be for the power to work.
var/target_range
///Message sent to chat when clicking on the power, before you use it.
var/prefire_message
///Most powers happen the moment you click. Some, like Mesmerize, require time and shouldn't cost you if they fail.
var/power_activates_immediately = TRUE
///Is this power LOCKED due to being used?
var/power_in_use = FALSE
/// Modify description to add notice that this is aimed.
/datum/action/cooldown/bloodsucker/targeted/New(Target)
desc += "<br>\[<i>Targeted Power</i>\]"
return ..()
/datum/action/cooldown/bloodsucker/targeted/Remove(mob/living/remove_from)
/datum/action/cooldown/bloodsucker/targeted/get_power_desc()
. = ..()
if(remove_from.click_intercept == src)
unset_click_ability(remove_from)
var/current_desc = "<br>\[<i>Targeted Power</i>\]"
if(target_range)
current_desc += "<br>Cast Range: [target_range]<br>"
current_desc += .
return current_desc
/datum/action/cooldown/bloodsucker/targeted/Trigger(trigger_flags, atom/target)
if((active) && can_deactivate())
DeactivatePower()
return FALSE
if(!can_pay_cost(owner) || !can_use(owner, trigger_flags))
return FALSE
// *Don't read this if you don't care about how actions work.*
// Actions are a wee complicated, but for anyone else who's going to take a look at this, let me explain.
// Actions start at Trigger, which is/ called by the client clicking the action button,
// if it's a targeted power, like this one here, it will call set_click_ability,
// which will set up the click interception. Thus clicking will call Trigger again, but with a target this time.
// Otherwise, if click_to_activate is false, it will simply always call Trigger without a target,
// and call PreActivate, which then calls Activate.
// For this ability, we call InterceptClickOn to trigger the ability with a target, as we want
// to be able to use trigger_flags, which Activate doesn't have.
// If click_to_activate is true, only these two procs are called when the ability is clicked on
/datum/action/cooldown/bloodsucker/targeted/set_click_ability(mob/on_who)
// activate runs before
if(!PreActivate())
return
. = ..()
if(prefire_message)
to_chat(owner, span_announce("[prefire_message]"))
ActivatePower(trigger_flags)
if(target)
return InterceptClickOn(owner, null, target)
return set_click_ability(owner)
/datum/action/cooldown/bloodsucker/targeted/DeactivatePower()
if(power_flags & BP_AM_TOGGLE)
STOP_PROCESSING(SSprocessing, src)
active = FALSE
build_all_button_icons()
unset_click_ability(owner)
// ..() // we don't want to pay cost here
/datum/action/cooldown/bloodsucker/targeted/unset_click_ability(mob/on_who, refund_cooldown)
. = ..()
if(active) //todo refactor active into is_action_active()
DeactivatePower()
/// Check if target is VALID (wall, turf, or character?)
/datum/action/cooldown/bloodsucker/targeted/proc/CheckValidTarget(atom/target_atom)
if(target_atom == owner)
return FALSE
if(!target_atom)
return FALSE
return TRUE
/// Check if valid target meets conditions
@@ -56,37 +57,62 @@
if(target_range)
// Out of Range
if(!(target_atom in view(target_range, owner)))
if(target_range > 1) // Only warn for range if it's greater than 1. Brawn doesn't need to announce itself.
if(target_range > 1) // Only warn for range if it's greater than 1
owner.balloon_alert(owner, "out of range.")
return FALSE
return istype(target_atom)
/// Click Target
/datum/action/cooldown/bloodsucker/targeted/proc/click_with_power(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/PreActivate(atom/target)
if(!target)
return ..()
// CANCEL RANGED TARGET check
if(power_in_use || !CheckValidTarget(target_atom))
if(power_in_use || !CheckValidTarget(target))
return FALSE
// Valid? (return true means DON'T cancel power!)
if(!can_pay_cost() || !can_use(owner) || !CheckCanTarget(target_atom))
return TRUE
power_in_use = TRUE // Lock us into this ability until it successfully fires off. Otherwise, we pay the blood even if we fail.
FireTargetedPower(target_atom) // We use this instead of ActivatePower(trigger_flags), which has no input
// Skip this part so we can return TRUE right away.
if(!can_pay_cost() || !can_use(owner) || !CheckCanTarget(target))
return FALSE
if(power_activates_immediately)
power_activated_sucessfully() // Mesmerize pays only after success.
PowerActivatedSuccesfully() // Mesmerize pays only after success.
power_in_use = FALSE
return TRUE
/// Like ActivatePower, but specific to Targeted (and takes an atom input). We don't use ActivatePower for targeted.
/datum/action/cooldown/bloodsucker/targeted/proc/FireTargetedPower(atom/target_atom)
log_combat(owner, target_atom, "used [name] on")
/datum/action/cooldown/bloodsucker/targeted/proc/FireTargetedPower(atom/target, params)
return FALSE
/// Called on right click
/datum/action/cooldown/bloodsucker/targeted/proc/FireSecondaryTargetedPower(atom/target, params)
return FireTargetedPower(target, params)
/datum/action/cooldown/bloodsucker/targeted/ActivatePower(atom/target)
. = ..()
if(!target)
return .
log_combat(owner, target, "used [name] on [target].")
return TRUE
/datum/action/cooldown/bloodsucker/targeted/DeactivatePower(deactivate_flags)
. = ..()
if(!.)
return
// sometimes things will call DeactivatePower, but not unset_click_ability, so we have to unset the click interception here.
if(owner.click_intercept == src) // TODO test if this is no longer needed
owner.click_intercept = null
/// The power went off! We now pay the cost of the power.
/datum/action/cooldown/bloodsucker/targeted/proc/power_activated_sucessfully()
/datum/action/cooldown/bloodsucker/targeted/proc/PowerActivatedSuccesfully(cooldown_override, cost_override)
StartCooldown(cooldown_override)
unset_click_ability(owner)
pay_cost()
StartCooldown()
DeactivatePower()
pay_cost(cost_override)
// if(active)
// DeactivatePower()
/datum/action/cooldown/bloodsucker/targeted/InterceptClickOn(mob/living/caller, params, atom/target)
click_with_power(target)
. = ..()
if(!.)
return FALSE
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
return FireSecondaryTargetedPower(target, modifiers)
else
return FireTargetedPower(target, modifiers)
@@ -1,39 +1,59 @@
#define BRAWN_BREAKOUT_LEVEL 3
#define BRAWN_AIRLOCK_LEVEL 4
/datum/action/cooldown/bloodsucker/targeted/brawn
name = "Brawn"
desc = "Snap restraints, break lockers and doors, or deal terrible damage with your bare hands."
desc = "Snap restraints, break lockers and doors at higher levels, or deal terrible damage with your bare hands."
button_icon_state = "power_strength"
power_explanation = "Brawn:\n\
Click any person to bash into them, break restraints you have or knocking a grabber down. Only one of these can be done per use.\n\
Punching a Cyborg will heavily EMP them in addition to deal damage.\n\
At level 3, you get the ability to break closets open, additionally can both break restraints AND knock a grabber down in the same use.\n\
At level 4, you get the ability to bash airlocks open, as long as they aren't bolted.\n\
Higher levels will increase the damage and knockdown when punching someone."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 8
cooldown_time = 9 SECONDS
target_range = 1
power_activates_immediately = TRUE
prefire_message = "Select a target."
/datum/action/cooldown/bloodsucker/targeted/brawn/ActivatePower(trigger_flags)
/datum/action/cooldown/bloodsucker/targeted/brawn/get_power_explanation_extended()
. = list()
. += "Click any person to bash into them, break restraints you have or knocking a grabber down. Only one of these can be done per use."
. += "Brawn will do [GetDamage()] brute damage to the target and knockdown them for [DisplayTimeText(GetKnockdown())]."
. += "Punching a Cyborg will heavily EMP them in addition to deal damage."
. += "At level [BRAWN_BREAKOUT_LEVEL], you get the ability to break closets open, additionally can both break restraints AND knock a grabber down in the same use."
. += "At level [BRAWN_AIRLOCK_LEVEL], you get the ability to bash airlocks open, as long as they aren't bolted."
. += "Higher levels will increase the damage and knockdown when punching someone."
/datum/action/cooldown/bloodsucker/targeted/brawn/ActivatePower(atom/target)
// Did we break out of our handcuffs?
if(break_restraints())
power_activated_sucessfully()
playsound(get_turf(owner), 'sound/effects/grillehit.ogg', 80, 1, -1)
PowerActivatedSuccesfully()
return FALSE
// Did we knock a grabber down? We can only do this while not also breaking restraints if strong enough.
if(level_current >= 3 && escape_puller())
power_activated_sucessfully()
return FALSE
if(owner.pulledby)
if(level_current >= BRAWN_BREAKOUT_LEVEL && escape_puller())
PowerActivatedSuccesfully()
return FALSE
owner.balloon_alert(owner, "ability level too low to break free!")
// Did neither, now we can PUNCH.
return ..()
if(HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
owner.balloon_alert(owner, "your hands are blocked!")
return FALSE
// check if we have atleast one arm
if(!owner.get_active_hand())
owner.balloon_alert(owner, "you need a usable arm!")
return FALSE
return TRUE
// Look at 'biodegrade.dm' for reference
/datum/action/cooldown/bloodsucker/targeted/brawn/proc/break_restraints()
var/mob/living/carbon/human/user = owner
///Only one form of shackles removed per use
var/used = FALSE
var/obj/handcuffed = user.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
if(user.buckled && handcuffed && user.buckled.unbuckle_mob(user))
user.visible_message(
span_warning("[user] breaks free of [user.buckled]!"),
span_warning("We break free of [user.buckled]!"),
)
user.buckled = null
return TRUE
// Breaks out of lockers
if(istype(user.loc, /obj/structure/closet))
@@ -46,22 +66,22 @@
)
to_chat(user, span_warning("We bash [closet] wide open!"))
addtimer(CALLBACK(src, PROC_REF(break_closet), user, closet), 1)
used = TRUE
return TRUE
// Remove both Handcuffs & Legcuffs
var/obj/cuffs = user.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
var/obj/legcuffs = user.get_item_by_slot(ITEM_SLOT_LEGCUFFED)
if(!used && (istype(cuffs) || istype(legcuffs)))
user.visible_message(
span_warning("[user] discards their restraints like it's nothing!"),
span_warning("We break through our restraints!"),
)
user.clear_cuffs(cuffs, TRUE)
user.clear_cuffs(legcuffs, TRUE)
used = TRUE
// Remove both Handcuffs & Legcuffs in one step
var/legcuffed = user.get_item_by_slot(ITEM_SLOT_LEGCUFFED)
if(handcuffed || legcuffed)
var/hand_cuffs = user.clear_cuffs(handcuffed, TRUE)
var/leg_cuffs = user.clear_cuffs(legcuffed, TRUE)
if(hand_cuffs || leg_cuffs)
user.visible_message(
span_warning("[user] discards their restraints like it's nothing!"),
span_warning("We break through our restraints!"),
)
return TRUE
// Remove Straightjackets
if(user.wear_suit?.breakouttime && !used)
if(user.wear_suit?.breakouttime)
var/obj/item/clothing/suit/straightjacket = user.get_item_by_slot(ITEM_SLOT_OCLOTHING)
user.visible_message(
span_warning("[user] rips straight through the [user.p_their()] [straightjacket]!"),
@@ -69,12 +89,8 @@
)
if(straightjacket && user.wear_suit == straightjacket)
qdel(straightjacket)
used = TRUE
// Did we end up using our ability? If so, play the sound effect and return TRUE
if(used)
playsound(get_turf(user), 'sound/effects/grillehit.ogg', 80, 1, -1)
return used
return TRUE
return FALSE
// This is its own proc because its done twice, to repeat code copypaste.
/datum/action/cooldown/bloodsucker/targeted/brawn/proc/break_closet(mob/living/carbon/human/user, obj/structure/closet/closet)
@@ -85,8 +101,6 @@
closet.open()
/datum/action/cooldown/bloodsucker/targeted/brawn/proc/escape_puller()
if(!owner.pulledby) // || owner.pulledby.grab_state <= GRAB_PASSIVE)
return FALSE
var/mob/pulled_mob = owner.pulledby
var/pull_power = pulled_mob.grab_state
playsound(get_turf(pulled_mob), 'sound/effects/woodhit.ogg', 75, 1, -1)
@@ -108,41 +122,42 @@
owner.pulledby = null // It's already done, but JUST IN CASE.
return TRUE
/datum/action/cooldown/bloodsucker/targeted/brawn/FireTargetedPower(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/brawn/FireTargetedPower(atom/target, params)
. = ..()
var/mob/living/user = owner
// Target Type: Mob
if(isliving(target_atom))
var/mob/living/target = target_atom
var/mob/living/carbon/carbonuser = user
if(isliving(target))
var/mob/living/target_atom = target
//You know what I'm just going to take the average of the user's limbs max damage instead of dealing with 2 hands
var/obj/item/bodypart/user_active_arm = carbonuser.get_active_hand()
var/hitStrength = user_active_arm.unarmed_damage_high * 1.25 + 2
var/hitStrength = GetDamage()
// Knockdown!
var/powerlevel = min(5, 1 + level_current)
var/powerlevel = GetPowerLevel()
if(rand(5 + powerlevel) >= 5)
target.visible_message(
span_danger("[user] lands a vicious punch, sending [target] away!"), \
target_atom.visible_message(
span_danger("[user] lands a vicious punch, sending [target_atom] away!"), \
span_userdanger("[user] has landed a horrifying punch on you, sending you flying!"),
)
target.Knockdown(min(5, rand(10, 10 * powerlevel)))
target_atom.Knockdown(GetKnockdown())
// Attack!
owner.balloon_alert(owner, "you punch [target]!")
playsound(get_turf(target), 'sound/weapons/punch4.ogg', 60, 1, -1)
user.do_attack_animation(target, ATTACK_EFFECT_SMASH)
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(target.zone_selected))
target.apply_damage(hitStrength, BRUTE, affecting)
owner.balloon_alert(owner, "you punch [target_atom]!")
playsound(get_turf(target_atom), 'sound/weapons/punch4.ogg', 60, 1, -1)
user.do_attack_animation(target_atom, ATTACK_EFFECT_SMASH)
var/obj/item/bodypart/affecting = target_atom.get_bodypart(ran_zone(target_atom.zone_selected))
target_atom.apply_damage(hitStrength, BRUTE, affecting)
// Knockback
var/send_dir = get_dir(owner, target)
var/turf/turf_thrown_at = get_ranged_target_turf(target, send_dir, powerlevel)
var/send_dir = get_dir(owner, target_atom)
var/turf/turf_thrown_at = get_ranged_target_turf(target_atom, send_dir, powerlevel)
owner.newtonian_move(send_dir) // Bounce back in 0 G
target.throw_at(turf_thrown_at, powerlevel, TRUE, owner) //new /datum/forced_movement(target, get_ranged_target_turf(target, send_dir, (hitStrength / 4)), 1, FALSE)
target_atom.throw_at(turf_thrown_at, powerlevel, TRUE, owner) //new /datum/forced_movement(target_atom, get_ranged_target_turf(target_atom, send_dir, (hitStrength / 4)), 1, FALSE)
// Target Type: Cyborg (Also gets the effects above)
if(issilicon(target))
target.emp_act(EMP_HEAVY)
if(issilicon(target_atom))
target_atom.emp_act(EMP_HEAVY)
// Target Type: Locker
else if(istype(target_atom, /obj/structure/closet) && level_current >= 3)
var/obj/structure/closet/target_closet = target_atom
else if(istype(target, /obj/structure/closet))
if(level_current <= BRAWN_BREAKOUT_LEVEL)
target.balloon_alert(user, "ability level too low to break open!")
return FALSE
var/obj/structure/closet/target_closet = target
user.balloon_alert(user, "you prepare to bash [target_closet] open...")
if(!do_after(user, 2.5 SECONDS, target_closet))
user.balloon_alert(user, "interrupted!")
@@ -151,8 +166,11 @@
addtimer(CALLBACK(src, PROC_REF(break_closet), user, target_closet), 1)
playsound(get_turf(user), 'sound/effects/grillehit.ogg', 80, TRUE, -1)
// Target Type: Door
else if(istype(target_atom, /obj/machinery/door) && level_current >= 4)
var/obj/machinery/door/target_airlock = target_atom
else if(istype(target, /obj/machinery/door))
if(level_current <= BRAWN_AIRLOCK_LEVEL)
target.balloon_alert(user, "ability level too low to break open!")
return FALSE
var/obj/machinery/door/target_airlock = target
playsound(get_turf(user), 'sound/machines/airlock_alien_prying.ogg', 40, TRUE, -1)
owner.balloon_alert(owner, "you prepare to tear open [target_airlock]...")
if(!do_after(user, 2.5 SECONDS, target_airlock))
@@ -165,6 +183,23 @@
playsound(get_turf(target_airlock), 'sound/effects/bang.ogg', 30, 1, -1)
target_airlock.open(2) // open(2) is like a crowbar or jaws of life.
/datum/action/cooldown/bloodsucker/targeted/brawn/proc/GetPowerLevel()
return min(5, 1 + level_current)
/datum/action/cooldown/bloodsucker/targeted/brawn/proc/GetKnockdown()
return min(5, rand(10, 10 * GetPowerLevel()))
/datum/action/cooldown/bloodsucker/targeted/brawn/proc/GetDamage()
var/mob/living/carbon/human/user = owner
var/obj/item/bodypart/user_active_arm
user_active_arm = user.get_active_hand()
if(!user || !user_active_arm)
return GetPunchDamage(initial(user_active_arm.unarmed_damage_high))
return GetPunchDamage(user_active_arm.unarmed_damage_high)
/datum/action/cooldown/bloodsucker/targeted/brawn/proc/GetPunchDamage(punch_damage)
return punch_damage * 1.25 + 2
/datum/action/cooldown/bloodsucker/targeted/brawn/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
@@ -189,3 +224,6 @@
else if(istype(target_atom, /obj/structure/closet))
return TRUE
return FALSE
#undef BRAWN_BREAKOUT_LEVEL
#undef BRAWN_AIRLOCK_LEVEL
@@ -3,27 +3,37 @@
* Level 3: Stun People Passed
*/
#define HASTE_GETUP_LEVEL 3
/datum/action/cooldown/bloodsucker/targeted/haste
name = "Immortal Haste"
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\
It will also refill your stamina so you can keep moving."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
prefire_message = "You prepare to dash!"
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 6
cooldown_time = 12 SECONDS
target_range = 15
power_activates_immediately = TRUE
power_activates_immediately = FALSE
///List of all people hit by our power, so we don't hit them again.
var/list/hit = list()
/datum/action/cooldown/bloodsucker/targeted/haste/get_power_desc_extended()
. = "Dash to a location, knocking down anyone in your way, and refilling your stamina. Those nearby may be knocked away, stunned, or left empty-handed.\n"
if(level_current >= HASTE_GETUP_LEVEL)
. += "Dashing from lying down will get you up, but won't affect your foes."
else
. += "You cannot dash while knocked down."
/datum/action/cooldown/bloodsucker/targeted/haste/get_power_explanation_extended()
. = list()
. += "Click anywhere to immediately dash towards that location."
. += "At level [HASTE_GETUP_LEVEL], if you are lying down, you will get up and regain your stamina, but the resulting dash will not knock down those nearby."
. += "Haste will knockdown your enemies for [DisplayTimeText(GetKnockdown())] and refill your stamina, but using haste while knocked down will make it go on cooldown for [DisplayTimeText(cooldown_time * 3)]"
. += "The Power will not work if you are lying down, in no gravity, or are aggressively grabbed."
. += "Anyone in your way during your Haste will be knocked down."
. += "Higher levels will increase the knockdown dealt to enemies."
. += "It will also refill your stamina so you can keep moving."
/datum/action/cooldown/bloodsucker/targeted/haste/can_use(mob/living/carbon/user, trigger_flags)
. = ..()
if(!.)
@@ -35,12 +45,12 @@
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(level_current < 3 && user.body_position == LYING_DOWN)
if(level_current < HASTE_GETUP_LEVEL && user.body_position == LYING_DOWN)
user.balloon_alert(user, "you must be standing to dash!")
return FALSE
return TRUE
/// Anything will do, if it's not me or my square
/// Anything will do, if it's not mea or my square
/datum/action/cooldown/bloodsucker/targeted/haste/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
@@ -48,20 +58,21 @@
return target_atom.loc != owner.loc
/// This is a non-async proc to make sure the power is "locked" until this finishes.
/datum/action/cooldown/bloodsucker/targeted/haste/FireTargetedPower(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/haste/FireTargetedPower(atom/target, params)
. = ..()
var/mob/living/user = owner
var/stuns_mobs = TRUE
if(level_current >= 3 && user.body_position == LYING_DOWN)
var/temp_cooldown = cooldown_time
if(level_current >= HASTE_GETUP_LEVEL && 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)
temp_cooldown = GetGetupCooldown()
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)
var/turf/targeted_turf = isturf(target) ? target : get_turf(target)
// Pulled? Not anymore.
user.pulledby?.stop_pulling()
// Go to target turf
@@ -70,6 +81,7 @@
playsound(get_turf(owner), 'sound/weapons/punchmiss.ogg', 25, 1, -1)
var/safety = get_dist(user, targeted_turf) * 3 + 1
var/consequetive_failures = 0
active = TRUE
while(--safety && (get_turf(user) != targeted_turf))
var/success = step_towards(user, targeted_turf) //This does not try to go around obstacles.
if(!success)
@@ -84,12 +96,16 @@
break
if(success) //don't sleep if we failed to move.
sleep(world.tick_lag)
user.adjustStaminaLoss(-user.staminaloss)
/datum/action/cooldown/bloodsucker/targeted/haste/power_activated_sucessfully()
. = ..()
UnregisterSignal(owner, COMSIG_MOVABLE_MOVED)
hit.Cut()
user.adjustStaminaLoss(-user.staminaloss)
PowerActivatedSuccesfully(temp_cooldown)
/datum/action/cooldown/bloodsucker/targeted/haste/proc/GetKnockdown()
return 10 + level_current * 4
/datum/action/cooldown/bloodsucker/targeted/haste/proc/GetGetupCooldown()
return cooldown_time * 3
/datum/action/cooldown/bloodsucker/targeted/haste/proc/on_move()
for(var/mob/living/hit_living in dview(1, get_turf(owner)) - owner)
@@ -97,5 +113,7 @@
continue
hit += hit_living
playsound(hit_living, "sound/weapons/punch[rand(1,4)].ogg", 15, 1, -1)
hit_living.Knockdown(10 + level_current * 4)
hit_living.Knockdown(GetKnockdown())
hit_living.spin(10, 1)
#undef HASTE_GETUP_LEVEL
@@ -1,28 +1,33 @@
#define LUNGE_INSTANT_LEVEL 4
#define LUNGE_INSTANT_RANGE 6
/datum/action/cooldown/bloodsucker/targeted/lunge
name = "Predatory Lunge"
desc = "Spring at your target to grapple them without warning, or tear the dead's heart out. Attacks from concealment or the rear may even knock them down if strong enough."
button_icon_state = "power_lunge"
power_explanation = "Predatory Lunge:\n\
Click any player to start spinning wildly and, after a short delay, dash at them.\n\
When lunging at someone, you will grab them, immediately starting off at aggressive.\n\
Riot gear and Monster Hunters are protected and will only be passively grabbed.\n\
You cannot use the Power if you are already grabbing someone, or are being grabbed.\n\
If you grab from behind, or while using cloak of darkness, you will knock the target down.\n\
If used on a dead body, will tear out a random organ from the zone you are targeting.\n\
Higher levels increase the knockdown dealt to enemies.\n\
At level 4, you will no longer spin, but you will be limited to tackling from only 6 tiles away."
power_flags = NONE
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED|AB_CHECK_LYING|AB_CHECK_PHASED|AB_CHECK_LYING
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 10
cooldown_time = 10 SECONDS
power_activates_immediately = FALSE
unset_after_click = FALSE
/datum/action/cooldown/bloodsucker/targeted/lunge/upgrade_power()
/datum/action/cooldown/bloodsucker/targeted/lunge/get_power_explanation_extended()
. = list()
. += "Click any player to start spinning wildly and, after a short delay, dash at them."
. += "When lunging at someone, you will grab them, immediately starting off at aggressive."
. += "Riot gear and Monster Hunters are protected and will only be passively grabbed."
. += "You cannot use the Power if you are already grabbing someone, or are being grabbed."
. += "If you grab from behind, or while using cloak of darkness, you will knock the target down."
. += "If used on a dead body, will tear out a random organ from the zone you are targeting."
. += "Higher levels increase how long enemies are knocked down."
. += "At level [LUNGE_INSTANT_LEVEL], you will no longer spin, but you will be limited to tackling from only [LUNGE_INSTANT_RANGE] tiles away."
/datum/action/cooldown/bloodsucker/targeted/lunge/on_power_upgrade()
. = ..()
//range is lowered when you get stronger.
if(level_current > 3)
target_range = 6
//range is lowered when you get stronger, since it's instant now.
if(level_current > LUNGE_INSTANT_LEVEL)
target_range = LUNGE_INSTANT_RANGE
/datum/action/cooldown/bloodsucker/targeted/lunge/can_use(mob/living/carbon/user, trigger_flags)
. = ..()
@@ -53,23 +58,19 @@
var/mob/living/turf_target = target_atom
if(!isturf(turf_target.loc))
return FALSE
// Check: can the Bloodsucker even move?
var/mob/living/user = owner
if(user.body_position == LYING_DOWN || HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/targeted/lunge/can_deactivate()
return !(datum_flags & DF_ISPROCESSING) //only if you aren't lunging
/datum/action/cooldown/bloodsucker/targeted/lunge/FireTargetedPower(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/lunge/FireTargetedPower(atom/target, params)
. = ..()
owner.face_atom(target_atom)
if(level_current > 3)
do_lunge(target_atom)
owner.face_atom(target)
if(level_current >= LUNGE_INSTANT_LEVEL)
do_lunge(target)
return
prepare_target_lunge(target_atom)
prepare_target_lunge(target)
return TRUE
///Starts processing the power and prepares the lunge by spinning, calls lunge at the end of it.
@@ -123,7 +124,7 @@
lunge_end(hit_atom, targeted_turf)
/datum/action/cooldown/bloodsucker/targeted/lunge/proc/lunge_end(atom/hit_atom, turf/target_turf)
power_activated_sucessfully()
PowerActivatedSuccesfully()
// Am I next to my target to start giving the effects?
if(!owner.Adjacent(hit_atom))
return
@@ -180,6 +181,11 @@
// Did we knock them down?
/datum/action/cooldown/bloodsucker/targeted/lunge/DeactivatePower()
/datum/action/cooldown/bloodsucker/targeted/lunge/DeactivatePower(deactivate_flags)
. = ..()
if(!.)
return
REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, BLOODSUCKER_TRAIT)
return ..()
#undef LUNGE_INSTANT_LEVEL
#undef LUNGE_INSTANT_RANGE
@@ -7,30 +7,62 @@
* Level 5: Doesn't need to be facing you anymore
*/
#define MESMERIZE_MUTE_LEVEL 2
#define MESMERIZE_GLASSES_LEVEL 3
#define MESMERIZE_FACING_LEVEL 5
/datum/action/cooldown/bloodsucker/targeted/mesmerize
name = "Mesmerize"
desc = "Dominate the mind of a mortal who can see your eyes."
button_icon_state = "power_mez"
power_explanation = "Mesmerize:\n\
Click any player to attempt to mesmerize them.\n\
You cannot wear anything covering your face, and both parties must be facing eachother. Obviously, both parties need to not be blind. \n\
If your target is already mesmerized or a Monster Hunter, the Power will fail.\n\
Once mesmerized, the target will be unable to move for a certain amount of time, scaling with level.\n\
At level 2, your target will additionally be muted.\n\
At level 3, you will be able to use the power through items covering your face.\n\
At level 5, you will be able to mesmerize regardless of your target's direction.\n\
Higher levels will increase the time of the mesmerize's freeze.\n\
Additionally it works on silicon lifeforms, causing a EMP effect instead of a freeze."
power_flags = NONE
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 30
cooldown_time = 20 SECONDS
target_range = 8
cooldown_time = 30 SECONDS
target_range = 4
power_activates_immediately = FALSE
unset_after_click = FALSE
prefire_message = "Whom will you subvert to your will?"
///Our mesmerized target - Prevents several mesmerizes.
var/datum/weakref/target_ref
/// How long it takes us to mesmerize our target.
var/mesmerize_delay = 5 SECONDS
/// At what level this ability will blind the target at. Level 0 = never.
var/blind_at_level = 0
/// if the ability requires you to be physically facing the target
var/requires_facing_target = TRUE
/// if the ability requires you to not have your eyes covered
var/blocked_by_glasses = TRUE
/// if the ability will knockdown on secondary click
var/knockdown_on_secondary = FALSE
// string id timer of the current cast, used for combat glare
var/timer
// a cooldown to ensure you can't spam both the primary and secondary mesmerizes
COOLDOWN_DECLARE(mesmerize_cooldown)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/get_power_desc_extended()
. += "[src] a target, locking them in place for a short time[level_current >= MESMERIZE_MUTE_LEVEL ? " and muting them" : ""].<br>"
if(knockdown_on_secondary)
. += "Right clicking on your victim will apply a knockdown for [DisplayTimeText(combat_mesmerize_time())].<br>"
else
. += "Right clicking on your victim will confuse them for [DisplayTimeText(combat_mesmerize_time())]."
/datum/action/cooldown/bloodsucker/targeted/mesmerize/get_power_explanation_extended()
. = list()
. += "Click any player to attempt to mesmerize them. This will stun the victim."
. += "The victim will realize they are being mesmerized, but will be unable to talk, but at level [MESMERIZE_MUTE_LEVEL] they will be also muted."
if(blocked_by_glasses && requires_facing_target)
. += "[src] requires you to not be wearing glasses and to be facing your target."
else if(blocked_by_glasses)
. += "[src] requires you to not be wearing glasses."
else if(requires_facing_target)
. += "[src] requires you to be facing your target."
. += "You cannot wear anything covering your face, and both parties must be facing eachother."
. += "Obviously, both parties need to not be blind."
. += "Right clicking with the ability will apply a knockdown for [DisplayTimeText(combat_mesmerize_time())], but will also confuse your victim for [DisplayTimeText(get_power_time())]."
. += "If your target is already mesmerized or a bloodsucker, the Power will fail."
. += "Once mesmerized, the target will be unable to move for [DisplayTimeText(get_power_time())] and muted for [DisplayTimeText(get_mute_time())], scaling with level."
. += "At level [MESMERIZE_GLASSES_LEVEL], you will be able to use the power through items covering your face."
. += "At level [MESMERIZE_FACING_LEVEL], you will be able to mesmerize regardless of your target's direction."
. += "Additionally it works on silicon lifeforms, causing a EMP effect instead of a freeze."
/datum/action/cooldown/bloodsucker/targeted/mesmerize/can_use(mob/living/carbon/user, trigger_flags)
. = ..()
@@ -41,7 +73,7 @@
to_chat(user, span_warning("You have no eyes with which to mesmerize."))
return FALSE
// Check: Eyes covered?
if(istype(user) && (user.is_eyes_covered() && level_current <= 2) || !isturf(user.loc))
if(blocked_by_glasses && istype(user) && (user.is_eyes_covered() && level_current <= 2) || !isturf(user.loc))
user.balloon_alert(user, "your eyes are concealed from sight.")
return FALSE
return TRUE
@@ -58,9 +90,11 @@
return FALSE
var/mob/living/current_target = target_atom // We already know it's carbon due to CheckValidTarget()
// No mind
#ifndef BLOODSUCKER_TESTING
if(!current_target.mind)
owner.balloon_alert(owner, "[current_target] is mindless.")
return FALSE
#endif
// Bloodsucker
if(IS_BLOODSUCKER(current_target))
owner.balloon_alert(owner, "bloodsuckers are immune to [src].")
@@ -78,11 +112,11 @@
owner.balloon_alert(owner, "[current_target] is blind.")
return FALSE
// Facing target?
if(!is_source_facing_target(owner, current_target)) // in unsorted.dm
if(requires_facing_target && !is_source_facing_target(owner, current_target)) // in unsorted.dm
owner.balloon_alert(owner, "you must be facing [current_target].")
return FALSE
// Target facing me? (On the floor, they're facing everyone)
if(((current_target.mobility_flags & MOBILITY_STAND) && !is_source_facing_target(current_target, owner) && level_current <= 4))
if(((current_target.mobility_flags & MOBILITY_STAND) && requires_facing_target && !is_source_facing_target(current_target, owner) && level_current <= MESMERIZE_FACING_LEVEL))
owner.balloon_alert(owner, "[current_target] must be facing you.")
return FALSE
@@ -90,53 +124,120 @@
target_ref = WEAKREF(current_target)
return TRUE
/datum/action/cooldown/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/target_atom)
. = ..()
/datum/action/cooldown/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/target, params)
var/mob/living/user = owner
var/mob/living/carbon/mesmerized_target = target_ref.resolve()
var/mob/living/carbon/mesmerized_target = target_ref?.resolve()
if(!COOLDOWN_FINISHED(src, mesmerize_cooldown))
return
if(!mesmerized_target)
CRASH("mesmerized_target is null")
perform_indicators(mesmerized_target, mesmerize_delay)
if(issilicon(mesmerized_target))
var/mob/living/silicon/mesmerized = mesmerized_target
mesmerized.emp_act(EMP_HEAVY)
owner.balloon_alert(owner, "temporarily shut [mesmerized] down.")
power_activated_sucessfully() // PAY COST! BEGIN COOLDOWN!
PowerActivatedSuccesfully() // PAY COST! BEGIN COOLDOWN!
return
// slow them down during the mesmerize
mute_target(mesmerized_target)
if(istype(mesmerized_target))
owner.balloon_alert(owner, "attempting to hypnotically gaze [mesmerized_target]...")
if(!do_after(user, 4 SECONDS, mesmerized_target, NONE, TRUE, extra_checks = CALLBACK(src, PROC_REF(ContinueActive), user, mesmerized_target)))
COOLDOWN_START(src, mesmerize_cooldown, mesmerize_delay)
if(!do_after(user, mesmerize_delay, mesmerized_target, IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE, TRUE, extra_checks = CALLBACK(src, PROC_REF(ContinueActive), user, mesmerized_target)))
StartCooldown(cooldown_time * 0.5)
return
var/power_time = 9 SECONDS + level_current * 1.5 SECONDS
// Can't quite time it here, but oh well
to_chat(mesmerized_target, "[src]'s eyes look into yours, and [span_hypnophrase("you feel your mind slipping away")]...")
/*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_ONLY(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT))
if(HAS_TRAIT_FROM_ONLY(mesmerized_target, TRAIT_NO_TRANSFORM, MESMERIZE_TRAIT))
owner.balloon_alert(owner, "[mesmerized_target] is already in a hypnotic gaze.")
return
if(iscarbon(mesmerized_target))
owner.balloon_alert(owner, "successfully mesmerized [mesmerized_target].")
if(level_current >= 2)
ADD_TRAIT(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
mesmerized_target.Immobilize(power_time)
mesmerized_target.adjust_silence(power_time)
mesmerized_target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // mesmerized_target.changeNext_move(power_time) // check click.dm
ADD_TRAIT(mesmerized_target, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT) // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
addtimer(CALLBACK(src, PROC_REF(end_mesmerize), user, mesmerized_target), power_time)
power_activated_sucessfully() // PAY COST! BEGIN COOLDOWN!
owner.balloon_alert(owner, "successfully mesmerized [mesmerized_target].")
mesmerize_effects(user, mesmerized_target)
PowerActivatedSuccesfully() // PAY COST! BEGIN COOLDOWN!
/datum/action/cooldown/bloodsucker/targeted/mesmerize/DeactivatePower()
target_ref = null
/datum/action/cooldown/bloodsucker/targeted/mesmerize/FireSecondaryTargetedPower(atom/target, params)
if(!isliving(target))
CRASH("[src] somehow casted on a non-living target, should have been stopped by CheckCanTarget.")
if(timer || !COOLDOWN_FINISHED(src, mesmerize_cooldown))
return
COOLDOWN_START(src, mesmerize_cooldown, 2 SECONDS)
var/mob/living/mesmerized_target = target
owner.balloon_alert(owner, "gazing [mesmerized_target]...")
perform_indicators(mesmerized_target, 3 SECONDS)
timer = addtimer(CALLBACK(src, PROC_REF(combat_mesmerize_effects), owner, mesmerized_target), 2 SECONDS)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/mesmerize_effects(mob/living/user, mob/living/mesmerized_target)
var/power_time = get_power_time()
mute_target(mesmerized_target)
mesmerized_target.Immobilize(power_time)
mesmerized_target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // mesmerized_target.changeNext_move(power_time) // check click.dm
ADD_TRAIT(mesmerized_target, TRAIT_NO_TRANSFORM, MESMERIZE_TRAIT) // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
addtimer(CALLBACK(src, PROC_REF(end_mesmerize), user, mesmerized_target), power_time)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/combat_mesmerize_effects(mob/living/user, mob/living/mesmerized_target)
if(!ContinueActive(user, mesmerized_target))
StartCooldown(cooldown_time * 0.5)
owner.balloon_alert(owner, "failed!")
return
to_chat(mesmerized_target, "[src]'s eyes look into yours, and [span_hypnophrase("your head becomes fuzzy for a moment")]...")
var/effect_time = combat_mesmerize_time()
mute_target(mesmerized_target)
if(knockdown_on_secondary)
mesmerized_target.Knockdown(effect_time)
else
mesmerized_target.adjust_confusion(effect_time)
PowerActivatedSuccesfully(cost_override = bloodcost * 0.5)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/get_power_time()
return 9 SECONDS + level_current * 1 SECONDS
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/get_mute_time()
return get_power_time()
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/combat_mesmerize_time()
return get_power_time() * 0.3
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/blind_target(mob/living/mesmerized_target)
if(!blind_at_level && level_current < blind_at_level)
return
mesmerized_target.become_blind(MESMERIZE_TRAIT)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/mute_target(mob/living/mesmerized_target)
if(level_current >= MESMERIZE_MUTE_LEVEL)
mesmerized_target.set_silence_if_lower(get_mute_time())
/datum/action/cooldown/bloodsucker/targeted/mesmerize/DeactivatePower(deactivate_flags)
. = ..()
target_ref = null
timer = null
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/end_mesmerize(mob/living/user, mob/living/target)
REMOVE_TRAIT(target, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(target, TRAIT_NO_TRANSFORM, MESMERIZE_TRAIT)
target.cure_blind(MESMERIZE_TRAIT)
// They Woke Up! (Notice if within view)
if(istype(user) && target.stat == CONSCIOUS && (target in view(6, get_turf(user))))
owner.balloon_alert(owner, "[target] snapped out of their trance.")
if(istype(user) && target.stat == CONSCIOUS && (target in view(target_range, get_turf(user))))
target.balloon_alert(owner, "[target] snapped out of their trance.")
/datum/action/cooldown/bloodsucker/targeted/mesmerize/ContinueActive(mob/living/user, mob/living/target)
return ..() && can_use(user) && CheckCanTarget(target)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/perform_indicators(mob/target, duration)
// Display an animated overlay over our head to indicate what's going on
eldritch_eye(target, "eye_open", 1 SECONDS)
var/main_duration = max(duration - 2 SECONDS, 1 SECONDS)
addtimer(CALLBACK(src, PROC_REF(eldritch_eye), target, "eye_flash", main_duration), 1 SECONDS)
addtimer(CALLBACK(src, PROC_REF(eldritch_eye), target, "eye_close", 1 SECONDS), main_duration + 1 SECONDS)
/// Display an animated overlay over our head to indicate what's going on
/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/eldritch_eye(mob/target, icon_state = "eye_open", duration = 1 SECONDS)
var/image/image = image('icons/effects/eldritch.dmi', owner, icon_state, ABOVE_ALL_MOB_LAYER, pixel_x = -owner.pixel_x, pixel_y = 28) /// TODO make this disable cloak
SET_PLANE_EXPLICIT(image, ABOVE_LIGHTING_PLANE, owner)
flick_overlay_global(image, list(owner?.client, target?.client), duration)
#undef MESMERIZE_GLASSES_LEVEL
#undef MESMERIZE_FACING_LEVEL
#undef MESMERIZE_MUTE_LEVEL
@@ -2,19 +2,21 @@
name = "Trespass"
desc = "Become mist and advance two tiles in one direction. Useful for skipping past doors and barricades."
button_icon_state = "power_tres"
power_explanation = "Trespass:\n\
Click anywhere from 1-2 tiles away from you to teleport.\n\
This power goes through all obstacles except Walls.\n\
Higher levels decrease the sound played from using the Power, and increase the speed of the transition."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
bloodsucker_check_flags = BP_CANT_USE_IN_TORPOR
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 10
cooldown_time = 8 SECONDS
prefire_message = "Select a destination."
//target_range = 2
target_range = 2
var/turf/target_turf // We need to decide where we're going based on where we clicked. It's not actually the tile we clicked.
/datum/action/cooldown/bloodsucker/targeted/trespass/get_power_explanation_extended()
. = list()
. += "Click anywhere [target_range] tiles away from you to teleport."
. += "This power goes through all obstacles except Walls."
. += "Higher levels decrease the sound played from using the Power, and increase the speed of the transition."
. += "It takes [DisplayTimeText(GetTeleportDelay())] to teleport."
/datum/action/cooldown/bloodsucker/targeted/trespass/can_use(mob/living/carbon/user, trigger_flags)
. = ..()
if(!.)
@@ -23,7 +25,6 @@
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/targeted/trespass/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
@@ -33,7 +34,6 @@
return FALSE
return TRUE // All we care about is destination. Anything you click is fine.
/datum/action/cooldown/bloodsucker/targeted/trespass/CheckCanTarget(atom/target_atom)
// NOTE: Do NOT use ..()! We don't want to check distance or anything.
@@ -58,7 +58,7 @@
return TRUE
/datum/action/cooldown/bloodsucker/targeted/trespass/FireTargetedPower(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/trespass/FireTargetedPower(atom/target, params)
. = ..()
// Find target turf, at or below Atom
@@ -76,7 +76,7 @@
puff.set_up(3, 0, my_turf)
puff.start()
var/mist_delay = max(5, 20 - level_current * 2.5) // Level up and do this faster.
var/mist_delay = GetTeleportDelay() // Level up and do this faster.
// Freeze Me
user.Stun(mist_delay, ignore_canstun = TRUE)
@@ -104,3 +104,6 @@
puff.effect_type = /obj/effect/particle_effect/fluid/smoke/vampsmoke
puff.set_up(3, 0, target_turf)
puff.start()
/datum/action/cooldown/bloodsucker/targeted/trespass/proc/GetTeleportDelay()
return max(5, 20 - level_current * 2.5)
@@ -7,21 +7,17 @@
/datum/action/cooldown/bloodsucker/targeted/tremere
name = "Tremere Gift"
desc = "A Tremere exclusive gift."
button_icon_state = "power_auspex"
background_icon_state = "tremere_power_off"
desc = ""
power_explanation = ""
active_background_icon_state = "tremere_power_on"
base_background_icon_state = "tremere_power_off"
button_icon = 'modular_zubbers/icons/mob/actions/tremere_bloodsucker.dmi'
background_icon = 'modular_zubbers/icons/mob/actions/tremere_bloodsucker.dmi'
// Tremere powers don't level up, we have them hardcoded.
level_current = 0
// Re-defining these as we want total control over them
power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
power_flags = BP_AM_STATIC_COOLDOWN
purchase_flags = TREMERE_CAN_BUY
// Targeted stuff
power_activates_immediately = FALSE
unset_after_click = FALSE
///The upgraded version of this Power. 'null' means it's the max level.
var/upgraded_power = null
@@ -8,102 +8,90 @@
* Level 5 - Cloak of Darkness until clicking an area, teleports the user to the selected area, causes nearby people to fall asleep.
*/
// Look to /datum/action/cooldown/spell/pointed/void_phase for help.
#define AUSPEX_BLOOD_COST_PER_TILE 5
#define AUSPEX_BLEED_LEVEL 4
#define AUSPEX_SLEEP_LEVEL 5
#define AUSPEX_ANYWHERE_LEVEL 6
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex
name = "Level 1: Auspex"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/two
name = "Auspex"
level_current = 1
desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport up to 2 tiles away."
button_icon_state = "power_auspex"
power_explanation = "Level 1: Auspex:\n\
When Activated, you will be hidden in a Cloak of Darkness.\n\
Click any area up to 2 tile away to teleport there, ending the Power.\n\
Additionally upon teleporting your Stamina will be restored."
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
bloodcost = 5
constant_bloodcost = 2
bloodsucker_check_flags = BP_CANT_USE_IN_TORPOR
bloodcost = 10
constant_bloodcost = 1
cooldown_time = 12 SECONDS
target_range = 2
prefire_message = "Where do you wish to teleport to?"
power_activates_immediately = FALSE
prefire_message = "Right click to teleport"
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/two
name = "Level 2: Auspex"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/three
level_current = 2
desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport up to 3 tiles away."
power_explanation = "Level 2: Auspex:\n\
When Activated, you will be hidden in a Cloak of Darkness.\n\
Click any area up to 3 tile away to teleport there, ending the Power.\n\
Additionally upon teleporting your Stamina will be restored."
bloodcost = 10
cooldown_time = 10 SECONDS
target_range = 3
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/on_power_upgrade()
// 1 + for default, the other + is for the upgrade that hasn't been added yet.
if(level_current >= AUSPEX_ANYWHERE_LEVEL)
target_range = 0
else
target_range = min(level_current + 2, 10)
. = ..()
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/three
name = "Level 3: Auspex"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced
level_current = 3
desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport."
power_explanation = "Level 3: Auspex:\n\
When Activated, you will be hidden in a Cloak of Darkness.\n\
Click any area up to teleport there, ending the Power.\n\
Additionally upon teleporting your Stamina will be restored."
bloodcost = 15
cooldown_time = 8 SECONDS
target_range = null
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/get_power_desc_extended()
. = "Hide yourself within a Cloak of Darkness, click on a tile to teleport"
if(target_range)
. += " up to [target_range] tiles away."
else
. += " anywhere you can see."
if(level_current >= AUSPEX_BLEED_LEVEL)
if(level_current >= AUSPEX_SLEEP_LEVEL)
. += " This will cause people at your destination to start bleeding and fall asleep."
else
. += " This will cause people at your destination to start bleeding."
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced
name = "Level 4: Auspex"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced/two
level_current = 4
desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport, leaving nearby people bleeding."
power_explanation = "Level 4: Auspex:\n\
When Activated, you will be hidden in a Cloak of Darkness.\n\
Click any area up to teleport there, ending the Power and causing people at your end location to start bleeding.\n\
Additionally upon teleporting your Stamina will be restored."
background_icon_state = "tremere_power_gold_off"
active_background_icon_state = "tremere_power_gold_on"
base_background_icon_state = "tremere_power_gold_off"
bloodcost = 20
cooldown_time = 6 SECONDS
target_range = null
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/advanced/two
name = "Level 5: Auspex"
upgraded_power = null
level_current = 5
desc = "Hide yourself within a Cloak of Darkness, click on an area to teleport, leaving nearby people bleeding and asleep."
power_explanation = "Level 5: Auspex:\n\
When Activated, you will be hidden in a Cloak of Darkness.\n\
Click any area up to teleport there, ending the Power and causing people at your end location to fall over in pain.\n\
Additionally upon teleporting your Stamina will be restored."
bloodcost = 25
cooldown_time = 8 SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/get_power_explanation_extended()
. = list()
. += "When Activated, you will be hidden in a Cloak of Darkness."
. += "[target_range ? "Click to teleport up to [target_range] tiles away, as long as you can see it" : "You can teleport anywhere you can see"]."
. += "Teleporting will refill your stamina to full."
. += "At level [AUSPEX_BLEED_LEVEL] you will cause people at your end location to start bleeding."
. += "At level [AUSPEX_SLEEP_LEVEL] you will cause people at your end location to fall asleep."
. += "At level [AUSPEX_ANYWHERE_LEVEL] you will be able to teleport anywhere, even if you cannot properly see the tile."
. += "The power will cost [AUSPEX_BLOOD_COST_PER_TILE] blood per tile that you teleport."
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return isturf(target_atom)
if(!isturf(target_atom))
return FALSE
var/turf/target_turf = target_atom
if(target_turf.is_blocked_turf_ignore_climbable())
return FALSE
if(!(target_turf in view(owner.client.view, owner.client)))
owner.balloon_alert(owner, "out of view!")
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/ActivatePower(trigger_flags)
. = ..()
owner.AddElement(/datum/element/digitalcamo)
animate(owner, alpha = 15, time = 1 SECONDS)
animate(owner, alpha = 15, time = 2 SECONDS)
return TRUE
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/DeactivatePower()
animate(owner, alpha = 255, time = 1 SECONDS)
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/DeactivatePower(deactivate_flags)
. = ..()
if(!.)
return FALSE
animate(owner, alpha = 255, time = 2 SECONDS)
owner.RemoveElement(/datum/element/digitalcamo)
return ..()
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/FireTargetedPower(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/FireSecondaryTargetedPower(atom/target, params)
. = ..()
var/mob/living/user = owner
var/turf/targeted_turf = get_turf(target_atom)
var/turf/targeted_turf = get_turf(target)
auspex_blink(user, targeted_turf)
/datum/action/cooldown/bloodsucker/targeted/tremere/auspex/proc/auspex_blink(mob/living/user, turf/targeted_turf)
var/blood_cost = AUSPEX_BLOOD_COST_PER_TILE * get_dist(user, targeted_turf)
if(!can_pay_blood(blood_cost))
owner.balloon_alert(owner, "not enough blood!")
return
playsound(user, 'sound/magic/summon_karp.ogg', 60)
playsound(targeted_turf, 'sound/magic/summon_karp.ogg', 60)
@@ -113,13 +101,18 @@
for(var/mob/living/carbon/living_mob in range(1, targeted_turf)-user)
if(IS_BLOODSUCKER(living_mob) || IS_VASSAL(living_mob))
continue
if(level_current >= 4)
if(level_current >= AUSPEX_BLEED_LEVEL)
var/obj/item/bodypart/bodypart = pick(living_mob.bodyparts)
bodypart.force_wound_upwards(/datum/wound/slash/flesh/critical)
living_mob.adjustBruteLoss(15)
if(level_current >= 5)
if(level_current >= AUSPEX_SLEEP_LEVEL)
living_mob.Knockdown(10 SECONDS, ignore_canstun = TRUE)
do_teleport(owner, targeted_turf, no_effects = TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
user.adjustStaminaLoss(-user.staminaloss)
power_activated_sucessfully()
PowerActivatedSuccesfully(cost_override = blood_cost)
#undef AUSPEX_BLOOD_COST_PER_TILE
#undef AUSPEX_BLEED_LEVEL
#undef AUSPEX_SLEEP_LEVEL
#undef AUSPEX_ANYWHERE_LEVEL
@@ -8,201 +8,209 @@
* Level 5 - Target (if at least in crit & has a mind) will revive as a Vassal for 8 minutes before dying.
*/
// Copied from mesmerize.dm
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate
name = "Level 1: Dominate"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/two
#define TEMP_VASSALIZE_COST 150
#define DOMINATE_XRAY_LEVEL 3
#define DOMINATE_NON_MUTE_VASSALIZE_LEVEL 4
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate
name = "Dominate"
button_icon_state = "power_auspex"
background_icon_state = "tremere_power_off"
active_background_icon_state = "tremere_power_on"
base_background_icon_state = "tremere_power_off"
button_icon = 'modular_zubbers/icons/mob/actions/tremere_bloodsucker.dmi'
background_icon = 'modular_zubbers/icons/mob/actions/tremere_bloodsucker.dmi'
level_current = 1
desc = "Mesmerize any foe who stands still long enough."
button_icon_state = "power_dominate"
power_explanation = "Level 1: Dominate:\n\
Click any person to, after a 4 second timer, Mesmerize them.\n\
This will completely immobilize them for the next 10.5 seconds."
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = TREMERE_CAN_BUY
bloodcost = 15
constant_bloodcost = 2
cooldown_time = 50 SECONDS
constant_bloodcost = 0.1
target_range = 6
prefire_message = "Select a target."
mesmerize_delay = 4 SECONDS
blind_at_level = 3
requires_facing_target = FALSE
blocked_by_glasses = FALSE
knockdown_on_secondary = TRUE
/// Data huds to show while the power is active
var/list/datahuds = list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC, DATA_HUD_BOT_PATH)
var/list/thralls = list()
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/two
name = "Level 2: Dominate"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/three
level_current = 2
desc = "Mesmerize and mute any foe who stands still long enough."
power_explanation = "Level 2: Dominate:\n\
Click any person to, after a 4 second timer, Mesmerize them.\n\
This will completely immobilize and mute them for the next 12 seconds."
bloodcost = 20
cooldown_time = 40 SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/three
name = "Level 3: Dominate"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced
level_current = 3
desc = "Mesmerize, mute and blind any foe who stands still long enough."
power_explanation = "Level 3: Dominate:\n\
Click any person to, after a 4 second timer, Mesmerize them.\n\
This will completely immobilize, mute, and blind them for the next 13.5 seconds."
bloodcost = 30
cooldown_time = 35 SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/CheckValidTarget(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/Remove(mob/removed_from)
. = ..()
if(!.)
return FALSE
return isliving(target_atom)
for(var/thrall in thralls)
if(!thrall)
continue
end_possession(thrall)
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/CheckCanTarget(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/get_power_desc_extended()
. = ..()
if(!.)
return FALSE
if(level_current >= DOMINATE_VASSALIZE_LEVEL)
. += "If your target is in critical condition or dead, they will instead be turned into a temporary Vassal. This will cost [TEMP_VASSALIZE_COST] blood. Pre-existing dead vassals will simply be revived."
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/get_power_explanation_extended()
. = list()
. += "Click any person to, after [DisplayTimeText(mesmerize_delay)], stun them for [DisplayTimeText(get_power_time())]."
. += "Right clicking on your victim however will apply a knockdown will confuse and slow them down for [DisplayTimeText(get_power_time())]."
. += "A left click will completely immobilize, and blind them for the next [DisplayTimeText(get_power_time())] seconds, and will also mute them for [DisplayTimeText(get_power_time())] seconds."
. += "While this ability is active, you will be able to see additional information about everyone in the room."
. += "At level [DOMINATE_XRAY_LEVEL], you will gain X-Ray vision while this ability is active."
. += "At level [DOMINATE_VASSALIZE_LEVEL], while adjacent to the target, if your target is in critical condition or dead, they will instead be turned into a temporary Vassal. This will cost [TEMP_VASSALIZE_COST] blood."
. += "The victim must have atleast [BLOOD_VOLUME_BAD] blood to be vassalized."
. += "The vassal will be mute and deaf if the level of [src] is not at least [DOMINATE_NON_MUTE_VASSALIZE_LEVEL]"
. += "If you use this on a currently dead normal Vassal, they will will not suddenly cease to live as if a temporary Vassal."
. += "They will have complete loyalty to you, until their death in [DisplayTimeText(get_vassal_duration())] upon use."
. += "Vassalizing or reviving a vassal will make this ability go on cooldown for [DisplayTimeText(get_vassalize_cooldown())]."
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/CheckCanTarget(atom/target_atom)
var/mob/living/selected_target = target_atom
if(!selected_target.mind)
owner.balloon_alert(owner, "[selected_target] is mindless.")
if(level_current >= DOMINATE_VASSALIZE_LEVEL && (IS_VASSAL(selected_target) || selected_target.stat >= SOFT_CRIT))
if(selected_target?.mind && owner.Adjacent(selected_target))
return TRUE
. = ..()
if(!.)
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced
name = "Level 4: Possession"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/two
level_current = 4
desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged 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, 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"
base_background_icon_state = "tremere_power_gold_off"
bloodcost = 80
cooldown_time = 3 MINUTES
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/two
name = "Level 5: Possession"
desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged 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, 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
// The advanced version
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/CheckCanTarget(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/ContinueActive(mob/living/user, mob/living/target)
if(!target)
return can_use(user)
. = ..()
if(!.)
return FALSE
var/mob/living/selected_target = target_atom
if((IS_VASSAL(selected_target) || selected_target.stat >= SOFT_CRIT) && !owner.Adjacent(selected_target))
owner.balloon_alert(owner, "out of range.")
return FALSE
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/ActivatePower(atom/target)
. = ..()
if(level_current >= DOMINATE_XRAY_LEVEL)
ADD_TRAIT(owner, TRAIT_XRAY_VISION, DOMINATE_TRAIT)
for(var/hudtype in datahuds)
var/datum/atom_hud/data_hud = GLOB.huds[hudtype]
data_hud.show_to(owner)
owner.update_sight()
return TRUE
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/FireTargetedPower(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/DeactivatePower(deactivate_flags)
. = ..()
var/mob/living/target = target_atom
if(!.)
return
if(level_current >= DOMINATE_XRAY_LEVEL)
REMOVE_TRAIT(owner, TRAIT_XRAY_VISION, DOMINATE_TRAIT)
for(var/hudtype in datahuds)
var/datum/atom_hud/data_hud = GLOB.huds[hudtype]
data_hud.hide_from(owner)
owner.update_sight()
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/FireTargetedPower(atom/target, params)
var/mob/living/target_mob = target
var/mob/living/user = owner
if(target.stat != CONSCIOUS && level_current >= 4)
if(target_mob.stat != CONSCIOUS && level_current >= DOMINATE_VASSALIZE_LEVEL)
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")
return
attempt_mesmerize(target, user)
else
if(IS_VASSAL(target_mob))
owner.balloon_alert(owner, "too far to revive!")
else
owner.balloon_alert(owner, "too far to vassalize!")
return TRUE
return ..()
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/proc/attempt_mesmerize(mob/living/target, mob/living/user)
owner.balloon_alert(owner, "attempting to mesmerize.")
if(!do_after(user, 3 SECONDS, target, NONE, TRUE))
return
power_activated_sucessfully()
var/power_time = 90 + level_current * 15
/*if(IS_MONSTERHUNTER(target))
to_chat(target, span_notice("You feel you something crawling under your skin, but it passes."))
return*/
// 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))
var/mob/living/carbon/mesmerized = target
owner.balloon_alert(owner, "successfully mesmerized [mesmerized].")
if(level_current >= 2)
ADD_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
if(level_current >= 3)
target.become_blind(BLOODSUCKER_TRAIT)
mesmerized.Immobilize(power_time)
mesmerized.next_move = world.time + power_time
ADD_TRAIT(mesmerized, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT)
addtimer(CALLBACK(src, PROC_REF(end_mesmerize), user, target), power_time)
if(issilicon(target))
var/mob/living/silicon/mesmerized = target
mesmerized.emp_act(EMP_HEAVY)
owner.balloon_alert(owner, "temporarily shut [mesmerized] down.")
/datum/action/cooldown/bloodsucker/targeted/tremere/proc/end_mesmerize(mob/living/user, mob/living/target)
REMOVE_TRAIT(target, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT)
target.cure_blind(BLOODSUCKER_TRAIT)
REMOVE_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
if(istype(user) && target.stat == CONSCIOUS && (target in view(6, get_turf(user))))
owner.balloon_alert(owner, "[target] snapped out of their trance.")
/datum/action/cooldown/bloodsucker/targeted/tremere/dominate/proc/attempt_vassalize(mob/living/target, mob/living/user)
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!.")
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/proc/attempt_vassalize(mob/living/target, mob/living/user)
owner.face_atom(target)
var/datum/antagonist/vassal/vassal = IS_VASSAL(target)
if(!victim_has_blood(target))
return FALSE
owner.balloon_alert(owner, "attempting to vassalize.")
if(vassal)
owner.balloon_alert(owner, "attempting to revive.")
else
owner.balloon_alert(owner, "attempting to vassalize.")
if(!do_after(user, 6 SECONDS, target, NONE, TRUE))
return FALSE
if(is_vassal?.master == bloodsuckerdatum_power)
if(!victim_has_blood(target))
return FALSE
if(vassal?.master == bloodsuckerdatum_power)
if(target.stat != DEAD)
owner.balloon_alert(owner, "not dead!")
return FALSE
power_activated_sucessfully()
PowerActivatedSuccesfully(get_vassalize_cooldown())
to_chat(user, span_warning("We revive [target]!"))
owner.balloon_alert(owner, "successfully revived!")
target.mind.grab_ghost()
target.mind?.grab_ghost()
target.revive(ADMIN_HEAL_ALL)
pay_cost(TEMP_VASSALIZE_COST - bloodcost)
log_combat(owner, target, "tremere revived", addition="Revived their vassal using dominate")
return FALSE
if(!bloodsuckerdatum_power.make_vassal(target))
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*/
power_activated_sucessfully()
PowerActivatedSuccesfully(get_vassalize_cooldown())
to_chat(user, span_warning("We revive [target]!"))
target.mind.grab_ghost()
// no escaping at this point
target.mind?.grab_ghost(TRUE)
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 = (1 MINUTES) * level_current
if(level_current <= 4)
var/living_time = get_vassal_duration()
log_combat(owner, target, "tremere mindslaved", addition="Revived and converted [target] into a temporary tremere vassal for [DisplayTimeText(living_time)].")
if(level_current <= DOMINATE_NON_MUTE_VASSALIZE_LEVEL)
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))
user.balloon_alert(target, "only [DisplayTimeText(living_time)] left to live!")
to_chat(target, span_warning("You will only live for [DisplayTimeText(living_time)]! Obey your master and go out in a blaze of glory!"))
var/timer_id = addtimer(CALLBACK(src, PROC_REF(end_possession), target), living_time, TIMER_STOPPABLE)
// timer that only the master and thrall can see
setup_timer(user, target, living_time, timer_id)
thralls += target
RegisterSignals(target, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING), PROC_REF(end_possession), timer_id)
RegisterSignal(vassaldatum, COMSIG_ANTAGONIST_REMOVED, PROC_REF(on_antag_datum_removal), target, timer_id)
pay_cost(TEMP_VASSALIZE_COST - bloodcost)
return TRUE
/datum/action/cooldown/bloodsucker/targeted/tremere/proc/end_possession(mob/living/user)
if(!IS_VASSAL(user) && !HAS_TRAIT_FROM_ONLY(user, TRAIT_MUTE, DOMINATE_TRAIT) && !HAS_TRAIT_FROM_ONLY(user, TRAIT_MUTE, DOMINATE_TRAIT))
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/proc/victim_has_blood(mob/living/target)
// you can always revive non-temporary vassals
if(IS_VASSAL(target))
return TRUE
if(target.blood_volume < BLOOD_VOLUME_BAD)
owner.balloon_alert(owner, "not enough blood in victim!")
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/proc/setup_timer(mob/living/user, mob/living/target, living_time, timer_id)
var/list/show_to = list(user, target)
if(bloodsuckerdatum_power && length(bloodsuckerdatum_power.vassals))
for(var/datum/antagonist/vassal in bloodsuckerdatum_power.vassals)
if(!vassal?.owner?.current)
continue
show_to += vassal.owner.current
new /atom/movable/screen/text/screen_timer/attached(null, show_to, timer_id, "Dies in ${timer}", null, null, target)
new /atom/movable/screen/text/screen_timer(null, target, timer_id, "You die in ${timer}")
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/proc/on_antag_datum_removal(datum/antagonist/vassal, mob/living/thrall, timer_id)
end_possession(thrall, timer_id)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/proc/end_possession(mob/living/user, timer_id)
if(timer_id)
deltimer(timer_id)
if(!user)
CRASH("[src] end_possession called with no user!")
if(!(user in thralls))
return
thralls -= user
user.remove_traits(list(TRAIT_MUTE, TRAIT_DEAF), DOMINATE_TRAIT)
user.mind.remove_antag_datum(/datum/antagonist/vassal)
if(!HAS_TRAIT(user, TRAIT_NOBLOOD))
user.blood_volume = 0
if(!IS_VASSAL(user))
to_chat(user, span_warning("You feel the blood keeping you alive run out!"))
return
to_chat(user, span_warning("You feel the Blood of your Master run out!"))
user.mind?.remove_antag_datum(/datum/antagonist/vassal)
if(user.stat == DEAD)
return
user.death()
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/proc/get_vassal_duration()
return 4 MINUTES * max(level_current, 1)
/datum/action/cooldown/bloodsucker/targeted/mesmerize/dominate/proc/get_vassalize_cooldown()
return cooldown_time * 3
#undef TEMP_VASSALIZE_COST
#undef DOMINATE_XRAY_LEVEL
@@ -8,119 +8,205 @@
* Level 5 - Bloodbeam spell that breaks open lockers/doors + double damage & steals blood - Gives them a Blood shield until they use Bloodbeam
*/
#define BLOOD_SHIELD_BLOCK_CHANCE 75
#define BLOOD_SHIELD_BLOOD_COST 15
#define THAUMATURGY_BLOOD_COST_PER_CHARGE 5
#define THAUMATURGY_COOLDOWN_PER_CHARGE 5 SECONDS
#define THAUMATURGY_SHIELD_LEVEL 2
#define THAUMATURGY_DOOR_BREAK_LEVEL 3
#define THAUMATURGY_EXTRA_DAMAGE_LEVEL 4
#define THAUMATURGY_BLOOD_STEAL_LEVEL 5
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy
name = "Level 1: Thaumaturgy"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/two
desc = "Fire a blood bolt at your enemy, dealing Burn damage."
name = "Thaumaturgy"
level_current = 1
button_icon_state = "power_thaumaturgy"
power_explanation = "Thaumaturgy:\n\
Gives you a one shot blood bolt spell, firing it at a person deals 20 Burn damage"
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
bloodcost = 20
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
// custom cooldown handling based on charges
power_flags = BP_AM_STATIC_COOLDOWN
bloodcost = 5
constant_bloodcost = 0
cooldown_time = 6 SECONDS
prefire_message = "Click where you wish to fire."
///Blood shield given while this Power is active.
// 5 seconds per charge
cooldown_time = 10 SECONDS
prefire_message = "Right click where you wish to fire."
click_to_activate = TRUE // you pay to replenish charges
power_activates_immediately = FALSE
unset_after_click = FALSE // Lets us cast multiple times
/// How many times you can shoot before you need to recast
var/charges = 0
/// How long it takes before you can shoot again
var/shot_cooldown = 0
var/datum/weakref/blood_shield
var/obj/projectile/magic/arcane_barrage/bloodsucker/magic_9ball
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/two
name = "Level 2: Thaumaturgy"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/three
desc = "Create a Blood shield and fire a blood bolt at your enemy, dealing Burn damage."
level_current = 2
power_explanation = "Thaumaturgy:\n\
Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
You will also have the ability to fire a Blood beam, ending the Power.\n\
If the Blood beam hits a person, it will deal 20 Burn damage."
prefire_message = "Click where you wish to fire (using your power removes blood shield)."
bloodcost = 40
cooldown_time = 4 SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/three
name = "Level 3: Thaumaturgy"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced
desc = "Create a Blood shield and fire a blood bolt, dealing Burn damage and opening doors/lockers."
level_current = 3
power_explanation = "Thaumaturgy:\n\
Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
You will also have the ability to fire a Blood beam, ending the Power.\n\
If the Blood beam hits a person, it will deal 20 Burn damage. If it hits a locker or door, it will break it open."
bloodcost = 50
cooldown_time = 6 SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced
name = "Level 4: Blood Strike"
upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced/two
desc = "Create a Blood shield and fire a blood bolt, dealing Burn damage and opening doors/lockers."
level_current = 4
power_explanation = "Thaumaturgy:\n\
Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
You will also have the ability to fire a Blood beam, ending the Power.\n\
If the Blood beam hits a person, it will deal 40 Burn damage.\n\
If it hits a locker or door, it will break it open."
background_icon_state = "tremere_power_gold_off"
active_background_icon_state = "tremere_power_gold_on"
base_background_icon_state = "tremere_power_gold_off"
prefire_message = "Click where you wish to fire (using your power removes blood shield)."
bloodcost = 60
cooldown_time = 6 SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/advanced/two
name = "Level 5: Blood Strike"
upgraded_power = null
desc = "Create a Blood shield and fire a blood bolt, dealing Burn damage, stealing Blood and opening doors/lockers."
level_current = 5
power_explanation = "Thaumaturgy:\n\
Activating Thaumaturgy will temporarily give you a Blood Shield,\n\
The blood shield has a 75% block chance, but costs 15 Blood per hit to maintain.\n\
You will also have the ability to fire a Blood beam, ending the Power.\n\
If the Blood beam hits a person, it will deal 40 Burn damage and steal blood to feed yourself, though at a net-negative.\n\
If it hits a locker or door, it will break it open."
bloodcost = 80
cooldown_time = 8 SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/ActivatePower(trigger_flags)
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/Grant()
charges = get_max_charges()
. = ..()
owner.balloon_alert(owner, "you start thaumaturgy")
if(level_current >= 2) // Only if we're at least level 2.
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/Remove()
. = ..()
var/shield = blood_shield?.resolve()
if(shield)
QDEL_NULL(shield)
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/on_power_upgrade()
cooldown_time = get_max_charges() * THAUMATURGY_COOLDOWN_PER_CHARGE
bloodcost = get_max_charges() * THAUMATURGY_BLOOD_COST_PER_CHARGE
// just in case you somehow level up while the power is active
charges = get_max_charges()
. = ..()
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/get_power_desc_extended()
. = "<br>Projectile can seek for [get_shot_range()] tiles.<br>"
. += "Fire a slow seeking blood bolt at your enemy.<br>"
if(level_current >= THAUMATURGY_SHIELD_LEVEL)
. += "Right click the button to create a blood shield<br>"
if(level_current >= THAUMATURGY_DOOR_BREAK_LEVEL)
. += "The projectile will open doors/lockers"
if(level_current >= THAUMATURGY_BLOOD_STEAL_LEVEL)
. += " and steal blood from the target"
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/get_power_explanation_extended()
. = list()
. += "Thaumaturgy grants you the ability to cast and shoot a slow moving target seeking blood projectile."
. += "The projectile will auto aim to a nearby mob if you aim at the ground."
. += "If the Blood blast hits a person, it will deal [get_blood_bolt_damage()] [initial(magic_9ball.damage_type)] damage, and is blocked by [initial(magic_9ball.armor_flag)] armor."
. += "You can use Blood blast [get_max_charges()] times before needing to recast Thaumaturgy. After each shot you will have to wait [DisplayTimeText(get_shot_cooldown())]."
. += "At level [THAUMATURGY_SHIELD_LEVEL] it will grant you a shield that will block [BLOOD_SHIELD_BLOCK_CHANCE]% of incoming damage, costing you [THAUMATURGY_BLOOD_COST_PER_CHARGE] blood each time."
. += "To activate the shield, right click the action button."
. += "At level [THAUMATURGY_DOOR_BREAK_LEVEL], it will also break open lockers and doors."
. += "At level [THAUMATURGY_BLOOD_STEAL_LEVEL], it will also steal blood to feed yourself, just as much as each charge costs."
. += "The cooldown increases by [DisplayTimeText(THAUMATURGY_COOLDOWN_PER_CHARGE)] per charge used, and each blast costs [THAUMATURGY_BLOOD_COST_PER_CHARGE] blood."
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/ActivatePower(mob/target)
. = ..()
charges = get_max_charges()
toggle_blood_shield(TRUE)
return TRUE
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/proc/toggle_blood_shield(toggle)
if(level_current < THAUMATURGY_SHIELD_LEVEL)
return
// don't toggle if we're already in the state we want to be in
if(toggle == !!blood_shield)
return
if(blood_shield)
var/shield = blood_shield?.resolve()
owner.visible_message(
span_warning("[owner]\'s [blood_shield] looses it's form and dissapears into [src]'\s hands "),
span_warning("We unform our Blood shield!"),
span_hear("You hear liquids sloshing around."),
)
owner.balloon_alert(owner, "you unform the [shield]")
qdel(shield)
blood_shield = null
else
var/obj/item/shield/bloodsucker/new_shield = new
blood_shield = WEAKREF(new_shield)
if(!owner.put_in_inactive_hand(new_shield))
QDEL_NULL(new_shield)
owner.balloon_alert(owner, "off hand is full!")
to_chat(owner, span_notice("Blood shield couldn't be activated as your off hand is full."))
to_chat(owner, span_notice("[capitalize(src)] couldn't be activated as your off hand is full."))
return FALSE
owner.balloon_alert(owner, "you form the [src]")
owner.visible_message(
span_warning("[owner]\'s hands begins to bleed and forms into a blood shield!"),
span_warning("We activate our Blood shield!"),
span_warning("[owner]\'s hands begins to bleed and forms into a [src]!"),
span_warning("We form our [src]!"),
span_hear("You hear liquids forming together."),
)
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/DeactivatePower()
if(blood_shield)
QDEL_NULL(blood_shield)
return ..()
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/FireTargetedPower(atom/target_atom)
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/DeactivatePower(deactivate_flags)
. = ..()
if(!.)
return
var/used_charges = get_max_charges() - charges
toggle_blood_shield(FALSE)
if(used_charges > 0)
StartCooldown()
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/StartCooldown(override_cooldown_time, override_melee_cooldown_time)
var/used_charges = get_max_charges() - charges
// no cooldown if we didn't use any charges
if(used_charges <= 0)
return
charges = get_max_charges()
return ..(used_charges * THAUMATURGY_COOLDOWN_PER_CHARGE, override_melee_cooldown_time)
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/proc/get_blood_bolt_damage()
if(level_current >= THAUMATURGY_EXTRA_DAMAGE_LEVEL)
return 40
return 20
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/proc/get_max_charges()
return level_current * 2
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/proc/get_shot_cooldown()
return max(1.5 - (level_current * 0.1), 0) SECONDS
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/proc/get_shot_range()
return initial(magic_9ball.range) + level_current * 10
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/update_button_status(atom/movable/screen/movable/action_button/button, force)
. = ..()
if(next_use_time - world.time <= 0)
button.maptext = MAPTEXT_TINY_UNICODE(span_center("[charges]/[get_max_charges()]"))
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/FireSecondaryTargetedPower(atom/target, params)
if(shot_cooldown > world.time)
return
if(!can_pay_blood(THAUMATURGY_BLOOD_COST_PER_CHARGE))
owner.balloon_alert(owner, "not enough blood!")
DeactivatePower()
return
shot_cooldown = world.time + get_shot_cooldown()
var/mob/living/user = owner
owner.balloon_alert(owner, "you fire a blood bolt!")
to_chat(user, span_warning("You fire a blood bolt!"))
owner.visible_message(
span_warning("[owner] fires a blood bolt at [target]!"),
span_warning("You fire a blood bolt at [target]!"),
span_hear("You hear a loud crackling sound."),
)
user.changeNext_move(CLICK_CD_RANGE)
user.newtonian_move(get_dir(target_atom, user))
var/obj/projectile/magic/arcane_barrage/bloodsucker/magic_9ball = new(user.loc)
magic_9ball.bloodsucker_power = src
magic_9ball.firer = user
magic_9ball.def_zone = ran_zone(user.zone_selected)
magic_9ball.preparePixelProjectile(target_atom, user)
INVOKE_ASYNC(magic_9ball, TYPE_PROC_REF(/obj/projectile, fire))
playsound(user, 'sound/magic/wand_teleport.ogg', 60, TRUE)
power_activated_sucessfully()
user.newtonian_move(get_dir(target, user))
user.face_atom(target)
handle_shot(user, target)
pay_cost(THAUMATURGY_BLOOD_COST_PER_CHARGE)
playsound(user, 'sound/magic/wand_teleport.ogg', 60, TRUE)
charges -= 1
build_all_button_icons(UPDATE_BUTTON_STATUS)
if(charges <= 0)
// delay the message so it doesn't overlap with the cooldown message
addtimer(CALLBACK(owner, TYPE_PROC_REF(/atom, balloon_alert), owner, "no charges left!"), 0.5 SECONDS)
PowerActivatedSuccesfully(cost_override = 0)
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/proc/handle_shot(mob/user, atom/target)
magic_9ball = new(get_turf(user))
magic_9ball.firer = user
magic_9ball.power_ref = WEAKREF(src)
magic_9ball.damage = get_blood_bolt_damage()
magic_9ball.def_zone = ran_zone(user.zone_selected, min(level_current * 10, 90))
magic_9ball.preparePixelProjectile(target, user)
// autotarget if we aim at a turf
if(isturf(target))
var/list/targets = list()
for(var/mob/living/possible_target as anything in orange(1, target))
if(!ismob(possible_target))
continue
var/datum/antagonist/vassal/vassal = IS_VASSAL(possible_target)
if(length(bloodsuckerdatum_power?.vassals) && vassal && (vassal in bloodsuckerdatum_power?.vassals))
continue
targets += possible_target
if(length(targets))
magic_9ball.set_homing_target(pick(targets))
else if(ismob(target))
magic_9ball.homing_target = target
magic_9ball.homing_turn_speed = min(10 * level_current, 90)
magic_9ball.range = initial(magic_9ball.range) + level_current * 10
INVOKE_ASYNC(magic_9ball, TYPE_PROC_REF(/obj/projectile, fire))
// ditch the pointer to reduce harddels
magic_9ball = null
/**
* # Blood Bolt
*
@@ -129,33 +215,39 @@
/obj/projectile/magic/arcane_barrage/bloodsucker
name = "blood bolt"
icon_state = "mini_leaper"
damage = 20
var/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/bloodsucker_power
damage = 1
wound_bonus = 20
armour_penetration = 30
speed = 1
pixel_speed_multiplier = 0.6
impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser
range = 30
armor_flag = LASER
var/datum/weakref/power_ref
/obj/projectile/magic/arcane_barrage/bloodsucker/on_hit(target, blocked = 0, pierce_hit)
if(istype(target, /obj/structure/closet) && bloodsucker_power.level_current >= 3)
var/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/bloodsucker_power = power_ref?.resolve()
if(!bloodsucker_power)
return ..()
if(istype(target, /obj/structure/closet) && bloodsucker_power.level_current >= THAUMATURGY_DOOR_BREAK_LEVEL)
var/obj/structure/closet/hit_closet = target
if(hit_closet)
hit_closet.welded = FALSE
hit_closet.locked = FALSE
hit_closet.broken = TRUE
hit_closet.update_appearance()
qdel(src)
return BULLET_ACT_HIT
if(istype(target, /obj/machinery/door) && bloodsucker_power.level_current >= 3)
return ..()
if(istype(target, /obj/machinery/door) && bloodsucker_power.level_current >= THAUMATURGY_DOOR_BREAK_LEVEL)
var/obj/machinery/door/hit_airlock = target
hit_airlock.open(2)
qdel(src)
return BULLET_ACT_HIT
return ..()
if(ismob(target))
if(bloodsucker_power.level_current >= 4)
damage = 40
if(bloodsucker_power.level_current >= 5)
if(bloodsucker_power.level_current >= THAUMATURGY_BLOOD_STEAL_LEVEL)
var/mob/living/person_hit = target
person_hit.blood_volume -= 60
bloodsucker_power.bloodsuckerdatum_power.AdjustBloodVolume(60)
qdel(src)
return BULLET_ACT_HIT
person_hit.blood_volume -= THAUMATURGY_BLOOD_COST_PER_CHARGE
bloodsucker_power.bloodsuckerdatum_power.AdjustBloodVolume(THAUMATURGY_BLOOD_COST_PER_CHARGE)
return ..()
. = ..()
/**
@@ -173,14 +265,23 @@
icon_state = "blood_shield"
lefthand_file = 'modular_zubbers/icons/mob/inhands/weapons/bloodsucker_lefthand.dmi'
righthand_file = 'modular_zubbers/icons/mob/inhands/weapons/bloodsucker_righthand.dmi'
block_chance = 75
block_chance = BLOOD_SHIELD_BLOCK_CHANCE
/obj/item/shield/bloodsucker/Initialize()
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, BLOODSUCKER_TRAIT)
/obj/item/shield/bloodsucker/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner)
if(bloodsuckerdatum)
bloodsuckerdatum.AdjustBloodVolume(-15)
bloodsuckerdatum.AdjustBloodVolume(-BLOOD_SHIELD_BLOOD_COST)
return ..()
#undef BLOOD_SHIELD_BLOCK_CHANCE
#undef BLOOD_SHIELD_BLOOD_COST
#undef THAUMATURGY_BLOOD_COST_PER_CHARGE
#undef THAUMATURGY_COOLDOWN_PER_CHARGE
#undef THAUMATURGY_SHIELD_LEVEL
#undef THAUMATURGY_DOOR_BREAK_LEVEL
#undef THAUMATURGY_BLOOD_STEAL_LEVEL
@@ -9,6 +9,7 @@
purchase_flags = NONE
bloodcost = 10
cooldown_time = 10 SECONDS
level_current = -1
/datum/action/cooldown/bloodsucker/distress/ActivatePower(trigger_flags)
. = ..()
@@ -20,3 +21,4 @@
var/mob/living/user = owner
user.adjustBruteLoss(10)
return TRUE
@@ -8,11 +8,13 @@
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 dead or unconcious."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_WHILE_UNCONSCIOUS
power_flags = BP_CONTINUOUS_EFFECT
check_flags = AB_CHECK_CONSCIOUS
bloodsucker_check_flags = NONE
purchase_flags = NONE
bloodcost = 1.5
cooldown_time = 10 SECONDS
level_current = -1
/datum/action/cooldown/bloodsucker/recuperate/can_use(mob/living/carbon/user, trigger_flags)
. = ..()
@@ -28,6 +30,7 @@
. = ..()
to_chat(owner, span_notice("Your muscles clench as your master's immortal blood mixes with your own, knitting your wounds."))
owner.balloon_alert(owner, "recuperate turned on.")
return TRUE
/datum/action/cooldown/bloodsucker/recuperate/process(seconds_per_tick)
. = ..()
@@ -60,6 +63,9 @@
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/recuperate/DeactivatePower()
/datum/action/cooldown/bloodsucker/recuperate/DeactivatePower(deactivate_flags)
. = ..()
if(!.)
return
owner.balloon_alert(owner, "recuperate turned off.")
return ..()
@@ -7,12 +7,11 @@
Use this power with a bloodbag in your hand to instead fill it with Vampiric Blood which \
can be used to reset ex-vassal deconversion timers. \
Right-Click will show the status of all Vassals."
power_flags = NONE
check_flags = NONE
purchase_flags = NONE
bloodcost = 10
cooldown_time = 10 SECONDS
level_current = -1
///Bloodbag we have in our hands.
var/obj/item/reagent_containers/blood/bloodbag
///Weakref to a target we're bringing into the fold.
@@ -22,7 +21,7 @@
. = ..()
if(!.)
return FALSE
var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/ex_vassal)
var/datum/antagonist/vassal/revenge/revenge_vassal = IS_REVENGE_VASSAL(owner)
if(revenge_vassal)
return FALSE
@@ -34,7 +33,7 @@
if(owner.pulling && isliving(owner.pulling))
var/mob/living/pulled_target = owner.pulling
var/datum/antagonist/ex_vassal/former_vassal = pulled_target.mind.has_antag_datum(/datum/antagonist/ex_vassal)
var/datum/antagonist/ex_vassal/former_vassal = IS_EX_VASSAL(pulled_target)
if(!former_vassal)
owner.balloon_alert(owner, "not a former vassal!")
return FALSE
@@ -53,7 +52,7 @@
/datum/action/cooldown/bloodsucker/vassal_blood/ActivatePower(trigger_flags)
. = ..()
var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/vassal/revenge)
var/datum/antagonist/vassal/revenge/revenge_vassal = IS_REVENGE_VASSAL(owner)
if(trigger_flags & TRIGGER_SECONDARY_ACTION)
for(var/datum/antagonist/ex_vassal/former_vassals as anything in revenge_vassal.ex_vassals)
var/information = "[former_vassals.owner.current]"
@@ -67,11 +66,11 @@
to_chat(owner, "[information]")
DeactivatePower()
return
return FALSE
if(target_ref)
var/mob/living/target = target_ref.resolve()
var/datum/antagonist/ex_vassal/former_vassal = target.mind.has_antag_datum(/datum/antagonist/ex_vassal)
var/datum/antagonist/ex_vassal/former_vassal = IS_EX_VASSAL(target)
if(!former_vassal || former_vassal.revenge_vassal)
target_ref = null
return
@@ -79,7 +78,7 @@
former_vassal.return_to_fold(revenge_vassal)
target_ref = null
DeactivatePower()
return
return FALSE
if(bloodbag)
var/mob/living/living_owner = owner
@@ -88,3 +87,4 @@
var/obj/item/reagent_containers/blood/o_minus/bloodsucker/new_bag = new(owner.loc)
owner.put_in_active_hand(new_bag)
DeactivatePower()
return TRUE
@@ -2,14 +2,12 @@
name = "Veil of Many Faces"
desc = "Disguise yourself in the illusion of another identity."
button_icon_state = "power_veil"
power_explanation = "Veil of Many Faces: \n\
Activating Veil of Many Faces will shroud you in smoke and forge you a new identity.\n\
Your name and appearance will be completely randomized, and turning the ability off again will undo it all.\n\
Clothes, gear, and Security/Medical HUD status is kept the same while this power is active."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS
power_flags = NONE
check_flags = AB_CHECK_CONSCIOUS
bloodsucker_check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_IN_TORPOR
purchase_flags = BLOODSUCKER_DEFAULT_POWER
bloodcost = 15
level_current = -1
constant_bloodcost = 0.1
cooldown_time = 10 SECONDS
// Outfit Vars
@@ -30,13 +28,20 @@
var/list/prev_features // For lizards and such
var/disguise_name
/datum/action/cooldown/bloodsucker/veil/ActivatePower(trigger_flags)
/datum/action/cooldown/bloodsucker/veil/get_power_explanation_extended()
. = list()
. += "Activating Veil of Many Faces will shroud you in smoke and forge you a new identity."
. += "Your name and appearance will be completely randomized, and turning the ability off again will undo it all."
. += "Clothes, gear, and Security/Medical HUD status is kept the same while this power is active."
/datum/action/cooldown/bloodsucker/veil/ActivatePower(atom/target)
. = ..()
cast_effect() // POOF
// if(blahblahblah)
// Disguise_Outfit()
veil_user()
owner.balloon_alert(owner, "veil turned on.")
return TRUE
/* // Meant to disguise your character's clothing into fake ones.
/datum/action/cooldown/bloodsucker/veil/proc/Disguise_Outfit()
@@ -101,9 +106,9 @@
identity[VISIBLE_NAME_FACE] = disguise_name
user.SetSpecialVoice(disguise_name)
/datum/action/cooldown/bloodsucker/veil/DeactivatePower()
/datum/action/cooldown/bloodsucker/veil/DeactivatePower(deactivate_flags)
. = ..()
if(!ishuman(owner))
if(!. || !ishuman(owner))
return
var/mob/living/carbon/human/user = owner
// Revert Identity
@@ -1,14 +1,19 @@
/datum/antagonist/bloodsucker/proc/claim_coffin(obj/structure/closet/crate/claimed, area/current_area)
/datum/antagonist/bloodsucker/proc/can_claim_coffin(obj/structure/closet/crate/claimed, area/current_area)
if(coffin)
return FALSE
// ALREADY CLAIMED
if(claimed.resident)
if(claimed.resident == owner.current)
to_chat(owner, "This is your [src].")
else
to_chat(owner, "This [src] has already been claimed by another.")
if(claimed.resident != owner.current)
claimed.balloon_alert(owner.current, "already claimed by another!")
return FALSE
if(!(GLOB.the_station_areas.Find(current_area.type)))
claimed.balloon_alert(owner.current, "not part of station!")
return
return TRUE
/datum/antagonist/bloodsucker/proc/claim_coffin(obj/structure/closet/crate/claimed, area/current_area)
if(!can_claim_coffin(claimed, current_area))
return FALSE
// This is my Lair
coffin = claimed
bloodsucker_lair_area = current_area
@@ -128,7 +133,7 @@
/// NOTE: This can be any coffin that you are resting AND inside of.
/obj/structure/closet/crate/coffin/proc/claim_coffin(mob/living/claimant, area/current_area)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = claimant.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(claimant)
// Successfully claimed?
if(bloodsuckerdatum.claim_coffin(src, current_area))
resident = claimant
@@ -142,48 +147,48 @@
STOP_PROCESSING(SSprocessing, src)
return ..()
/obj/structure/closet/crate/coffin/process(mob/living/user)
/obj/structure/closet/crate/coffin/process()
. = ..()
if(!.)
return FALSE
if(user in src)
var/list/turf/area_turfs = get_area_turfs(get_area(src))
// Create Dirt etc.
var/turf/T_Dirty = pick(area_turfs)
if(T_Dirty && !T_Dirty.density)
// Default: Dirt
// STEP ONE: COBWEBS
// CHECK: Wall to North?
var/turf/check_N = get_step(T_Dirty, NORTH)
if(istype(check_N, /turf/closed/wall))
// CHECK: Wall to West?
var/turf/check_W = get_step(T_Dirty, WEST)
if(istype(check_W, /turf/closed/wall))
new /obj/effect/decal/cleanable/cobweb(T_Dirty)
// CHECK: Wall to East?
var/turf/check_E = get_step(T_Dirty, EAST)
if(istype(check_E, /turf/closed/wall))
new /obj/effect/decal/cleanable/cobweb/cobweb2(T_Dirty)
new /obj/effect/decal/cleanable/dirt(T_Dirty)
var/list/turf/area_turfs = get_area_turfs(get_area(src))
// Create Dirt etc.
var/turf/T_Dirty = pick(area_turfs)
if(T_Dirty && !T_Dirty.density)
// Default: Dirt
// STEP ONE: COBWEBS
// CHECK: Wall to North?
var/turf/check_N = get_step(T_Dirty, NORTH)
if(istype(check_N, /turf/closed/wall))
// CHECK: Wall to West?
var/turf/check_W = get_step(T_Dirty, WEST)
if(istype(check_W, /turf/closed/wall))
new /obj/effect/decal/cleanable/cobweb(T_Dirty)
// CHECK: Wall to East?
var/turf/check_E = get_step(T_Dirty, EAST)
if(istype(check_E, /turf/closed/wall))
new /obj/effect/decal/cleanable/cobweb/cobweb2(T_Dirty)
new /obj/effect/decal/cleanable/dirt(T_Dirty)
/obj/structure/closet/crate/proc/unclaim_coffin(manual = FALSE)
/obj/structure/closet/crate/proc/unclaim_coffin(manual = FALSE, silent = FALSE)
// Unanchor it (If it hasn't been broken, anyway)
anchored = FALSE
if(!resident || !resident.mind)
return
un_enlarge(resident)
// Unclaiming
var/datum/antagonist/bloodsucker/bloodsuckerdatum = resident.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(resident)
if(bloodsuckerdatum && bloodsuckerdatum.coffin == src)
bloodsuckerdatum.coffin = null
bloodsuckerdatum.bloodsucker_lair_area = null
for(var/obj/structure/bloodsucker/bloodsucker_structure in get_area(src))
if(bloodsucker_structure.owner == resident)
bloodsucker_structure.unbolt()
if(manual)
to_chat(resident, span_cult_italic("You have unclaimed your coffin! This also unclaims all your other Bloodsucker structures!"))
else
to_chat(resident, span_cult_italic("You sense that the link with your coffin and your sacred lair has been broken! You will need to seek another."))
if(!silent)
if(manual)
to_chat(resident, span_cult_italic("You have unclaimed your coffin! This also unclaims all your other Bloodsucker structures!"))
else
to_chat(resident, span_cult_italic("You sense that the link with your coffin and your sacred lair has been broken! You will need to seek another."))
// Remove resident. Because this object isnt removed from the game immediately (GC?) we need to give them a way to see they don't have a home anymore.
resident = null
@@ -213,32 +218,24 @@
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(bloodsuckerdatum && (user in src))
if(prompt_coffin_claim(bloodsuckerdatum))
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 if(!bloodsuckerdatum.frenzied)
if(bloodsuckerdatum.GetUnspentRank() < 1)
bloodsuckerdatum.blood_level_gain()
// 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)
bloodsuckerdatum.check_begin_torpor(TORPOR_SKIP_CHECK_DAMAGE)
return TRUE
// /obj/structure/closet/crate/coffin/proc/is_claimable_coffin(datum/antagonist/bloodsucker/dracula, area/current_area)
// if(!dracula)
// return FALSE
// if(resident == dracula.owner.current)
// return TRUE
// if(!dracula.can_claim_coffin(src, current_area))
// return FALSE
// if(!dracula.coffin && resident)
// return FALSE
// return TRUE
/obj/structure/closet/crate/coffin/proc/prompt_coffin_claim(datum/antagonist/bloodsucker/dracula)
if(!dracula)
return FALSE
if(resident == dracula.owner.current)
return TRUE
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)
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?
@@ -327,12 +324,13 @@
to_chat(user, span_notice("You flip a secret latch and lock yourself inside [src]."))
else
to_chat(user, span_notice("You flip a secret latch and unlock [src]."))
return
return TRUE
// Broken? Let's fix it.
to_chat(resident, span_notice("The secret latch that would lock [src] from the inside is broken. You set it back into place..."))
if(!do_after(resident, 5 SECONDS, src))
to_chat(resident, span_notice("You fail to fix [src]'s mechanism."))
return
return FALSE
to_chat(resident, span_notice("You fix the mechanism and lock it."))
broken = FALSE
locked = TRUE
return TRUE
@@ -46,7 +46,7 @@
/obj/structure/bloodsucker/attack_hand(mob/user, list/modifiers)
// . = ..() // Don't call parent, else they will handle unbuckling.
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user)
/// Claiming the Rack instead of using it?
if(istype(bloodsuckerdatum) && !owner)
if(!bloodsuckerdatum.bloodsucker_lair_area)
@@ -133,7 +133,7 @@
/obj/structure/bloodsucker/vassalrack/examine(mob/user)
. = ..()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user)
if(bloodsuckerdatum)
. += span_cult("You can support a total of [convert_integer_to_words(bloodsuckerdatum.max_vassals())] [bloodsuckerdatum.max_vassals() == 1 ? "vassal" : "vassals"], \
with [convert_integer_to_words(bloodsuckerdatum.free_vassal_slots())] [bloodsuckerdatum.free_vassal_slots() == 1 ? "slot" : "slots"] remaining.")
@@ -246,7 +246,7 @@
if(!has_buckled_mobs())
return FALSE
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user)
var/mob/living/carbon/buckled_carbons = pick(buckled_mobs)
// If I'm not a Bloodsucker, try to unbuckle them.
if(!istype(bloodsuckerdatum))
@@ -259,7 +259,7 @@
var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(buckled_carbons)
// Are they our Vassal?
if(vassaldatum && (vassaldatum in bloodsuckerdatum.vassals))
SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_INTERACT_WITH_VASSAL, vassaldatum)
SEND_SIGNAL(bloodsuckerdatum, COMSIG_BLOODSUCKER_INTERACT_WITH_VASSAL, vassaldatum)
return
if(bloodsuckerdatum.free_vassal_slots() < 1)
to_chat(user, span_warning("You can't vassalize more people until you level up more! You are currently at [bloodsuckerdatum.free_vassal_slots()] active / [bloodsuckerdatum.max_vassals()] max vassals."))
@@ -278,9 +278,9 @@
* * Vassalize target
*/
/obj/structure/bloodsucker/vassalrack/proc/torture_victim(mob/living/user, mob/living/target)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user)
if(IS_VASSAL(target))
var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal)
var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(target)
if(!vassaldatum.master.broke_masquerade)
balloon_alert(user, "someone else's vassal!")
return FALSE
@@ -332,7 +332,7 @@
bloodsuckerdatum.AdjustBloodVolume(-TORTURE_CONVERSION_COST)
if(bloodsuckerdatum.make_vassal(target))
remove_loyalties(target)
SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, user, target)
SEND_SIGNAL(bloodsuckerdatum, COMSIG_BLOODSUCKER_MADE_VASSAL, user, target)
/obj/structure/bloodsucker/vassalrack/proc/do_torture(mob/living/user, mob/living/carbon/target, mult = 1)
// Fifteen seconds if you aren't using anything. Shorter with weapons and such.
@@ -595,7 +595,7 @@
var/mob/living/carbon/human/user = source
var/rendered = span_cult_large("<b>[user.real_name]:</b> [capitalize(message)]")
user.log_talk(message, LOG_SAY, tag = ROLE_BLOODSUCKER)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user)
for(var/datum/antagonist/vassal/receiver as anything in bloodsuckerdatum.vassals)
if(!receiver.owner.current)
continue
@@ -86,12 +86,12 @@
if(!wood_stack && replace)
user.put_in_hands(new_item)
// TODO move this into bloodsuckerdatum
/// Do I have a stake in my heart?
/mob/living/proc/am_staked()
var/list/stakes = get_stakes()
if(!length(stakes))
return FALSE
return TRUE
var/obj/item/bodypart/chosen_bodypart = get_bodypart(BODY_ZONE_CHEST)
var/obj/item/stake/stake = locate() in chosen_bodypart.embedded_objects
return stake
/mob/living/proc/get_stakes()
var/obj/item/bodypart/chosen_bodypart = get_bodypart(BODY_ZONE_CHEST)
@@ -173,18 +173,17 @@
playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1)
if(tryEmbed(target.get_bodypart(BODY_ZONE_CHEST), TRUE, TRUE)) //and if it embeds successfully in their chest, cause a lot of pain
target.apply_damage(max(10, force * 1.2), BRUTE, BODY_ZONE_CHEST, wound_bonus = 0, sharpness = TRUE)
if(QDELETED(src)) // in case trying to embed it caused its deletion (say, if it's DROPDEL)
/obj/item/stake/tryEmbed(atom/target, forced)
. = ..()
if(!(. & COMPONENT_EMBED_SUCCESS) || !isbodypart(target))
return FALSE
var/obj/item/bodypart/bodypart = target
if(bodypart.body_zone != BODY_ZONE_CHEST)
return
if(!target.mind)
return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = target.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(bloodsuckerdatum)
// If silver stake and DEAD or TORPOR... Kill the Bloodsucker!
if(target.StakeCanKillMe())
bloodsuckerdatum.FinalDeath()
else
to_chat(target, span_userdanger("You have been staked! Your powers are useless, your death forever, while it remains in place."))
target.balloon_alert(target, "you have been staked!")
SEND_SIGNAL(bodypart, COMSIG_BODYPART_STAKED, forced)
if(bodypart.owner)
SEND_SIGNAL(bodypart.owner, COMSIG_MOB_STAKED, forced)
///Can this target be staked? If someone stands up before this is complete, it fails. Best used on someone stationary.
/mob/living/proc/can_be_staked()
@@ -6,6 +6,10 @@
/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."
background_icon_state = "tremere_power_off"
active_background_icon_state = "tremere_power_on"
base_background_icon_state = "tremere_power_off"
background_icon = 'modular_zubbers/icons/mob/actions/tremere_bloodsucker.dmi'
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
@@ -13,6 +17,10 @@
convert_damage = TRUE
cooldown_time = 1 MINUTES
possible_shapes = list(/mob/living/basic/bat/bloodsucker)
power_explanation = "This ability turns you into a small bat, which can fly and heals from doing meele damage. \n\
However, you don't do much meele damage, and are slightly weak to fire damage.\n\
Using the ability also spawns some normal bats around where you cast it, causing confusion.\n\
Keep in mind if you die in this form, your real body will also die, and any damage received is transfered as well."
var/bats_to_spawn = 3
/datum/action/cooldown/spell/shapeshift/bat/cast(mob/living/cast_on)
@@ -148,11 +148,11 @@
to_chat(owner, span_userdanger("You are now the mortal servant of [master.owner.current], a Bloodsucker!"))
to_chat(owner, span_boldannounce("The power of [master.owner.current.p_their()] immortal blood compels you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.\n\
You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed.")) // if only there was a /p_theirs() proc...
You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed."))
owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
antag_memory += "You, becoming the mortal servant of <b>[master.owner.current]</b>, a bloodsucking vampire!<br>"
/// Message told to your Master.
to_chat(master.owner, span_userdanger("[owner.current] has become addicted to your immortal blood. [owner.current.p_they(TRUE)] [owner.current.p_are()] now your undying servant!"))
to_chat(master.owner, span_userdanger("[owner.current] has become addicted to your immortal blood. [capitalize(owner.current.p_they(TRUE))] [owner.current.p_are()] now your mortal servant!"))
master.owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
/datum/antagonist/vassal/farewell()
@@ -186,7 +186,7 @@
if(!choice)
return
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)
var/datum/antagonist/bloodsucker/vampire = IS_BLOODSUCKER(choice.current)
master = vampire
new_owner.add_antag_datum(src)
to_chat(choice, span_notice("Through divine intervention, you've gained a new vassal!"))
@@ -72,6 +72,6 @@
vassaldatum.silent = FALSE
//send alerts of completion
to_chat(master, span_danger("You have turned [vassal_owner.current] into your [vassaldatum.name]!"))
to_chat(vassal_owner, span_notice("As blood drips over your body, you feel closer to your Master... You are now the Favorite Vassal!"))
to_chat(master, span_danger("You have turned [vassal_owner.current] into your [vassaldatum.name]! They will no longer be deconverted upon Mindshielding!"))
to_chat(vassal_owner, span_notice("As Blood drips over your body, you feel closer to your Master... You are now the [vassaldatum.name]!"))
vassal_owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 75, FALSE, pressure_affected = FALSE)
@@ -32,7 +32,7 @@
/datum/antagonist/ex_vassal/proc/on_examine(datum/source, mob/examiner, examine_text)
SIGNAL_HANDLER
var/datum/antagonist/vassal/revenge/vassaldatum = examiner.mind.has_antag_datum(/datum/antagonist/vassal/revenge)
var/datum/antagonist/vassal/revenge/vassaldatum = IS_REVENGE_VASSAL(examiner)
if(vassaldatum && !revenge_vassal)
examine_text += span_notice("[owner.current] is an ex-vassal!")
@@ -17,10 +17,10 @@
/datum/antagonist/vassal/favorite/on_gain()
. = ..()
SEND_SIGNAL(master, BLOODSUCKER_MAKE_FAVORITE, src)
SEND_SIGNAL(master, COMSIG_BLOODSUCKER_MAKE_FAVORITE, src)
/datum/antagonist/vassal/favorite/on_removal()
SEND_SIGNAL(master, BLOODSUCKER_LOOSE_FAVORITE, src)
SEND_SIGNAL(master, COMSIG_BLOODSUCKER_LOOSE_FAVORITE, src)
remove_powers(bloodsucker_powers)
. = ..()
@@ -35,10 +35,10 @@
/datum/antagonist/vassal/revenge/on_gain()
. = ..()
RegisterSignal(master, BLOODSUCKER_FINAL_DEATH, PROC_REF(on_master_death))
RegisterSignal(master, COMSIG_BLOODSUCKER_FINAL_DEATH, PROC_REF(on_master_death))
/datum/antagonist/vassal/revenge/on_removal()
UnregisterSignal(master, BLOODSUCKER_FINAL_DEATH)
UnregisterSignal(master, COMSIG_BLOODSUCKER_FINAL_DEATH)
return ..()
/datum/antagonist/vassal/revenge/proc/on_master_death(datum/antagonist/bloodsucker/bloodsuckerdatum, mob/living/carbon/master)
@@ -2,3 +2,7 @@
/datum/movespeed_modifier/frenzy_speedup
blacklisted_movetypes = (FLYING|FLOATING)
multiplicative_slowdown = -0.4
/datum/movespeed_modifier/mesmerize_slowdown
blacklisted_movetypes = (FLYING|FLOATING)
multiplicative_slowdown = 0.5
@@ -40,7 +40,7 @@
to_chat(owner, span_userdanger("<FONT size = 3>Blood! You need Blood, now! You enter a total Frenzy! You will DIE if you do not get BLOOD."))
to_chat(owner, span_announce("* Bloodsucker Tip: While in Frenzy, you quickly accrue burn damage, instantly Aggresively grab, have stun resistance, cannot speak, hear, or use any powers outside of Feed and Trespass (If you have it)."))
owner.balloon_alert(owner, "you enter a frenzy! Drink blood, or you will die!")
SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_ENTERS_FRENZY)
SEND_SIGNAL(bloodsuckerdatum, COMSIG_BLOODSUCKER_ENTERS_FRENZY)
// Give the other Frenzy effects
owner.add_traits(trait_list, FRENZY_TRAIT)
@@ -51,7 +51,7 @@
owner.add_client_colour(/datum/client_colour/manual_heart_blood)
var/obj/cuffs = user.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
var/obj/legcuffs = user.get_item_by_slot(ITEM_SLOT_LEGCUFFED)
if(user.handcuffed || user.legcuffed)
if((user.handcuffed && cuffs) || (user.legcuffed && legcuffs))
user.clear_cuffs(cuffs, TRUE)
user.clear_cuffs(legcuffs, TRUE)
bloodsuckerdatum.frenzied = TRUE
@@ -66,7 +66,7 @@
owner.remove_movespeed_modifier(/datum/movespeed_modifier/frenzy_speedup)
owner.remove_client_colour(/datum/client_colour/manual_heart_blood)
SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_EXITS_FRENZY)
SEND_SIGNAL(bloodsuckerdatum, COMSIG_BLOODSUCKER_EXITS_FRENZY)
bloodsuckerdatum.frenzied = FALSE
return ..()
@@ -111,7 +111,7 @@
";This is your fault.",
",LWhy do we always infight, what's wrong with a little teamwork, it gets us further.",
"#What's a hacked autodrobe but a machine forced to show itself to you. Is it moral?",
"#This is supposed to be a station for moths and by moths. But if so why is there non moths on the station?",
"#This is supposed to be a station for moths and by moths. But if so why are there non-moths on the station?",
"#If you wish to defeat me. Train for another 10,000 years.",
"#If i knew you was coming i'd have baked a cake! Wait, who was i talking to?",
"#We'll meet again. I don't know where and i don't know when but i know we'll meet again. Hopefully on a not very sunny day.",
+3
View File
@@ -8585,6 +8585,7 @@
#include "modular_zubbers\code\__DEFINES\guardian_defines.dm"
#include "modular_zubbers\code\__DEFINES\moonstation_defines.dm"
#include "modular_zubbers\code\__DEFINES\send2relay.dm"
#include "modular_zubbers\code\__DEFINES\span.dm"
#include "modular_zubbers\code\__DEFINES\traits.dm"
#include "modular_zubbers\code\_globalvars\lists\_maintenance_loot.dm"
#include "modular_zubbers\code\_globalvars\lists\maintenance_loot_common.dm"
@@ -8595,6 +8596,7 @@
#include "modular_zubbers\code\_globalvars\lists\maintenance_loot_uncommon.dm"
#include "modular_zubbers\code\_globalvars\lists\quirks.dm"
#include "modular_zubbers\code\_globalvars\lists\~maintenance_loot.dm"
#include "modular_zubbers\code\_onclick\hud\screen_objects\hud_timer.dm"
#include "modular_zubbers\code\controllers\configuration\entries\nsfw.dm"
#include "modular_zubbers\code\controllers\subsystem\air.dm"
#include "modular_zubbers\code\controllers\subsystem\mapping.dm"
@@ -8635,6 +8637,7 @@
#include "modular_zubbers\code\datums\diseases\advance\presets.dm"
#include "modular_zubbers\code\datums\greyscale\config_types\greyscale_configs\greyscale_clothes.dm"
#include "modular_zubbers\code\datums\mapgen\Cavegens\moonstation.dm"
#include "modular_zubbers\code\datums\materials\basemats.dm"
#include "modular_zubbers\code\datums\mood_events\bloodsucker_events.dm"
#include "modular_zubbers\code\datums\mood_events\dominant_mood.dm"
#include "modular_zubbers\code\datums\mood_events\food_events.dm"
@@ -21,7 +21,7 @@ export type ClanInfo = {
export type PowerInfo = {
power_name: string;
power_explanation: string;
power_explanation: string[];
power_icon: string;
};
@@ -55,7 +55,7 @@ const ObjectivePrintout = (props: any) => {
export const AntagInfoBloodsucker = (props: any) => {
const [tab, setTab] = useState(1);
return (
<Window width={620} height={580} theme="spookyconsole">
<Window width={620} height={700} theme="spookyconsole">
<Window.Content>
<Tabs>
<Tabs.Tab
@@ -100,7 +100,7 @@ const BloodsuckerIntro = () => {
</Stack.Item>
<Stack.Item>
<Section fill title="Strengths and Weaknesses">
<Stack vertical>
<Stack vertical height="7rem">
<Stack.Item>
<span>
You regenerate your health slowly, you&#39;re weak to fire, and
@@ -118,6 +118,11 @@ const BloodsuckerIntro = () => {
Avoid using your Feed ability while near others, or else you
will risk <i>breaking the Masquerade</i>!
</span>
<span>
Loosing your heart will render your powers useless, but going
into a coffin with a heart inside will allow you to regenerate
it.
</span>
</Stack.Item>
</Stack>
</Section>
@@ -202,7 +207,10 @@ const BloodsuckerClan = (props: any) => {
<Stack.Item fontSize="20px" textAlign="center">
You are part of the {ClanInfo.clan_name}
</Stack.Item>
<Stack.Item fontSize="16px">
<Stack.Item
fontSize="16px"
style={{ flexBasis: '60% !important' }}
>
{ClanInfo.clan_description}
</Stack.Item>
</>
@@ -210,8 +218,8 @@ const BloodsuckerClan = (props: any) => {
</Stack.Item>
</Stack>
</Section>
<PowerDetails powers={powers} />
</Stack.Item>
<PowerDetails powers={powers} />
</Stack>
);
};
+14 -2
View File
@@ -61,8 +61,20 @@ export const PowerDetails = (props: PowerDetailsProps) => {
<Divider vertical />
</Stack.Item>
<Stack.Divider />
<Stack.Item grow fontSize="16px">
{selectedPower && selectedPower.power_explanation}
<Stack.Item grow fontSize="16px" width="40%">
{selectedPower?.power_explanation?.length &&
selectedPower.power_explanation.map((line, index) => {
if (index === 0) {
return line;
} else {
return (
<>
<br />
{line}
</>
);
}
})}
</Stack.Item>
</Stack>
</Section>