mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 16:18:01 +01:00
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:
co-authored by
Waterpig
The Sharkening
parent
a99c2be5cf
commit
abea882914
@@ -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"
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
-19
@@ -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.")
|
||||
|
||||
|
||||
+73
-47
@@ -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
|
||||
|
||||
+146
-45
@@ -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)
|
||||
|
||||
+4
-8
@@ -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
|
||||
|
||||
+209
-108
@@ -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)
|
||||
|
||||
+1
-1
@@ -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!")
|
||||
|
||||
|
||||
+2
-2
@@ -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)
|
||||
. = ..()
|
||||
|
||||
|
||||
+2
-2
@@ -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.",
|
||||
|
||||
@@ -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'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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user