diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index c4bd9fb7c9f..bf5b5c9e9bd 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -137,6 +137,7 @@ #define ROLE_VAMPIRICACCIDENT "Bloodsucker (Midround)" #define ROLE_BLOODSUCKERBREAKOUT "Bloodsucker (Latejoin)" #define ROLE_MONSTERHUNTER "Monster Hunter" +#define ROLE_VASSAL "Vassal" //BUBBER EDIT END /// This defines the antagonists you can operate with in the settings. diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index e5b3756cfd4..a57f29b3150 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -433,6 +433,8 @@ ROLE_WIZARD, ROLE_BORER, //SKYRAT EDIT ROLE_ASSAULT_OPERATIVE, //SKYRAT EDIT + ROLE_BLOODSUCKER,// BUBBER EDIT, + ROLE_VASSAL, // BUBBER EDIT ), "Skyrat Ban Options" = list( BAN_PACIFICATION, diff --git a/code/modules/mob/living/brain/brain_say.dm b/code/modules/mob/living/brain/brain_say.dm index 79e8e1d3079..c9109795271 100644 --- a/code/modules/mob/living/brain/brain_say.dm +++ b/code/modules/mob/living/brain/brain_say.dm @@ -1,4 +1,16 @@ /mob/living/brain/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterpoof = null, message_range = 7, datum/saymode/saymode = null) + // BUBBER CHANGE: funny talking head + if(stat == DEAD) // lets you talk in deadchat + return ..() + var/datum/antagonist/bloodsucker/spooky_vampire_head = mind?.has_antag_datum(/datum/antagonist/bloodsucker) + if(!container && spooky_vampire_head) + var/obj/head = spooky_vampire_head.is_head(src) + if(!head) + return + var/animation_time = max(2, length_char(message) * 0.5) + head.Shake(duration = animation_time) + ..() + // BUBBER CHANGE END if(!(container && istype(container, /obj/item/mmi))) return //No MMI, can't speak, bucko./N else diff --git a/modular_zubbers/code/__DEFINES/bloodsucker.dm b/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm similarity index 87% rename from modular_zubbers/code/__DEFINES/bloodsucker.dm rename to modular_zubbers/code/__DEFINES/bloodsucker_defines.dm index 62f465116ff..822c903c8ca 100644 --- a/modular_zubbers/code/__DEFINES/bloodsucker.dm +++ b/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm @@ -4,22 +4,29 @@ /// You have special interactions with Bloodsuckers #define TRAIT_BLOODSUCKER_HUNTER "bloodsucker_hunter" +// how much to multiply the coffin size by mob_size +#define COFFIN_ENLARGE_MULT 0.5 /** * Blood-level defines */ /// Determines Bloodsucker regeneration rate #define BS_BLOOD_VOLUME_MAX_REGEN 700 /// Cost to torture someone halfway, in blood. Called twice for full cost -#define TORTURE_BLOOD_HALF_COST 8 +#define TORTURE_BLOOD_HALF_COST 4 /// Cost to convert someone after successful torture, in blood -#define TORTURE_CONVERSION_COST 50 +#define TORTURE_CONVERSION_COST 10 +/// How much blood it costs you to make a vassal into a special vassal +#define SPECIAL_VASSAL_COST 150 /// Once blood is this low, will enter Frenzy #define FRENZY_THRESHOLD_ENTER 25 /// Once blood is this high, will exit Frenzy #define FRENZY_THRESHOLD_EXIT 250 +/// a bloodsucker can't loose more humanity than this, and looses the masquerade ability when reaching it +#define HUMANITY_LOST_MAXIMUM 50 + /// Level up blood cost define, max_blood * this = blood cost -#define BLOODSUCKER_LEVELUP_PERCENTAGE 0.2 +#define BLOODSUCKER_LEVELUP_PERCENTAGE 0.15 ///The level when at a bloodsucker becomes snobby about who they drink from and gain their non-fledling reputation #define BLOODSUCKER_HIGH_LEVEL 4 @@ -73,7 +80,7 @@ /// This Power can't be used in Frenzy. #define BP_CANT_USE_IN_FRENZY (1<<2) /// This Power can't be used with a stake in you -#define BP_CANT_USE_WHILE_STAKED (1<<3) +#define BP_CAN_USE_WHILE_STAKED (1<<3) /// This Power can't be used while incapacitated #define BP_CANT_USE_WHILE_INCAPACITATED (1<<4) /// This Power can't be used while unconscious @@ -97,6 +104,13 @@ /// This Power doesn't cost bloot to run while unconscious #define BP_AM_COSTLESS_UNCONSCIOUS (1<<3) +/** + * Torpor check bitflags + */ +#define TORPOR_SKIP_CHECK_ALL (1<<0) +#define TORPOR_SKIP_CHECK_FRENZY (1<<1) +#define TORPOR_SKIP_CHECK_DAMAGE (1<<2) + /** * Bloodsucker Signals */ @@ -106,6 +120,8 @@ #define BLOODSUCKER_INTERACT_WITH_VASSAL "bloodsucker_interact_with_vassal" ///Called when a Bloodsucker makes a Vassal into their Favorite Vassal: (datum/vassal_datum, mob/master) #define BLOODSUCKER_MAKE_FAVORITE "bloodsucker_make_favorite" +// called when a bloodsucker looses their favorite vassal, cleaning up whatever they gained +#define BLOODSUCKER_LOOSE_FAVORITE "bloodsucker_loose_favorite" ///Called when a new Vassal is successfully made: (datum/bloodsucker_datum) #define BLOODSUCKER_MADE_VASSAL "bloodsucker_made_vassal" ///Called when a Bloodsucker exits Torpor. @@ -120,6 +136,8 @@ #define BLOODSUCKER_ENTERS_FRENZY "bloodsucker_enters_frenzy" ///Called when a Bloodsucker exits Frenzy #define BLOODSUCKER_EXITS_FRENZY "bloodsucker_exits_frenzy" +// Called when anyone enters the coffin +#define COMSIG_ENTER_COFFIN "comsig_enter_coffin" /** * Sol signals & Defines @@ -164,6 +182,12 @@ */ /// Source trait for Bloodsuckers-related traits #define BLOODSUCKER_TRAIT "bloodsucker_trait" + +#define VASSAL_TRAIT "vassal_trait" + +/// Source trait for dominate related traits +#define DOMINATE_TRAIT "dominate_trait" + /// Source trait for Monster Hunter-related traits #define HUNTER_TRAIT "monsterhunter_trait" /// Source trait while Feeding @@ -186,6 +210,7 @@ #define IS_MONSTERHUNTER(mob) (FALSE) #define BLOODSUCKER_SIGHT_COLOR_CUTOFF list(25, 8, 5) +#define POLL_IGNORE_VASSAL "vassal" // Why waste memory on a dynamic global list if we can just bake it in on compile time? #define BLOODSUCKER_PROTECTED_ROLES list( \ @@ -214,4 +239,5 @@ /datum/species/skeleton, \ /datum/species/zombie, \ /datum/species/mutant, \ + /datum/species/dullahan \ ) \ diff --git a/modular_zubbers/code/__DEFINES/traits.dm b/modular_zubbers/code/__DEFINES/traits.dm index 67a63435c41..0de6ca840a2 100644 --- a/modular_zubbers/code/__DEFINES/traits.dm +++ b/modular_zubbers/code/__DEFINES/traits.dm @@ -1 +1,3 @@ #define TRAIT_MONOPHOBIA "monophobia" + +#define TRAIT_COFFIN_ENLARGED "coffin_enlarged" diff --git a/modular_zubbers/code/datums/action.dm b/modular_zubbers/code/datums/action.dm new file mode 100644 index 00000000000..9c0995fe94e --- /dev/null +++ b/modular_zubbers/code/datums/action.dm @@ -0,0 +1,3 @@ +/datum/action + /// Extended description of the action, usually shown in an antag UI. + var/power_explanation = "" diff --git a/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index a304b0f81be..b6423d25a96 100644 --- a/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -38,6 +38,8 @@ /datum/dynamic_ruleset/roundstart/bloodsucker/execute() for(var/datum/mind/candidate_minds as anything in assigned) if(!candidate_minds.make_bloodsucker()) + candidate_minds.special_role = null + candidate_minds.restricted_roles = null message_admins("[ADMIN_LOOKUPFLW(candidate_minds)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.") assigned -= candidate_minds continue diff --git a/modular_zubbers/code/modules/antagonists/_common/antag_datum.dm b/modular_zubbers/code/modules/antagonists/_common/antag_datum.dm new file mode 100644 index 00000000000..14f19dc26e8 --- /dev/null +++ b/modular_zubbers/code/modules/antagonists/_common/antag_datum.dm @@ -0,0 +1,18 @@ + +/datum/antagonist + var/antag_panel_title = "Antagonist Panel" + var/antag_panel_description = "This is the antagonist panel. It contains all the abilities you have access to as an antagonist. Use them wisely." + +/datum/antagonist/proc/ability_ui_data(actions = list()) + var/list/data = list() + data["title"] = "[antag_panel_title]\n[antag_panel_data()]" + data["description"] = antag_panel_description + for(var/datum/action/cooldown/power as anything in actions) + var/list/power_data = list() + + power_data["power_name"] = power.name + power_data["power_explanation"] = power?.power_explanation + power_data["power_icon"] = power.button_icon_state + + data["powers"] += list(power_data) + return data diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm index dba59f4753f..e79f851870d 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm @@ -24,6 +24,8 @@ COOLDOWN_DECLARE(bloodsucker_spam_sol_burn) ///Timer between alerts for Healing messages COOLDOWN_DECLARE(bloodsucker_spam_healing) + /// Timer between exiting torpor + COOLDOWN_DECLARE(bloodsucker_spam_torpor) ///Used for assigning your name var/bloodsucker_name @@ -38,10 +40,10 @@ var/broke_masquerade = FALSE ///How many Masquerade Infractions do we have? var/masquerade_infractions = 0 - ///Blood required to enter Frenzy - var/frenzy_threshold = FRENZY_THRESHOLD_ENTER ///If we are currently in a Frenzy var/frenzied = FALSE + /// sired by a ventrue + var/ventrue_sired ///ALL Powers currently owned var/list/datum/action/cooldown/bloodsucker/powers = list() @@ -91,12 +93,18 @@ TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_VIRUSIMMUNE, - TRAIT_TOXIMMUNE, TRAIT_HARDLY_WOUNDED, TRAIT_NO_MIRROR_REFLECTION, - TRAIT_DRINKS_BLOOD + TRAIT_DRINKS_BLOOD, + TRAIT_TOXIMMUNE, + // Fun fact, toxins can still be applied to you if you loose your liver even with TOXIMMUNE + TRAIT_STABLELIVER ) - var/biotype = MOB_VAMPIRIC + var/static/biotype = MOB_VAMPIRIC + /// Weakref to the owner mob's heart, without bloodsucker_life stops and they die. Handled via signals due to the fact that + /// Bloodsuckers don't take damage from lacking a heart due to TRAIT_NOBREATH + /// Saved here so we can keep a track of it and remove signals properly + var/datum/weakref/heart /** * Apply innate effects is everything given to the mob @@ -110,6 +118,9 @@ RegisterSignal(current_mob, COMSIG_LIVING_LIFE, PROC_REF(LifeTick)) RegisterSignal(current_mob, COMSIG_LIVING_DEATH, PROC_REF(on_death)) RegisterSignal(current_mob, COMSIG_SPECIES_GAIN, PROC_REF(on_species_gain)) + RegisterSignal(current_mob, COMSIG_QDELETING, PROC_REF(on_removal)) + RegisterSignal(current_mob, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_organ_gain)) + talking_head() handle_clown_mutation(current_mob, mob_override ? null : "As a vampiric clown, you are no longer a danger to yourself. Your clownish nature has been subdued by your thirst for blood.") add_team_hud(current_mob) @@ -117,7 +128,9 @@ on_hud_created() else RegisterSignal(current_mob, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created)) - current_mob?.dna?.species.on_bloodsucker_gain(current_mob) + if(ishuman(current_mob)) + current_mob?.dna?.species.on_bloodsucker_gain(current_mob) + add_signals_to_heart(current_mob) #ifdef BLOODSUCKER_TESTING var/turf/user_loc = get_turf(current_mob) new /obj/structure/closet/crate/coffin(user_loc) @@ -126,13 +139,14 @@ /** * Remove innate effects is everything given to the mob - * When a body is tranferred, this is called on the old mob. + * When a body is transferred, this is called on the old mob. * while on_removal is called ONCE per ANTAG, this is called ONCE per BODY. */ /datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override) . = ..() var/mob/living/carbon/current_mob = mob_override || owner.current - UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_ATOM_EXAMINE, COMSIG_LIVING_DEATH, COMSIG_SPECIES_GAIN)) + remove_signals_from_heart(current_mob) + UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_ATOM_EXAMINE, COMSIG_LIVING_DEATH, COMSIG_SPECIES_GAIN, COMSIG_QDELETING)) handle_clown_mutation(current_mob, removing = FALSE) if(current_mob.hud_used) @@ -160,17 +174,21 @@ bloodsucker_hud.show_hud(bloodsucker_hud.hud_version) UnregisterSignal(owner.current, COMSIG_MOB_HUD_CREATED) + update_hud() + update_blood_hud() /// Override some properties of incompatible species /datum/antagonist/bloodsucker/proc/on_species_gain(mob/living/carbon/human/target, datum/species/current_species, datum/species/old_species) SIGNAL_HANDLER if(!ishuman(owner.current)) return + add_signals_to_heart(target) var/mob/living/carbon/human/user = owner.current user?.dna?.species.on_bloodsucker_gain(target) /datum/antagonist/bloodsucker/get_admin_commands() . = ..() + .["Set blood level"] = CALLBACK(src, PROC_REF(admin_set_blood)) .["Give Level"] = CALLBACK(src, PROC_REF(RankUp), TRUE) if(bloodsucker_level_unspent >= 1) .["Remove Level"] = CALLBACK(src, PROC_REF(RankDown)) @@ -187,13 +205,14 @@ ///Called when you get the antag datum, called only ONCE per antagonist. /datum/antagonist/bloodsucker/on_gain() + if(!owner?.current) + return ..() RegisterSignal(SSsunlight, COMSIG_SOL_RANKUP_BLOODSUCKERS, PROC_REF(sol_rank_up)) RegisterSignal(SSsunlight, COMSIG_SOL_NEAR_START, PROC_REF(sol_near_start)) RegisterSignal(SSsunlight, COMSIG_SOL_END, PROC_REF(on_sol_end)) RegisterSignal(SSsunlight, COMSIG_SOL_RISE_TICK, PROC_REF(handle_sol)) RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(give_warning)) - - if(IS_VASSAL(owner.current)) // Vassals shouldnt be getting the same benefits as Bloodsuckers. + if(ventrue_sired) // sired bloodsuckers shouldnt be getting the same benefits as Bloodsuckers. bloodsucker_level_unspent = 0 show_in_roundend = FALSE else @@ -213,13 +232,16 @@ /// Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion. /datum/antagonist/bloodsucker/on_removal() + UnregisterSignal(SSsunlight, list(COMSIG_SOL_RANKUP_BLOODSUCKERS, COMSIG_SOL_NEAR_START, COMSIG_SOL_END, COMSIG_SOL_RISE_TICK, COMSIG_SOL_WARNING_GIVEN, COMSIG_QDELETING)) + free_all_vassals() + check_cancel_sunlight() //check if sunlight should end + if(!owner?.current) + return if(ishuman(owner.current)) var/mob/living/carbon/human/user = owner.current user?.dna?.species.regenerate_organs(user, null, TRUE) - UnregisterSignal(SSsunlight, list(COMSIG_SOL_RANKUP_BLOODSUCKERS, COMSIG_SOL_NEAR_START, COMSIG_SOL_END, COMSIG_SOL_RISE_TICK, COMSIG_SOL_WARNING_GIVEN)) clear_powers_and_stats() - check_cancel_sunlight() //check if sunlight should end - free_all_vassals() + ventrue_sired = null return ..() /datum/antagonist/bloodsucker/on_body_transfer(mob/living/old_body, mob/living/new_body) @@ -306,7 +328,7 @@ return finish_preview_icon(final_icon) /datum/antagonist/bloodsucker/ui_static_data(mob/user) - var/list/data = list() + var/list/data = ability_ui_data(powers) //we don't need to update this that much. data["in_clan"] = !!my_clan var/list/clan_data = list() @@ -317,15 +339,6 @@ data["clan"] += list(clan_data) - for(var/datum/action/cooldown/bloodsucker/power as anything in powers) - var/list/power_data = list() - - power_data["power_name"] = power.name - power_data["power_explanation"] = power.power_explanation - power_data["power_icon"] = power.button_icon_state - - data["powers"] += list(power_data) - return data + ..() /datum/antagonist/bloodsucker/ui_assets(mob/user) @@ -343,8 +356,9 @@ if(my_clan) return assign_clan_and_bane() + if(ui.closing) + return ui.send_full_update(force = TRUE) - return /datum/antagonist/bloodsucker/roundend_report() var/list/report = list() @@ -403,10 +417,10 @@ var/obj/item/bodypart/user_left_arm = user.get_bodypart(BODY_ZONE_L_ARM) var/obj/item/bodypart/user_right_arm = user.get_bodypart(BODY_ZONE_R_ARM) user.dna?.remove_all_mutations() - user_left_arm.unarmed_damage_low += 1 //lowest possible punch damage - 0 - user_left_arm.unarmed_damage_high += 1 //highest possible punch damage - 9 - user_right_arm.unarmed_damage_low += 1 //lowest possible punch damage - 0 - user_right_arm.unarmed_damage_high += 1 //highest possible punch damage - 9 + user_left_arm.unarmed_damage_low += 1 //lowest possible punch damage - 6 now + user_left_arm.unarmed_damage_high += 1 //highest possible punch damage - 11 + user_right_arm.unarmed_damage_low += 1 //lowest possible punch damage - 6 + user_right_arm.unarmed_damage_high += 1 //highest possible punch damage - 11 user.mob_biotypes |= biotype //Give Bloodsucker Traits owner.current.add_traits(bloodsucker_traits, BLOODSUCKER_TRAIT) @@ -417,7 +431,7 @@ if(HAS_TRAIT(owner.current, TRAIT_SKITTISH)) REMOVE_TRAIT(owner.current, TRAIT_SKITTISH, ROUNDSTART_TRAIT) // Tongue & Language - owner.current.grant_language(/datum/language/vampiric, LANGUAGE_SPOKEN, LANGUAGE_MIND) + owner.current.grant_language(/datum/language/vampiric, ALL, LANGUAGE_MIND) /// Clear Disabilities & Organs heal_vampire_organs() @@ -439,6 +453,8 @@ // Powers for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers) RemovePower(all_powers) + if(QDELETED(owner.current)) + return /// Stats if(ishuman(owner.current)) var/mob/living/carbon/human/user = owner.current @@ -451,6 +467,9 @@ if(right_arm) right_arm.unarmed_damage_low = initial(right_arm.unarmed_damage_low) right_arm.unarmed_damage_high = initial(right_arm.unarmed_damage_high) + var/obj/item/bodypart/chest/target_chest = owner.current.get_bodypart(BODY_ZONE_CHEST) + if(target_chest && target_chest.bodypart_flags & BODYPART_UNREMOVABLE) + target_chest.bodypart_flags &= ~BODYPART_UNREMOVABLE // Remove all bloodsucker traits owner.current.remove_traits(bloodsucker_traits, BLOODSUCKER_TRAIT) // Language @@ -497,7 +516,7 @@ conversion_objective.objective_name = "Optional Objective" objectives += conversion_objective if(2) // Heart Thief Objective - var/datum/objective/bloodsucker/heartthief/heartthief_objective = new + var/datum/objective/steal_n_of_type/hearts/heartthief_objective = new heartthief_objective.owner = owner heartthief_objective.objective_name = "Optional Objective" objectives += heartthief_objective diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/conversion.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/conversion.dm index cd63b58b6ac..dccc4f21700 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/conversion.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/conversion.dm @@ -25,7 +25,7 @@ * conversion_target - Person being vassalized */ /datum/antagonist/bloodsucker/proc/can_make_vassal(mob/living/conversion_target) - if(!iscarbon(conversion_target) || conversion_target.stat > UNCONSCIOUS) + if(!iscarbon(conversion_target) || (conversion_target.stat < CONSCIOUS)) return FALSE // No Mind! if(!conversion_target.mind) @@ -53,7 +53,6 @@ /datum/antagonist/bloodsucker/proc/make_vassal(mob/living/conversion_target) if(!can_make_vassal(conversion_target)) return FALSE - //Check if they used to be a Vassal and was stolen. var/datum/antagonist/vassal/old_vassal = conversion_target.mind.has_antag_datum(/datum/antagonist/vassal) if(old_vassal) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm index 812c2c1deaf..48be42016ee 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm @@ -37,45 +37,49 @@ /// Update Blood Counter + Rank Counter /datum/antagonist/bloodsucker/proc/update_hud() - var/valuecolor - if(bloodsucker_blood_volume > BLOOD_VOLUME_SAFE) - valuecolor = "#FFDDDD" - else if(bloodsucker_blood_volume > BLOOD_VOLUME_BAD) - valuecolor = "#FFAAAA" - - if(blood_display) - blood_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_blood_volume) - if(vamprank_display) if(bloodsucker_level_unspent > 0) vamprank_display.icon_state = "[initial(vamprank_display.icon_state)]_up" else vamprank_display.icon_state = initial(vamprank_display.icon_state) - vamprank_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_level) + vamprank_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(hud_text_color(), bloodsucker_level) - if(sunlight_display) - if(SSsunlight.sunlight_active) - valuecolor = "#FF5555" - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_day" - else - switch(round(SSsunlight.time_til_cycle, 1)) - if(0 to 30) - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_30" - valuecolor = "#FFCCCC" - if(31 to 60) - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_60" - valuecolor = "#FFE6CC" - if(61 to 90) - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_90" - valuecolor = "#FFFFCC" - else - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_night" - valuecolor = "#FFFFFF" - sunlight_display.maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT( \ - valuecolor, \ - (SSsunlight.time_til_cycle >= 60) ? "[round(SSsunlight.time_til_cycle / 60, 1)] m" : "[round(SSsunlight.time_til_cycle, 1)] s" \ - ) + update_sol_hud() +/datum/antagonist/bloodsucker/proc/hud_text_color() + return bloodsucker_blood_volume > BLOOD_VOLUME_SAFE ? "#FFDDDD" : "#FFAAAA" + +/// Updated every time blood is changed by either +/datum/antagonist/bloodsucker/proc/update_blood_hud() + if(!blood_display) + return + blood_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(hud_text_color(), bloodsucker_blood_volume) + +/datum/antagonist/bloodsucker/proc/update_sol_hud() + var/valuecolor = hud_text_color() + if(!sunlight_display) + return + if(SSsunlight.sunlight_active) + valuecolor = "#FF5555" + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_day" + else + switch(round(SSsunlight.time_til_cycle, 1)) + if(0 to 30) + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_30" + valuecolor = "#FFCCCC" + if(31 to 60) + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_60" + valuecolor = "#FFE6CC" + if(61 to 90) + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_90" + valuecolor = "#FFFFCC" + else + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_night" + valuecolor = "#FFFFFF" + sunlight_display.maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT( \ + valuecolor, \ + (SSsunlight.time_til_cycle >= 60) ? "[round(SSsunlight.time_til_cycle / 60, 1)] m" : "[round(SSsunlight.time_til_cycle, 1)] s" \ + ) /// 1 tile down #undef UI_BLOOD_DISPLAY /// 2 tiles down diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm index 78a9f1a37b0..ef7857b4e1e 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm @@ -8,20 +8,21 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(exposed_mob) if(!bloodsuckerdatum) return ..() - bloodsuckerdatum.bloodsucker_blood_volume = min(bloodsuckerdatum.bloodsucker_blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM) - + if(bloodsuckerdatum.bloodsucker_blood_volume > BLOOD_VOLUME_NORMAL) + return + bloodsuckerdatum.AdjustBloodVolume(round(reac_volume, 0.1)) /mob/living/carbon/transfer_blood_to(atom/movable/AM, amount, forced) . = ..() - if(!mind) return var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker) if(!bloodsuckerdatum) return - bloodsuckerdatum.bloodsucker_blood_volume -= amount + bloodsuckerdatum.AdjustBloodVolume(-amount) -/// Prevents using a Memento Mori +// Prevents using a Memento Mori +// todo move this to it's own trait /obj/item/clothing/neck/necklace/memento_mori/memento(mob/living/carbon/human/user) if(IS_BLOODSUCKER(user)) to_chat(user, span_warning("The Memento notices your undead soul, and refuses to react..")) @@ -35,6 +36,7 @@ return ..() // Used when analyzing a Bloodsucker, Masquerade will hide brain traumas (Unless you're a Beefman) +/// todo move this to it's own trait or something /mob/living/carbon/get_traumas() if(!mind) return ..() @@ -52,7 +54,11 @@ if(bloodsuckerdatum) . += "" . += "Blood Drank: [bloodsuckerdatum.total_blood_drank]" - . += "Frenzy blood threshold: [bloodsuckerdatum.frenzy_enter_threshold()]" + . += "Maximum blood: [bloodsuckerdatum.max_blood_volume]" + if(bloodsuckerdatum.frenzied) + . += "Frenzy exit blood threshold: [bloodsuckerdatum.frenzy_exit_threshold()]" + else + . += "Frenzy blood threshold: [bloodsuckerdatum.frenzy_enter_threshold()]" /datum/outfit/bloodsucker_outfit name = "Bloodsucker outfit (Preview only)" @@ -96,3 +102,16 @@ continue amount += chosen_bodypart.burn_dam return amount + +/mob/living/brain/can_be_revived() + if(health <= HEALTH_THRESHOLD_DEAD) + return FALSE + if(IS_BLOODSUCKER(src)) + return TRUE + . =..() + +// prevents players being trapped in their brain, alive, yet limbless and voiceless +/obj/item/bodypart/head/drop_organs(mob/user, violent_removal) + var/obj/item/organ/internal/brain/brain = locate(/obj/item/organ/internal/brain) in src + brain.brainmob.death() + . = ..() diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm index 009b7bf0bdb..44fcc51f6fd 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm @@ -4,27 +4,27 @@ /// Runs from COMSIG_LIVING_LIFE, handles Bloodsucker constant proccesses. /datum/antagonist/bloodsucker/proc/LifeTick(mob/living/source, seconds_per_tick, times_fired) SIGNAL_HANDLER - - if(isbrain(owner.current)) - return if(!owner) INVOKE_ASYNC(src, PROC_REF(HandleDeath)) return - if(HAS_TRAIT(owner.current, TRAIT_NODEATH)) + if(isbrain(owner.current)) + INVOKE_ASYNC(src, PROC_REF(update_hud)) + return + if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)) check_end_torpor() // Deduct Blood - if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_IMMOBILIZED) && !HAS_TRAIT(owner.current, TRAIT_NODEATH)) - INVOKE_ASYNC(src, PROC_REF(AddBloodVolume), -BLOODSUCKER_PASSIVE_BLOOD_DRAIN) // -.1 currently + if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_IMMOBILIZED) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)) + INVOKE_ASYNC(src, PROC_REF(AdjustBloodVolume), -BLOODSUCKER_PASSIVE_BLOOD_DRAIN) // -.1 currently if(HandleHealing()) if((COOLDOWN_FINISHED(src, bloodsucker_spam_healing)) && bloodsucker_blood_volume > 0) to_chat(owner.current, span_notice("The power of your blood begins knitting your wounds...")) COOLDOWN_START(src, bloodsucker_spam_healing, BLOODSUCKER_SPAM_HEALING) // Standard Updates - SEND_SIGNAL(src, COMSIG_BLOODSUCKER_ON_LIFETICK) INVOKE_ASYNC(src, PROC_REF(HandleStarving)) INVOKE_ASYNC(src, PROC_REF(update_blood)) INVOKE_ASYNC(src, PROC_REF(update_hud)) + SEND_SIGNAL(src, COMSIG_BLOODSUCKER_ON_LIFETICK) /datum/antagonist/bloodsucker/proc/on_death(mob/living/source, gibbed) SIGNAL_HANDLER @@ -39,16 +39,33 @@ /** * ## BLOOD STUFF */ -/datum/antagonist/bloodsucker/proc/AddBloodVolume(value) +/datum/antagonist/bloodsucker/proc/AdjustBloodVolume(value) bloodsucker_blood_volume = clamp(bloodsucker_blood_volume + value, 0, max_blood_volume) + update_blood_hud() + +/datum/antagonist/bloodsucker/proc/SetBloodVolume(value) + bloodsucker_blood_volume = clamp(value, 0, max_blood_volume) + update_blood_hud() + +#define MASQUERADE /datum/action/cooldown/bloodsucker/masquerade /datum/antagonist/bloodsucker/proc/AddHumanityLost(value) - // 100 humanity lost already causes you to frenzy at 25 + 100 * 10 = 1025 blood and deal 1 + 100 / 10 = 11 burn per second due to frenzy - if(humanity_lost >= 100) - to_chat(owner.current, span_warning("You hit the maximum amount of lost Humanty, you are far from Human.")) + if(value == 0) return - humanity_lost += value - to_chat(owner.current, span_warning("You feel as if you lost some of your humanity, you will now enter Frenzy at [FRENZY_THRESHOLD_ENTER + (humanity_lost * 10)] Blood.")) + var/has_masq = is_path_in_list(MASQUERADE, powers) + if(value + humanity_lost >= HUMANITY_LOST_MAXIMUM) + if(has_masq) + RemovePowerByPath(MASQUERADE) + to_chat(owner.current, span_warning("You hit the maximum amount of lost Humanty, you are far from Human. You've forgotten how to pretend to be like your prey...")) + else + to_chat(owner.current, span_hypnophrase("The Beast, it yearns for Blood...")) + else if(!has_masq) + BuyPower(MASQUERADE) + to_chat(owner.current, span_hypnophrase("You've remembered, yet again, how it feels to live again.")) + humanity_lost = clamp(value, 0, HUMANITY_LOST_MAXIMUM) + to_chat(owner.current, span_warning("You feel as if you [value < 0 ? "gained" : "lost" ] some of your humanity, you will now enter Frenzy at [FRENZY_THRESHOLD_ENTER + (humanity_lost * 10)] Blood.")) + +#undef MASQUERADE /// mult: SILENT feed is 1/3 the amount /datum/antagonist/bloodsucker/proc/handle_feeding(mob/living/carbon/target, mult=1, power_level) @@ -74,7 +91,7 @@ blood_taken /= 2 //if (!iscarbon(target)) // Penalty for Animals (they're junk food) // Apply to Volume - AddBloodVolume(blood_taken) + AdjustBloodVolume(blood_taken) // Reagents (NOT Blood!) if(target.reagents && target.reagents.total_volume) target.reagents.trans_to(owner.current, INGEST, 1) // Run transfer of 1 unit of reagent from them to me. @@ -90,42 +107,49 @@ /datum/antagonist/bloodsucker/proc/HandleHealing(mult = 1) var/actual_regen = bloodsucker_regen_rate + additional_regen // Don't heal if I'm staked or on Masquerade (+ not in a Coffin). Masqueraded Bloodsuckers in a Coffin however, will heal. - if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT(owner.current, TRAIT_NODEATH))) + if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))) return FALSE // Garlic in you? No healing for you! if(HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT)) return FALSE - owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (actual_regen * 4) * mult) //adjustBrainLoss(-1 * (actual_regen * 4) * mult, 0) + owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * actual_regen * mult) //adjustBrainLoss(-1 * (actual_regen * 4) * mult, 0) if(!iscarbon(owner.current)) // Damage Heal: Do I have damage to ANY bodypart? return FALSE var/mob/living/carbon/user = owner.current var/costMult = 1 // Coffin makes it cheaper - var/bruteheal = min(user.getBruteLoss_nonProsthetic(), actual_regen) // BRUTE: Always Heal + // If you're a synth, you heal prosthetic damage. + var/bruteLoss = getBruteLoss() + var/bruteheal = min(bruteLoss, actual_regen) // BRUTE: Always Heal var/fireheal = 0 // BURN: Heal in Coffin while Fakedeath, or when damage above maxhealth (you can never fully heal fire) // Checks if you're in a coffin here, additionally checks for Torpor right below it. var/amInCoffin = istype(user.loc, /obj/structure/closet/crate/coffin) - if(amInCoffin && HAS_TRAIT(user, TRAIT_NODEATH)) + if(amInCoffin && HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)) if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && (COOLDOWN_FINISHED(src, bloodsucker_spam_healing))) to_chat(user, span_alert("You do not heal while your Masquerade ability is active.")) COOLDOWN_START(src, bloodsucker_spam_healing, BLOODSUCKER_SPAM_MASQUERADE) return FALSE - fireheal = min(user.getFireLoss_nonProsthetic(), actual_regen) + fireheal = min(getFireLoss(), actual_regen) mult *= 5 // Increase multiplier if we're sleeping in a coffin. costMult /= 2 // Decrease cost if we're sleeping in a coffin. user.extinguish_mob() + user.bodytemperature = user.get_body_temp_normal() + if(ishuman(user)) + var/mob/living/carbon/human/humie = user + humie.set_coretemperature(humie.get_body_temp_normal(apply_change = FALSE)) user.remove_all_embedded_objects() // Remove Embedded! if(check_limbs(costMult)) return TRUE // In Torpor, but not in a Coffin? Heal faster anyways. - else if(HAS_TRAIT(user, TRAIT_NODEATH)) - fireheal = min(user.getFireLoss_nonProsthetic(), actual_regen) / 1.2 // 20% slower than being in a coffin + else if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)) + var/fireloss = getFireLoss() + fireheal = min(fireloss, actual_regen) / 1.2 // 20% slower than being in a coffin mult *= 3 // Heal if Damaged - if((bruteheal + fireheal > 0) && mult != 0) // Just a check? Don't heal/spend, and return. + if((bruteheal + fireheal) && mult != 0) // Just a check? Don't heal/spend, and return. // We have damage. Let's heal (one time) - user.adjustBruteLoss(-bruteheal * mult, forced=TRUE) // Heal BRUTE / BURN in random portions throughout the body. - user.adjustFireLoss(-fireheal * mult, forced=TRUE) - AddBloodVolume(((bruteheal * -0.5) + (fireheal * -1)) * costMult * mult) // Costs blood to heal + user.adjustBruteLoss(-bruteheal * mult) // Heal BRUTE / BURN in random portions throughout the body. + user.adjustFireLoss(-fireheal * mult) + AdjustBloodVolume(((bruteheal * -0.5) + (fireheal * -1)) * costMult * mult) // Costs blood to heal return TRUE /datum/antagonist/bloodsucker/proc/check_limbs(costMult = 1) @@ -136,9 +160,9 @@ return FALSE for(var/missing_limb in missing) //Find ONE Limb and regenerate it. user.regenerate_limb(missing_limb, FALSE) - AddBloodVolume(-limb_regen_cost) + AdjustBloodVolume(-limb_regen_cost) var/obj/item/bodypart/missing_bodypart = user.get_bodypart(missing_limb) // 2) Limb returns Damaged - missing_bodypart.brute_dam = 60 + missing_bodypart.brute_dam = missing_bodypart.max_damage to_chat(user, span_notice("Your flesh knits as it regrows your [missing_bodypart]!")) playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) return TRUE @@ -153,18 +177,22 @@ * * This is called on Bloodsucker's Assign, and when they end Torpor. */ - -/datum/antagonist/bloodsucker/proc/heal_vampire_organs() +/// TODO: Separate this into smaller functions +/datum/antagonist/bloodsucker/proc/heal_vampire_organs(regenerate_heart = FALSE) var/mob/living/carbon/bloodsuckeruser = owner.current - + // please don't poison or asphyxiate the immune + bloodsuckeruser.setToxLoss(0, forced = TRUE) + bloodsuckeruser.setOxyLoss(0, forced = TRUE) if(!bloodsuckeruser) return - + if(HAS_TRAIT_FROM_ONLY(bloodsuckeruser, TRAIT_HUSK, CHANGELING_DRAIN) || bloodsuckeruser.has_status_effect(/datum/status_effect/gutted)) + to_chat(bloodsuckeruser, span_danger("Your immortal blood has healed your body from near-irrecoverable damage, but has used nearly all of your blood in doing so!")) + AddHumanityLost(2) + SetBloodVolume(min(bloodsucker_blood_volume, frenzy_enter_threshold() * 2)) + bloodsuckeruser.cure_husk(CHANGELING_DRAIN) bloodsuckeruser.cure_husk(BURN) - bloodsuckeruser.regenerate_organs(regenerate_existing = FALSE) - - for(var/obj/item/organ/organ as anything in bloodsuckeruser.organs) - organ.set_organ_damage(0) + if(regenerate_heart || bloodsuckeruser.get_organ_slot(ORGAN_SLOT_HEART)) + bloodsuckeruser.regenerate_organs(regenerate_existing = FALSE) if(!HAS_TRAIT(bloodsuckeruser, TRAIT_MASQUERADE)) var/obj/item/organ/internal/heart/current_heart = bloodsuckeruser.get_organ_slot(ORGAN_SLOT_HEART) current_heart.Stop() @@ -174,6 +202,10 @@ current_eyes.color_cutoffs = BLOODSUCKER_SIGHT_COLOR_CUTOFF current_eyes.sight_flags = SEE_MOBS bloodsuckeruser.update_sight() + /// Disable gutting for the chest + var/obj/item/bodypart/chest/target_chest = bloodsuckeruser.get_bodypart(BODY_ZONE_CHEST) + if(target_chest && !(target_chest.bodypart_flags & BODYPART_UNREMOVABLE)) + target_chest.bodypart_flags |= BODYPART_UNREMOVABLE // Sometimes bloodsuckers can get into a loop of reviving and dying, if they somehow get a new body without being revived. if(_listen_lookup?[COMSIG_BLOODSUCKER_ON_LIFETICK] || bloodsuckeruser._listen_lookup?[COMSIG_LIVING_REVIVE]) on_revive() @@ -184,7 +216,8 @@ // From [powers/panacea.dm] var/list/bad_organs = list( bloodsuckeruser.get_organ_by_type(/obj/item/organ/internal/body_egg), - bloodsuckeruser.get_organ_by_type(/obj/item/organ/internal/zombie_infection)) + bloodsuckeruser.get_organ_by_type(/obj/item/organ/internal/zombie_infection) + ) for(var/tumors in bad_organs) var/obj/item/organ/yucky_organs = tumors if(!istype(yucky_organs)) @@ -214,9 +247,14 @@ FinalDeath() return // Temporary Death? Convert to Torpor. - if(HAS_TRAIT(owner.current, TRAIT_NODEATH)) + if(HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT) || isbrain(owner.current)) return - check_begin_torpor(TRUE) + // Won't torpor without vital organs, as this means they'd revive without a heart + if(ishuman(owner.current)) + var/mob/living/carbon/human/humie = owner.current + if(humie.dna.species.mutantheart && !owner.current.get_organ_slot(ORGAN_SLOT_HEART)) + return + check_begin_torpor(TORPOR_SKIP_CHECK_ALL) /datum/antagonist/bloodsucker/proc/HandleStarving() // I am thirsty for blood! // Nutrition - The amount of blood is how full we are. @@ -226,11 +264,11 @@ // handled in bloodsucker_integration.dm // BLOOD_VOLUME_EXIT: [250] - Exit Frenzy (If in one) This is high because we want enough to kill the poor soul they feed off of. var/datum/status_effect/frenzy/status_effect = owner.current.has_status_effect(/datum/status_effect/frenzy) - if(bloodsucker_blood_volume >= frenzy_exit_threshold() && frenzied && status_effect.duration == -1) + if(bloodsucker_blood_volume >= frenzy_exit_threshold() && frenzied && status_effect?.duration == -1) status_effect.duration = world.time + 10 SECONDS owner.current.balloon_alert(owner.current, "Frenzy ends in 10 seconds!") // BLOOD_VOLUME_BAD: [224] - Jitter - if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD && prob(0.5) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE)) + if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD && prob(0.5) && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE)) owner.current.set_timed_status_effect(3 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) // BLOOD_VOLUME_SURVIVE: [122] - Blur Vision if(bloodsucker_blood_volume < BLOOD_VOLUME_SURVIVE) @@ -267,10 +305,25 @@ owner.current.blood_volume = bloodsucker_blood_volume +/// Turns the bloodsucker into a wacky talking head. +/datum/antagonist/bloodsucker/proc/talking_head() + var/mob/living/poor_fucker = owner.current + // Don't do anything if we're not actually inside a brain and a head + if(!is_head(poor_fucker) || poor_fucker.stat != DEAD || !poor_fucker.can_be_revived()) + return + // Ensure that the HUD updates (We might have lost the heart at some point) + RegisterSignal(poor_fucker, COMSIG_LIVING_LIFE, PROC_REF(LifeTick), TRUE) + poor_fucker.revive() + poor_fucker.stat = CONSCIOUS + to_chat(poor_fucker, span_warning("Your immortal [pick(list("blood", "curse"))] keeps your head alive! Though... what will you do now?")) + // No lungs to speak, let's make it spooky + poor_fucker.speech_span = SPAN_PAPYRUS + /// Gibs the Bloodsucker, roundremoving them. -/datum/antagonist/bloodsucker/proc/FinalDeath() +/datum/antagonist/bloodsucker/proc/FinalDeath(check_organs = FALSE) + SIGNAL_HANDLER // If we have no body, end here. - if(!owner.current) + if(!owner.current || isbrain(owner.current)) return UnregisterSignal(src, list( COMSIG_BLOODSUCKER_ON_LIFETICK, diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/names.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/names.dm index c5d78b293fc..60a9c1c1d74 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/names.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/names.dm @@ -36,7 +36,7 @@ ) ///Returns a Title for the Bloodsucker. -/datum/antagonist/bloodsucker/proc/SelectTitle(am_fledgling = 0, forced = FALSE) +/datum/antagonist/bloodsucker/proc/SelectTitle(am_fledgling = FALSE, forced = FALSE) // Already have Title if(!forced && bloodsucker_title != null) return diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/objectives.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/objectives.dm index ca5ea15828c..967ef6cefa7 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/objectives.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/objectives.dm @@ -165,35 +165,23 @@ // NOTE: Look up /steal in objective.dm for inspiration. /// Steal hearts. You just really wanna have some hearts. -/datum/objective/bloodsucker/heartthief +/datum/objective/steal_n_of_type/hearts + martyr_compatible = TRUE name = "heartthief" + wanted_items = (/obj/item/organ/internal/heart) // GENERATE! -/datum/objective/bloodsucker/heartthief/New() - target_amount = rand(2,3) +/datum/objective/steal_n_of_type/hearts/New() + amount = rand(2, 3) + explanation_text = "Steal and keep [amount] organic hearts. Must be obtained from non-monkeys. Examine hearts thoroughly to see if they are valid." + update_explanation_text() ..() -// EXPLANATION -/datum/objective/bloodsucker/heartthief/update_explanation_text() +/datum/objective/steal_n_of_type/hearts/check_if_valid_item(obj/item/organ/internal/heart/current_item) . = ..() - explanation_text = "Steal and keep [target_amount] organic heart\s." - -// WIN CONDITIONS? -/datum/objective/bloodsucker/heartthief/check_completion() - if(!owner.current) + if(current_item.type == /obj/item/organ/internal/heart/monkey || IS_ROBOTIC_ORGAN(current_item)) return FALSE - var/list/all_items = owner.current.get_all_contents() - var/heart_count = 0 - for(var/obj/item/organ/internal/heart/current_hearts in all_items) - if(IS_ROBOTIC_ORGAN(current_hearts)) // No robo-hearts allowed - continue - heart_count++ - - if(heart_count >= target_amount) - return TRUE - return FALSE - ////////////////////////////////////////////////////////////////////////////////////// ///Eat blood from a lot of people @@ -202,7 +190,7 @@ // GENERATE! /datum/objective/bloodsucker/gourmand/New() - target_amount = rand(450,650) + target_amount = rand(800, 3 * BLOOD_VOLUME_NORMAL) ..() // EXPLANATION @@ -285,10 +273,9 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(!bloodsuckerdatum) return FALSE - for(var/datum/antagonist/vassal/vassaldatum in bloodsuckerdatum.vassals) - if(IS_FAVORITE_VASSAL(vassaldatum.owner.current)) - if(vassaldatum.owner.has_antag_datum(/datum/antagonist/bloodsucker)) - return TRUE + for(var/datum/antagonist/bloodsucker/sired_vamp in GLOB.antagonists) + if(sired_vamp.ventrue_sired == bloodsuckerdatum) + return TRUE return FALSE diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm index 3b7e517de0e..3336ed52256 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm @@ -7,6 +7,10 @@ if(vamp_examine) examine_text += vamp_examine +/datum/antagonist/bloodsucker/proc/BuyPowers(powers = list()) + for(var/datum/action/cooldown/bloodsucker/power as anything in powers) + BuyPower(power) + ///Called when a Bloodsucker buys a power: (power) /datum/antagonist/bloodsucker/proc/BuyPower(datum/action/cooldown/bloodsucker/power) for(var/datum/action/cooldown/bloodsucker/current_powers as anything in powers) @@ -25,6 +29,11 @@ powers -= power power.Remove(owner.current) +/datum/antagonist/bloodsucker/proc/RemovePowerByPath(datum/action/cooldown/bloodsucker/power_to_remove) + for(var/datum/action/cooldown/bloodsucker/power as anything in powers) + if(power?.type == power_to_remove) + RemovePower(power) + ///When a Bloodsucker breaks the Masquerade, they get their HUD icon changed, and Malkavian Bloodsuckers get alerted. /datum/antagonist/bloodsucker/proc/break_masquerade(mob/admin) if(broke_masquerade) @@ -43,6 +52,8 @@ return to_chat(owner.current, span_cultboldtalic("You have re-entered the Masquerade.")) broke_masquerade = FALSE + antag_hud_name = "bloodsucker" + add_team_hud(owner.current) /datum/antagonist/bloodsucker/proc/give_masquerade_infraction() if(broke_masquerade) @@ -66,12 +77,6 @@ if(bloodsucker_level_unspent >= 2) to_chat(owner, span_announce("Bloodsucker Tip: If you cannot find or steal a coffin to use, you can build one from wood or metal.")) return - if(max_vassals() >= 1 && !(/datum/crafting_recipe/vassalrack in owner?.learned_recipes)) - owner.teach_crafting_recipe(/datum/crafting_recipe/vassalrack) - owner.teach_crafting_recipe(/datum/crafting_recipe/candelabrum) - owner.teach_crafting_recipe(/datum/crafting_recipe/bloodthrone) - owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin) - owner.current.balloon_alert(owner.current, "new recipes learned! Vassalization unlocked!") SpendRank() /datum/antagonist/bloodsucker/proc/RankDown() @@ -94,7 +99,7 @@ ///Disables all powers, accounting for torpor /datum/antagonist/bloodsucker/proc/DisableAllPowers(forced = FALSE) for(var/datum/action/cooldown/bloodsucker/power as anything in powers) - if(forced || ((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(owner.current, TRAIT_NODEATH))) + if(forced || ((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT))) if(power.active) power.DeactivatePower() @@ -124,16 +129,16 @@ * viewer - The person examining. */ /datum/antagonist/bloodsucker/proc/return_vamp_examine(mob/living/viewer) - if(!viewer.mind) + if(!viewer.mind && !isobserver(viewer)) return FALSE // Viewer is Target's Vassal? - if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in vassals) + if(!isobserver(viewer) && (viewer.mind.has_antag_datum(/datum/antagonist/vassal) in vassals)) var/returnString = "\[This is your Master!\]" var/returnIcon = "[icon2html('modular_zubbers/icons/misc/language.dmi', world, "bloodsucker")]" returnString += "\n" return returnIcon + returnString // Viewer not a Vamp AND not the target's vassal? - if(!viewer.mind.has_antag_datum((/datum/antagonist/bloodsucker)) && !(viewer in vassals)) + if(!isobserver(viewer) && !viewer.mind.has_antag_datum((/datum/antagonist/bloodsucker)) && !(viewer in vassals)) if(!(HAS_TRAIT(viewer.mind, TRAIT_BLOODSUCKER_HUNTER) && broke_masquerade)) return FALSE // Default String @@ -159,3 +164,88 @@ /datum/antagonist/bloodsucker/proc/frenzy_exit_threshold() return FRENZY_THRESHOLD_EXIT + (humanity_lost * 10) + +/datum/antagonist/bloodsucker/proc/add_signals_to_heart(mob/living/carbon/human/current_mob) + if(heart?.resolve()) + remove_signals_from_heart(current_mob) + var/organ = current_mob.get_organ_slot(ORGAN_SLOT_HEART) + heart = WEAKREF(organ) + RegisterSignal(organ, COMSIG_ORGAN_REMOVED, PROC_REF(on_organ_removal)) + RegisterSignal(organ, COMSIG_ORGAN_BEING_REPLACED, PROC_REF(before_organ_replace)) + +/datum/antagonist/bloodsucker/proc/remove_signals_from_heart(mob/living/carbon/human/current_mob) + var/organ = heart.resolve() + if(!organ) + return + UnregisterSignal(organ, COMSIG_ORGAN_REMOVED) + UnregisterSignal(organ, COMSIG_ORGAN_BEING_REPLACED) + organ = null + +/datum/antagonist/bloodsucker/proc/on_organ_removal(obj/item/organ/organ, mob/living/carbon/old_owner) + SIGNAL_HANDLER + if(old_owner.get_organ_slot(ORGAN_SLOT_HEART) || organ.slot != ORGAN_SLOT_HEART || !old_owner.dna.species.mutantheart) + return + remove_signals_from_heart(old_owner) + // You don't run bloodsucker life without a heart or brain + RegisterSignal(old_owner, COMSIG_ENTER_COFFIN, PROC_REF(regain_heart)) + UnregisterSignal(old_owner, COMSIG_LIVING_LIFE) + DisableAllPowers(TRUE) + if(HAS_TRAIT_FROM_ONLY(old_owner, TRAIT_NODEATH, BLOODSUCKER_TRAIT)) + torpor_end(TRUE) + to_chat(old_owner, span_userdanger("You have lost your [organ.slot]!")) + to_chat(old_owner, span_warning("This means you will no longer enter torpor nor revive from death, and you will no longer heal any damage, nor can you use your abilities.")) + +/datum/antagonist/bloodsucker/proc/on_organ_gain(mob/living/carbon/human/current_mob, obj/item/organ/replacement) + SIGNAL_HANDLER + if(replacement.slot != ORGAN_SLOT_HEART) + return + // Shit might get really fucked up. Let's try to fix things if it does + if(current_mob != owner.current) + UnregisterSignal(current_mob, COMSIG_CARBON_GAIN_ORGAN) + RegisterSignal(owner.current, COMSIG_CARBON_GAIN_ORGAN) + add_signals_to_heart(owner.current) + RegisterSignal(owner.current, COMSIG_LIVING_LIFE, PROC_REF(LifeTick), TRUE) + CRASH("What the fuck, somehow called on_organ_gain signal on [src] without current_mob being the antag datum's owner?") + UnregisterSignal(current_mob, COMSIG_ENTER_COFFIN) + RegisterSignal(current_mob, COMSIG_LIVING_LIFE, PROC_REF(LifeTick)) + add_signals_to_heart(current_mob) + +/// This handles regen_organs replacing organs, without this the bloodsucker would die for a moment due to their heart being removed for a moment +/datum/antagonist/bloodsucker/proc/before_organ_replace(obj/item/organ/old_organ, obj/item/organ/new_organ) + SIGNAL_HANDLER + if(new_organ.slot != ORGAN_SLOT_HEART) + return + remove_signals_from_heart(owner.current) + +/// checks if we're a brainmob inside a brain & the brain is inside a head +/datum/antagonist/bloodsucker/proc/is_head(mob/living/poor_fucker) + if(!istype(poor_fucker?.loc, /obj/item/organ/internal/brain)) + return + var/obj/brain = poor_fucker.loc + if(!istype(brain?.loc, /obj/item/bodypart/head)) + return + return brain.loc + +// helper procs for damage checking, just in case a synth becomes one, let's them heal thesmelves +/datum/antagonist/bloodsucker/proc/getBruteLoss() + var/mob/living/carbon/human/humie = owner.current + return issynthetic(humie) ? humie.getBruteLoss() : humie.getBruteLoss_nonProsthetic() + +/datum/antagonist/bloodsucker/proc/getFireLoss() + var/mob/living/carbon/human/humie = owner.current + return issynthetic(humie) ? humie.getFireLoss() : humie.getFireLoss_nonProsthetic() + +/datum/antagonist/bloodsucker/proc/admin_set_blood(mob/admin) + var/blood = tgui_input_number(admin, "What blood level to set [owner.current]'s to?", "Blood is life.", floor(bloodsucker_blood_volume), max_blood_volume, 0) + // 0 input is falsey + if(blood == null) + return + SetBloodVolume(blood) + update_hud() + +/datum/antagonist/bloodsucker/proc/regain_heart(mob/coffin_dweller, obj/structure/closet/crate/coffin/coffin, mob/user) + SIGNAL_HANDLER + var/obj/item/organ/heart = locate(/obj/item/organ/internal/heart) in coffin.contents + if(heart && !coffin_dweller.get_organ_slot(ORGAN_SLOT_HEART)) + to_chat(span_warning("You have regained your heart!")) + heart.Insert(coffin_dweller) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm index c34262f120f..d883645dd75 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm @@ -41,7 +41,7 @@ /// Cycle through all vamp antags and check if they're inside a closet. /datum/antagonist/bloodsucker/proc/handle_sol() SIGNAL_HANDLER - if(!owner || !owner.current) + if(!owner || !owner.current || isbrain(owner.current)) return if(!istype(owner.current.loc, /obj/structure)) @@ -64,10 +64,10 @@ if(istype(owner.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection if(owner.current.am_staked() && COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn)) - to_chat(owner.current, span_userdanger("You are staked! Remove the offending weapon from your heart before sleeping.")) + to_chat(owner.current, span_userdanger("You are staked you will keep burning until it is removed! Remove the offending weapon from your heart before sleeping.")) COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol - if(!HAS_TRAIT(owner.current, TRAIT_NODEATH)) - check_begin_torpor(TRUE) + if(!HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)) + check_begin_torpor(TORPOR_SKIP_CHECK_ALL) owner.current.add_mood_event("vampsleep", /datum/mood_event/coffinsleep) return @@ -112,58 +112,68 @@ * - Having less than 10 Brute & Burn Combined while INSIDE of your Coffin while it isnt Sol. * - Sol being over, dealt with by /sunlight/process() [bloodsucker_daylight.dm] */ -/datum/antagonist/bloodsucker/proc/check_begin_torpor(SkipChecks = FALSE) +/datum/antagonist/bloodsucker/proc/check_begin_torpor(SkipChecks = NONE) var/mob/living/carbon/user = owner.current /// Are we entering Torpor via Sol/Death? Then entering it isnt optional! - if(SkipChecks) - to_chat(user, span_danger("Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor.")) - torpor_begin() + if(SkipChecks & TORPOR_SKIP_CHECK_ALL) + if(COOLDOWN_FINISHED(src, bloodsucker_spam_torpor)) + to_chat(user, span_danger("Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor.")) + torpor_begin(TRUE) return /// Prevent Torpor whilst frenzied. - if(frenzied || (IS_DEAD_OR_INCAP(user) && bloodsucker_blood_volume == 0)) + if(!(SkipChecks & TORPOR_SKIP_CHECK_FRENZY) && (frenzied || (IS_DEAD_OR_INCAP(user) && bloodsucker_blood_volume == 0))) to_chat(user, span_userdanger("Your frenzy prevents you from entering torpor!")) return - var/total_brute = user.getBruteLoss_nonProsthetic() - var/total_burn = user.getFireLoss_nonProsthetic() - var/total_damage = total_brute + total_burn + // sometimes you might incur these damage types when you really, should not, important to check for it here so we can heal it later + var/total_damage = getBruteLoss() + getFireLoss() + user.getToxLoss() + user.getOxyLoss() /// Checks - Not daylight & Has more than 10 Brute/Burn & not already in Torpor - if(!SSsunlight.sunlight_active && total_damage >= 10 && !HAS_TRAIT(owner.current, TRAIT_NODEATH)) + if(SkipChecks & TORPOR_SKIP_CHECK_DAMAGE || !SSsunlight.sunlight_active && total_damage >= 10 && !HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)) torpor_begin() /datum/antagonist/bloodsucker/proc/check_end_torpor() var/mob/living/carbon/user = owner.current - var/total_brute = user.getBruteLoss_nonProsthetic() - var/total_burn = user.getFireLoss_nonProsthetic() + var/total_brute = getBruteLoss() + var/total_burn = getFireLoss() + // for waking up we ignore all other damage types so we don't get stuck var/total_damage = total_brute + total_burn - if(bloodsucker_blood_volume == 0 || owner.current.am_staked() || HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT)) - torpor_end() if(total_burn >= user.maxHealth * 2) return FALSE if(SSsunlight.sunlight_active) return FALSE + if(bloodsucker_blood_volume == 0 || owner.current.am_staked() || HAS_TRAIT(owner.current, TRAIT_GARLIC_REAGENT)) + // If you're frenzying, you need a bit more health to actually have a chance to do something + if(frenzied && total_damage >= user.maxHealth) + return FALSE + torpor_end() // You are in a Coffin, so instead we'll check TOTAL damage, here. if(istype(user.loc, /obj/structure/closet/crate/coffin)) if(total_damage <= 10) torpor_end() else - if(total_brute <= 10) + if(total_damage <= 10) torpor_end() + return TRUE -/datum/antagonist/bloodsucker/proc/torpor_begin() - to_chat(owner.current, span_notice("You enter the horrible slumber of deathless Torpor. You will heal until you are renewed.")) +/datum/antagonist/bloodsucker/proc/torpor_begin(silent = FALSE) + // slow down bucko + if(!COOLDOWN_FINISHED(src, bloodsucker_spam_torpor)) + return + if(!silent) + to_chat(owner.current, span_notice("You enter the horrible slumber of deathless Torpor. You will heal until you are renewed.")) // Force them to go to sleep REMOVE_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT) // Without this, you'll just keep dying while you recover. owner.current.add_traits(list(TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_DEATHCOMA, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE), BLOODSUCKER_TRAIT) - owner.current.set_timed_status_effect(0 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) + owner.current.do_jitter_animation(2) // Disable ALL Powers DisableAllPowers() -/datum/antagonist/bloodsucker/proc/torpor_end() - owner.current.grab_ghost() - to_chat(owner.current, span_warning("You have recovered from Torpor.")) +/datum/antagonist/bloodsucker/proc/torpor_end(quiet = FALSE) + if(quiet) + owner.current.grab_ghost() + to_chat(owner.current, span_warning("You have recovered from Torpor.")) owner.current.remove_traits(list(TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_DEATHCOMA, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE), BLOODSUCKER_TRAIT) - if(!HAS_TRAIT(owner.current, TRAIT_MASQUERADE)) + if(!HAS_TRAIT_FROM_ONLY(owner.current, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT)) ADD_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT) heal_vampire_organs() SEND_SIGNAL(src, BLOODSUCKER_EXIT_TORPOR) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm index 4ebe37bb114..c09f75b1f7e 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm @@ -38,7 +38,8 @@ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_RANK_UP, PROC_REF(on_spend_rank)) RegisterSignal(bloodsuckerdatum, BLOODSUCKER_INTERACT_WITH_VASSAL, PROC_REF(on_interact_with_vassal)) - RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MAKE_FAVORITE, PROC_REF(on_favorite_vassal)) + RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MAKE_FAVORITE, PROC_REF(favorite_vassal_gain)) + RegisterSignal(bloodsuckerdatum, BLOODSUCKER_LOOSE_FAVORITE, PROC_REF(favorite_vassal_loss)) RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, PROC_REF(on_vassal_made)) RegisterSignal(bloodsuckerdatum, BLOODSUCKER_EXIT_TORPOR, PROC_REF(on_exit_torpor)) @@ -205,7 +206,7 @@ if(cost_rank) bloodsuckerdatum.bloodsucker_level_unspent-- if(blood_cost) - bloodsuckerdatum.AddBloodVolume(-blood_cost) + bloodsuckerdatum.AdjustBloodVolume(-blood_cost) // Ranked up enough to get your true Reputation? if(bloodsuckerdatum.bloodsucker_level == BLOODSUCKER_HIGH_LEVEL) @@ -214,10 +215,19 @@ to_chat(bloodsuckerdatum.owner.current, span_notice("You are now a rank [bloodsuckerdatum.bloodsucker_level] Bloodsucker. \ - Your strength, health, feed rate, regen rate, and maximum blood capacity have all increased! \n\ + Your strength, feed rate, regen rate, and maximum blood capacity have all increased! \n\ * Your existing powers have all ranked up as well!")) bloodsuckerdatum.owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, TRUE, pressure_affected = FALSE) bloodsuckerdatum.update_hud() + bloodsuckerdatum.update_static_data_for_all_viewers() + + // unlock vassalizing if we have a vassal slot + if(bloodsuckerdatum.max_vassals() >= 1 && !(/datum/crafting_recipe/vassalrack in bloodsuckerdatum.owner?.learned_recipes)) + bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/vassalrack) + bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/candelabrum) + bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/bloodthrone) + bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin) + bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "new recipes learned! Vassalization unlocked!") /** * Called when we are trying to turn someone into a Favorite Vassal @@ -258,7 +268,7 @@ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "Out of Special Vassal slots!") return - to_chat(bloodsuckerdatum.owner.current, span_notice("You can change who this Vassal is, who are they to you?")) + to_chat(bloodsuckerdatum.owner.current, span_notice("You can change who this Vassal is, who are they to you? This will cost [SPECIAL_VASSAL_COST] blood.")) var/vassal_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, radial_display) if(!vassal_response) return @@ -266,7 +276,7 @@ if(QDELETED(src) || QDELETED(bloodsuckerdatum.owner.current) || QDELETED(vassaldatum.owner.current)) return FALSE vassaldatum.make_special(vassal_response) - bloodsuckerdatum.bloodsucker_blood_volume -= 150 + bloodsuckerdatum.AdjustBloodVolume(-SPECIAL_VASSAL_COST) return TRUE /** @@ -275,6 +285,9 @@ * bloodsuckerdatum - antagonist datum of the Bloodsucker who turned them into a Vassal. * vassaldatum - the antagonist datum of the Vassal being offered up. */ -/datum/bloodsucker_clan/proc/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) +/datum/bloodsucker_clan/proc/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) SIGNAL_HANDLER vassaldatum.BuyPower(/datum/action/cooldown/bloodsucker/targeted/brawn) + +/datum/bloodsucker_clan/proc/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) + SIGNAL_HANDLER diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_malkavian.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_malkavian.dm index af9d200ffa9..81938f9b9fa 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_malkavian.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_malkavian.dm @@ -52,7 +52,7 @@ var/message = pick(strings("malkavian_revelations.json", "revelations", "modular_zubbers/strings/bloodsuckers")) INVOKE_ASYNC(bloodsuckerdatum.owner.current, TYPE_PROC_REF(/atom/movable, say), message, forced = CLAN_MALKAVIAN) -/datum/bloodsucker_clan/malkavian/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) +/datum/bloodsucker_clan/malkavian/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) var/mob/living/carbon/carbonowner = vassaldatum.owner.current if(istype(carbonowner)) carbonowner.gain_trauma(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE) @@ -61,8 +61,18 @@ psychotic_brawling.teach(vassaldatum.owner.current, TRUE) to_chat(vassaldatum.owner.current, span_notice("Additionally, you now suffer the same fate as your Master, while also gaining the ability to tap into the madness when fighting.")) +/datum/bloodsucker_clan/malkavian/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) + var/mob/living/carbon/carbonowner = vassaldatum.owner.current + if(istype(carbonowner)) + carbonowner.cure_trauma_type(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE) + carbonowner.cure_trauma_type(/datum/brain_trauma/special/bluespace_prophet/phobetor, TRAUMA_RESILIENCE_ABSOLUTE) + if(!istype(/datum/martial_art/psychotic_brawling, vassaldatum.owner.martial_art)) + return + var/datum/martial_art/psychotic_brawling/psychotic_brawling = vassaldatum.owner.martial_art + psychotic_brawling.remove(vassaldatum.owner.current) + /datum/bloodsucker_clan/malkavian/on_exit_torpor(datum/antagonist/bloodsucker/source) - var/mob/living/carbon/carbonowner = bloodsuckerdatum.owner.current + var/mob/living/carbon/carbonowner = bloodsuckerdatum.owner.martial_art if(istype(carbonowner)) carbonowner.gain_trauma(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE) carbonowner.gain_trauma(/datum/brain_trauma/special/bluespace_prophet, TRAUMA_RESILIENCE_ABSOLUTE) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_nosferatu.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_nosferatu.dm index 16543d531fa..4946cb61832 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_nosferatu.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_nosferatu.dm @@ -33,6 +33,12 @@ if(!HAS_TRAIT(bloodsuckerdatum.owner.current, TRAIT_NOBLOOD)) bloodsuckerdatum.owner.current.blood_volume = BLOOD_VOLUME_SURVIVE -/datum/bloodsucker_clan/nosferatu/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) - vassaldatum.owner.current.add_traits(list(TRAIT_VENTCRAWLER_NUDE, TRAIT_DISFIGURED, TRAIT_TRUE_NIGHT_VISION, TRAIT_KNOW_ENGI_WIRES, TRAIT_SILENT_FOOTSTEPS), BLOODSUCKER_TRAIT) +/datum/bloodsucker_clan/nosferatu/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) + var/list/traits_to_add = list(TRAIT_VENTCRAWLER_NUDE, TRAIT_DISFIGURED, TRAIT_TRUE_NIGHT_VISION, TRAIT_KNOW_ENGI_WIRES, TRAIT_SILENT_FOOTSTEPS) + vassaldatum.owner.current.add_traits(traits_to_add, VASSAL_TRAIT) + vassaldatum.traits += traits_to_add + vassaldatum.owner.current.update_sight() to_chat(vassaldatum.owner.current, span_notice("Additionally, you can now ventcrawl while naked, and are permanently disfigured. You also have night vision, know how which wires to cut, and have silent footsteps.")) + +/datum/bloodsucker_clan/nosferatu/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) + vassaldatum.owner.current.update_sight() diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm index fe4c4d30aad..8f5fd1ece7a 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm @@ -45,7 +45,7 @@ to_chat(bloodsuckerdatum.owner.current, span_notice("You grow more ancient by the night!")) else // Give them the UI to purchase a power. - var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Select a power you wish to upgrade.", "Your Blood Thickens...", options) + var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Spend [round(blood_cost, 1)] blood to upgrade a power.", "Your Blood Thickens...", options) // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels. if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0) return @@ -72,10 +72,14 @@ finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost) -/datum/bloodsucker_clan/tremere/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) +/datum/bloodsucker_clan/tremere/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) var/datum/action/cooldown/spell/shapeshift/bat/batform = new(vassaldatum.owner || vassaldatum.owner.current) batform.Grant(vassaldatum.owner.current) +/datum/bloodsucker_clan/tremere/favorite_vassal_loss(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) + var/datum/action/cooldown/spell/shapeshift/bat/batform = locate() in vassaldatum.owner.current.actions + batform.Remove(vassaldatum.owner.current) + /datum/bloodsucker_clan/tremere/on_vassal_made(datum/antagonist/bloodsucker/source, mob/living/user, mob/living/target) . = ..() to_chat(bloodsuckerdatum.owner.current, span_danger("You have now gained an additional Rank to spend!")) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm index 441531d42f0..905527e9c93 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm @@ -1,12 +1,12 @@ ///The maximum level a Ventrue Bloodsucker can be, before they have to level up their vassal instead. -#define VENTRUE_MAX_LEVEL 3 +#define VENTRUE_MAX_POWERS 3 ///How much it costs for a Ventrue to rank up without a spare rank to spend. #define BLOODSUCKER_BLOOD_RANKUP_COST (550) /datum/bloodsucker_clan/ventrue name = CLAN_VENTRUE description = "The Ventrue Clan is extremely snobby with their meals, and refuse to drink blood from people without a mind. \n\ - You may only level yourself up to Level %MAX_LEVEL%, anything further will be ranks to spend on their Favorite Vassal through a Persuasion Rack. \n\ + You may have up to %MAX_POWERS% powers, anything further will be ranks to spend on their Favorite Vassal through a Persuasion Rack. \n\ The Favorite Vassal will slowly turn more Vampiric this way, until they finally lose their last bits of Humanity. \n\ Once you finish your embracing, the newly sired vampire will become just a vassal, and you'll be able to sire another bloodsucker." clan_objective = /datum/objective/bloodsucker/embrace @@ -17,19 +17,31 @@ /datum/bloodsucker_clan/ventrue/New(datum/antagonist/bloodsucker/owner_datum) . = ..() - description = replacetext(description, "%MAX_LEVEL%", VENTRUE_MAX_LEVEL) + description = replacetext(description, "%MAX_POWERS%", VENTRUE_MAX_POWERS) + +/datum/bloodsucker_clan/ventrue/proc/has_enough_abilities() + var/power_count = 0 + for(var/datum/action/cooldown/bloodsucker/power in bloodsuckerdatum.powers) + if(!(power.purchase_flags & BLOODSUCKER_DEFAULT_POWER)) + power_count++ + if(power_count >= VENTRUE_MAX_POWERS) + return TRUE + return FALSE /datum/bloodsucker_clan/ventrue/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost) - if(bloodsuckerdatum.bloodsucker_level < VENTRUE_MAX_LEVEL) + if(!target && !has_enough_abilities()) return ..() else if(!target) - to_chat(bloodsuckerdatum.owner.current, span_danger("You can only level up to Level [VENTRUE_MAX_LEVEL], anything further will be ranks to spend on your Favorite Vassal through a Persuasion Rack.")) + to_chat(bloodsuckerdatum.owner.current, span_danger("You can only have up to [VENTRUE_MAX_POWERS] powers, anything further will be ranks to spend on your Favorite Vassal through a Persuasion Rack.")) return FALSE var/datum/antagonist/vassal/favorite/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal/favorite) var/datum/antagonist/vassal/non_favorite = target.mind.has_antag_datum(/datum/antagonist/vassal) if(!vassaldatum && vassaldatum.master != bloodsuckerdatum.owner.current || !IS_BLOODSUCKER(target) && !non_favorite && vassaldatum.master != bloodsuckerdatum.owner.current) target.balloon_alert(target, "is not a sired bloodsucker nor your favorite vassal!") return FALSE + if(!vassaldatum.owner.current.mind) + target.balloon_alert(target, "vassal must be awake!") + return FALSE // Purchase Power Prompt var/list/options = list() for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers) @@ -67,18 +79,19 @@ return vassaldatum.vassal_level++ finish_spend_rank(vassaldatum, cost_rank, blood_cost) + var/traits_to_add = list() switch(vassaldatum.vassal_level) if(1) - target.add_traits(list(TRAIT_COLDBLOODED, TRAIT_NOBREATH, TRAIT_AGEUSIA), BLOODSUCKER_TRAIT) + traits_to_add = list(TRAIT_COLDBLOODED, TRAIT_NOBREATH, TRAIT_AGEUSIA) to_chat(target, span_notice("Your blood begins to feel cold, and as a mote of ash lands upon your tongue, you stop breathing...")) if(2) - target.add_traits(list(TRAIT_NOCRITDAMAGE, TRAIT_NOSOFTCRIT, TRAIT_SLEEPIMMUNE, TRAIT_VIRUSIMMUNE), BLOODSUCKER_TRAIT) + traits_to_add = list(TRAIT_NOCRITDAMAGE, TRAIT_NOSOFTCRIT, TRAIT_SLEEPIMMUNE, TRAIT_VIRUSIMMUNE) to_chat(target, span_notice("You feel your Master's blood reinforce you, strengthening you up.")) if(ishuman(target)) var/mob/living/carbon/human/human_target = target human_target.skin_tone = "albino" if(3) - target.add_traits(list(TRAIT_NOHARDCRIT, TRAIT_HARDLY_WOUNDED), BLOODSUCKER_TRAIT) + traits_to_add = list(TRAIT_NOHARDCRIT, TRAIT_HARDLY_WOUNDED) to_chat(target, span_notice("You feel yourself able to take cuts and stabbings like it's nothing.")) if(4 to INFINITY) var/datum/antagonist/bloodsucker/bloodsucker_target = target.mind.has_antag_datum(/datum/antagonist/bloodsucker) @@ -89,18 +102,18 @@ var/powers_to_transfer = list() // Get rid of the favorite datum and replace with a normal vassal datum if(target.mind.has_antag_datum(/datum/antagonist/vassal/favorite)) - var/datum/antagonist/vassal/new_antag_datum = new(target.mind) - powers_to_transfer = vassaldatum.bloodsucker_powers.Copy() - vassaldatum.bloodsucker_powers.Cut(powers_to_transfer) + for(var/datum/power as anything in vassaldatum.bloodsucker_powers) + powers_to_transfer += power.type target.mind.remove_antag_datum(/datum/antagonist/vassal/favorite) - new_antag_datum.master = vassaldatum.master - new_antag_datum.silent = TRUE - target.mind.add_antag_datum(new_antag_datum) - new_antag_datum.remove_powers(new_antag_datum.powers) - bloodsucker_target = target.mind.add_antag_datum(/datum/antagonist/bloodsucker) - bloodsucker_target?.powers += powers_to_transfer + var/datum/antagonist/bloodsucker/vamp = new() + vamp.ventrue_sired = bloodsuckerdatum + bloodsucker_target = target.mind.add_antag_datum(vamp) + bloodsucker_target.BuyPowers(powers_to_transfer) // Check for the recuperate power and remove it if they have it bloodsuckerdatum.owner.current.add_mood_event("madevamp", /datum/mood_event/madevamp) + if(vassaldatum && QDELETED(vassaldatum) && length(traits_to_add)) + target.add_traits(traits_to_add, VASSAL_TRAIT) + vassaldatum.traits += traits_to_add /datum/bloodsucker_clan/ventrue/proc/finish_spend_rank(datum/antagonist/vassal/vassaldatum, cost_rank, blood_cost) finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost) @@ -112,9 +125,6 @@ return TRUE if(!istype(vassaldatum)) return FALSE - if(bloodsuckerdatum.bloodsucker_level < VENTRUE_MAX_LEVEL) - to_chat(bloodsuckerdatum.owner.current, span_danger("You are too low level to Rank up [vassaldatum.owner.current] You must at least be level [VENTRUE_MAX_LEVEL].")) - return FALSE if(!bloodsuckerdatum.bloodsucker_level_unspent <= 0) bloodsuckerdatum.SpendRank(vassaldatum.owner.current) return TRUE @@ -132,9 +142,9 @@ to_chat(bloodsuckerdatum.owner.current, span_danger("You don't have any levels or enough Blood to Rank [vassaldatum.owner.current] up with.")) return TRUE -/datum/bloodsucker_clan/ventrue/on_favorite_vassal(datum/source, datum/antagonist/vassal/vassaldatum, mob/living/bloodsucker) - to_chat(bloodsucker, span_announce("* Bloodsucker Tip: You can now upgrade your Favorite Vassal by buckling them onto a persuasion rack!")) +/datum/bloodsucker_clan/ventrue/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) + to_chat(source.owner.current, span_announce("* Bloodsucker Tip: You can now upgrade your Favorite Vassal by buckling them onto a persuasion rack!")) vassaldatum.BuyPower(/datum/action/cooldown/bloodsucker/distress) #undef BLOODSUCKER_BLOOD_RANKUP_COST -#undef VENTRUE_MAX_LEVEL +#undef VENTRUE_MAX_POWERS diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm index 01919b0038e..b13f7775d6a 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm @@ -17,7 +17,6 @@ base_background_icon_state = "vamp_power_off" /// The text that appears when using the help verb, meant to explain how the Power changes when ranking up. - var/power_explanation = "" ///The owner's stored Bloodsucker datum var/datum/antagonist/bloodsucker/bloodsuckerdatum_power @@ -25,14 +24,14 @@ /// The effects on this Power (Toggled/Single Use/Static Cooldown) var/power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN|BP_AM_COSTLESS_UNCONSCIOUS /// Requirement flags for checks - check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS + check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS /// Who can purchase the Power var/purchase_flags = NONE // BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER|TREMERE_CAN_BUY|VASSAL_CAN_BUY // VARS // /// If the Power is currently active, differs from action cooldown because of how powers are handled. var/active = FALSE - ///Can increase to yield new abilities - Each Power ranks up each Rank + ///Can increase to yield new capabilities - Each Power ranks up each Rank, with the oldest power being the highest rank. var/level_current = 0 ///The cost to ACTIVATE this Power var/bloodcost = 0 @@ -102,6 +101,13 @@ ///Called when the Power is upgraded. /datum/action/cooldown/bloodsucker/proc/upgrade_power() level_current++ + build_all_button_icons(UPDATE_BUTTON_NAME) + +// Put desc that you want to update every time build_all_button_icons is called here +/datum/action/cooldown/bloodsucker/update_button_name(atom/movable/screen/movable/action_button/button, force) + . = ..() + if((purchase_flags & BLOODSUCKER_CAN_BUY) || (purchase_flags & TREMERE_CAN_BUY)) + button.desc += "

LEVEL: [level_current]" ///Checks if the Power is available to use. /datum/action/cooldown/bloodsucker/proc/can_use(mob/living/carbon/user, trigger_flags) @@ -109,6 +115,12 @@ return FALSE if(!isliving(user)) return FALSE + if(bloodsuckerdatum_power && !bloodsuckerdatum_power.heart?.resolve()) + to_chat(user, span_warning("To channel your powers you need a heart!")) + return FALSE + if(isbrain(user)) + to_chat(user, span_warning("What are you going to do, jump on someone and suck their blood? You're just a head.")) + return FALSE // Torpor? if((check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(user, TRAIT_NODEATH)) to_chat(user, span_warning("Not while you're in Torpor.")) @@ -121,11 +133,11 @@ to_chat(user, span_warning("You cannot use powers while in a Frenzy!")) return FALSE // Stake? - if((check_flags & BP_CANT_USE_WHILE_STAKED) && user.am_staked()) + if(!(check_flags & BP_CAN_USE_WHILE_STAKED) && user.am_staked()) to_chat(user, span_warning("You have a stake in your chest! Your powers are useless.")) return FALSE // Conscious? -- We use our own (AB_CHECK_CONSCIOUS) here so we can control it more, like the error message. - if((check_flags & BP_CANT_USE_WHILE_UNCONSCIOUS) && user.stat != CONSCIOUS) + if((check_flags & BP_CANT_USE_WHILE_UNCONSCIOUS) && user.stat >= UNCONSCIOUS) to_chat(user, span_warning("You can't do this while you are unconcious!")) return FALSE // Incapacitated? @@ -133,13 +145,16 @@ to_chat(user, span_warning("Not while you're incapacitated!")) return FALSE // Constant Cost (out of blood) - if(constant_bloodcost > 0 && bloodsuckerdatum_power?.bloodsucker_blood_volume <= 0) + if(constant_bloodcost > 0 && !can_pay_blood(user)) to_chat(user, span_warning("You don't have the blood to upkeep [src].")) return FALSE return TRUE /// NOTE: With this formula, you'll hit half cooldown at level 8 for that power. -/datum/action/cooldown/bloodsucker/StartCooldown() +/datum/action/cooldown/bloodsucker/StartCooldown(override_cooldown_time, override_melee_cooldown_time) + // don't re-set the cooldown if we don't have a override time and there's already a cooldown ongoing + if(!override_cooldown_time && next_use_time > world.time) + return // Calculate Cooldown (by power's level) if(power_flags & BP_AM_STATIC_COOLDOWN) cooldown_time = initial(cooldown_time) @@ -148,6 +163,9 @@ return ..() +/datum/action/cooldown/bloodsucker/proc/can_pay_blood(mob/living/carbon/user) + return bloodsuckerdatum_power ? bloodsuckerdatum_power?.bloodsucker_blood_volume >= 0 : user.blood_volume >= 0 + /datum/action/cooldown/bloodsucker/proc/can_deactivate() return TRUE @@ -164,8 +182,7 @@ // Bloodsuckers in a Frenzy don't have enough Blood to pay it, so just don't. if(bloodsuckerdatum_power.frenzied) return - bloodsuckerdatum_power.bloodsucker_blood_volume -= bloodcost - bloodsuckerdatum_power.update_hud() + bloodsuckerdatum_power.AdjustBloodVolume(-bloodcost) /datum/action/cooldown/bloodsucker/proc/ActivatePower(trigger_flags) active = TRUE @@ -200,7 +217,7 @@ if(power_flags & BP_AM_COSTLESS_UNCONSCIOUS && owner.stat != CONSCIOUS) return TRUE if(bloodsuckerdatum_power) - bloodsuckerdatum_power.AddBloodVolume(-constant_bloodcost * seconds_per_tick) + bloodsuckerdatum_power.AdjustBloodVolume(-constant_bloodcost * seconds_per_tick) else var/mob/living/living_owner = owner if(!HAS_TRAIT(living_owner, TRAIT_NOBLOOD)) @@ -211,7 +228,7 @@ /datum/action/cooldown/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target) if(!user) return FALSE - if(!constant_bloodcost > 0 || bloodsuckerdatum_power.bloodsucker_blood_volume > 0) + if(!constant_bloodcost > 0 || can_pay_blood(user)) return TRUE /// Used to unlearn Single-Use Powers diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm index 60df4447a9d..6d83cfe5e80 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm @@ -15,8 +15,8 @@ If you are in desperate need of blood, mice can be fed off of, at a cost.\n\ You must use the ability again to stop sucking blood." power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN - check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS - purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER + check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS|BP_CAN_USE_WHILE_STAKED + purchase_flags = BLOODSUCKER_DEFAULT_POWER bloodcost = 0 cooldown_time = 15 SECONDS @@ -69,8 +69,8 @@ bloodsuckerdatum_power.AddHumanityLost(5) target_ref = null - warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL - blood_taken = 0 + warning_target_bloodvol = initial(warning_target_bloodvol) + blood_taken = initial(blood_taken) REMOVE_TRAIT(user, TRAIT_IMMOBILIZED, FEED_TRAIT) REMOVE_TRAIT(user, TRAIT_MUTE, FEED_TRAIT) return ..() @@ -84,7 +84,7 @@ var/mob/living/user = owner user.add_mood_event("drankblood", /datum/mood_event/drankblood_bad) bloodsuckerdatum_power.AddHumanityLost(1) - bloodsuckerdatum_power.AddBloodVolume(25) + bloodsuckerdatum_power.AdjustBloodVolume(25) DeactivatePower() feed_target.death() return @@ -111,7 +111,7 @@ // Only people who AREN'T the target will notice this action. var/dead_message = feed_target.stat != DEAD ? " [feed_target.p_they(TRUE)] looks dazed, and will not remember this." : "" owner.visible_message( - span_notice("[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth."), \ + span_warning("[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth."), \ span_notice("You slip your fangs into [feed_target]'s wrist.[dead_message]"), \ vision_distance = FEED_NOTICE_RANGE, ignored_mobs = feed_target) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/fortitude.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/fortitude.dm index 82a34868067..002c18419af 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/fortitude.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/fortitude.dm @@ -4,7 +4,7 @@ button_icon_state = "power_fortitude" power_explanation = "Fortitude:\n\ Activating Fortitude will provide pierce, stun and dismember immunity.\n\ - You will additionally gain resistance to Brute and Stamina damge, scaling with level.\n\ + You will additionally gain resistance to both physical and stamina damage, scaling with level.\n\ While using Fortitude, attempting to run will crush you.\n\ At level 4, you gain complete stun immunity.\n\ Higher levels will increase Brute and Stamina resistance." @@ -29,6 +29,7 @@ if(IS_BLOODSUCKER(owner) || IS_VASSAL(owner)) fortitude_resist = max(0.3, 0.7 - level_current * 0.1) bloodsucker_user.physiology.brute_mod *= fortitude_resist + bloodsucker_user.physiology.burn_mod *= fortitude_resist + 0.2 bloodsucker_user.physiology.stamina_mod *= fortitude_resist was_running = (bloodsucker_user.move_intent == MOVE_INTENT_RUN) @@ -58,8 +59,8 @@ var/mob/living/carbon/human/bloodsucker_user = owner if(IS_BLOODSUCKER(owner) || IS_VASSAL(owner)) bloodsucker_user.physiology.brute_mod /= fortitude_resist - if(!HAS_TRAIT_FROM(bloodsucker_user, TRAIT_STUNIMMUNE, BLOODSUCKER_TRAIT)) - bloodsucker_user.physiology.stamina_mod /= fortitude_resist + bloodsucker_user.physiology.burn_mod /= fortitude_resist + 0.2 + bloodsucker_user.physiology.stamina_mod /= fortitude_resist // Remove Traits & Effects owner.remove_traits(list(TRAIT_PIERCEIMMUNE, TRAIT_NODISMEMBER, TRAIT_PUSHIMMUNE, TRAIT_STUNIMMUNE), BLOODSUCKER_TRAIT) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/gohome.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/gohome.dm index 5b55a8ec9c6..88bc01f8b3f 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/gohome.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/gohome.dm @@ -20,11 +20,12 @@ Immediately after activating, lights around the user will begin to flicker. \n\ Once the user teleports to their coffin, in their place will be a Rat or Bat." power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN - check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED + check_flags = BP_CANT_USE_IN_FRENZY purchase_flags = NONE bloodcost = 100 constant_bloodcost = 2 cooldown_time = 100 SECONDS + ///What stage of the teleportation are we in var/teleporting_stage = GOHOME_START ///The types of mobs that will drop post-teleportation. diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/masquerade.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/masquerade.dm index 74f02cc22f3..ff13b9b7f15 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/masquerade.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/masquerade.dm @@ -16,14 +16,14 @@ button_icon_state = "power_human" power_explanation = "Masquerade:\n\ Activating Masquerade will forge your identity to be practically identical to that of a human;\n\ - - You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus and cold immunity.\n\ + - You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus, gutting and cold immunity.\n\ - Your eyes turn to that of a regular human as your heart begins to beat.\n\ - You gain a Genetic sequence, and appear to have 100% blood when scanned by a Health Analyzer.\n\ - You will not appear as Pale when examined. Anything further than Pale, however, will not be hidden.\n\ - At the end of a Masquerade, you will re-gain your Vampiric abilities, as well as lose any Disease & Gene you might have." + At the end of a Masquerade, you will re-gain your Vampiric abilities, as well as lose any diseases you might have." power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN|BP_AM_COSTLESS_UNCONSCIOUS check_flags = BP_CANT_USE_IN_FRENZY - purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER + purchase_flags = BLOODSUCKER_DEFAULT_POWER bloodcost = 10 cooldown_time = 5 SECONDS constant_bloodcost = 0.1 @@ -40,7 +40,11 @@ // Handle Traits user.remove_traits(bloodsuckerdatum_power.bloodsucker_traits, BLOODSUCKER_TRAIT) + ADD_TRAIT(user, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT) + var/obj/item/bodypart/chest/target_chest = user.get_bodypart(BODY_ZONE_CHEST) + if(target_chest) + target_chest.bodypart_flags &= ~BODYPART_UNREMOVABLE // Handle organs var/obj/item/organ/internal/heart/vampheart = user.get_organ_slot(ORGAN_SLOT_HEART) if(vampheart) @@ -52,6 +56,7 @@ eyes.sight_flags = initial(eyes.sight_flags) user.update_sight() +/// todo, make bloodsuckerification into it's own proc, ie, eyes, traits, and such /datum/action/cooldown/bloodsucker/masquerade/DeactivatePower() . = ..() // activate = FALSE var/mob/living/carbon/user = owner @@ -59,14 +64,15 @@ // Remove status effect, mutations & diseases that you got while on masq. user.remove_status_effect(/datum/status_effect/masquerade) - user.dna.remove_all_mutations() for(var/datum/disease/diseases as anything in user.diseases) diseases.cure() // Handle Traits user.add_traits(bloodsuckerdatum_power.bloodsucker_traits, BLOODSUCKER_TRAIT) REMOVE_TRAIT(user, TRAIT_MASQUERADE, BLOODSUCKER_TRAIT) - + var/obj/item/bodypart/chest/target_chest = user.get_bodypart(BODY_ZONE_CHEST) + if(target_chest) + target_chest.bodypart_flags |= BODYPART_UNREMOVABLE // Handle organs var/obj/item/organ/internal/heart/vampheart = user.get_organ_slot(ORGAN_SLOT_HEART) if(vampheart) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/haste.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/haste.dm index 55be6a9ccb3..565aa001e8f 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/haste.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/haste.dm @@ -5,10 +5,11 @@ /datum/action/cooldown/bloodsucker/targeted/haste name = "Immortal Haste" - desc = "Dash somewhere with supernatural speed. Those nearby may be knocked away, stunned, or left empty-handed." + desc = "Force yourself to stand up if you're down and dash somewhere with supernatural speed. Those nearby may be knocked away, stunned, or left empty-handed." button_icon_state = "power_speed" power_explanation = "Immortal Haste:\n\ Click anywhere to immediately dash towards that location.\n\ + At level 3, if you are lying down, you will get up and regain your stamina, but the resulting dash will not knock down those nearby.\n\ The Power will not work if you are lying down, in no gravity, or are aggressively grabbed.\n\ Anyone in your way during your Haste will be knocked down.\n\ Higher levels will increase the knockdown dealt to enemies.\n\ @@ -34,8 +35,8 @@ if(!user.has_gravity(user.loc)) //We dont want people to be able to use this to fly around in space user.balloon_alert(user, "you cannot dash while floating!") return FALSE - if(user.body_position == LYING_DOWN) - user.balloon_alert(user, "you must be standing to tackle!") + if(level_current < 3 && user.body_position == LYING_DOWN) + user.balloon_alert(user, "you must be standing to dash!") return FALSE return TRUE @@ -49,8 +50,17 @@ /// This is a non-async proc to make sure the power is "locked" until this finishes. /datum/action/cooldown/bloodsucker/targeted/haste/FireTargetedPower(atom/target_atom) . = ..() - RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) var/mob/living/user = owner + var/stuns_mobs = TRUE + if(level_current >= 3 && user.body_position == LYING_DOWN) + to_chat(user, span_danger("Your heart takes a beat, and you force yourself to stand up!")) + user.SetKnockdown(0) + user.setStaminaLoss(0) + user.set_resting(FALSE, FALSE, TRUE) + stuns_mobs = FALSE + StartCooldown(cooldown_time * 3) + if(stuns_mobs) + RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) var/turf/targeted_turf = isturf(target_atom) ? target_atom : get_turf(target_atom) // Pulled? Not anymore. user.pulledby?.stop_pulling() diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm index 49afcd2fd5c..628b74a2364 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/targeted/mesmerize.dm @@ -113,7 +113,7 @@ /*if(IS_MONSTERHUNTER(mesmerized_target)) to_chat(mesmerized_target, span_notice("You feel your eyes burn for a while, but it passes.")) return*/ - if(HAS_TRAIT_FROM(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT)) + if(HAS_TRAIT_FROM_ONLY(mesmerized_target, TRAIT_MUTE, BLOODSUCKER_TRAIT)) owner.balloon_alert(owner, "[mesmerized_target] is already in a hypnotic gaze.") return if(iscarbon(mesmerized_target)) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm index 8432fa35e7d..2592a82cf5f 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/dominate.dm @@ -68,12 +68,12 @@ name = "Level 4: Possession" upgraded_power = /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/two level_current = 4 - desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged to temporary Vassals." + desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged or dead to temporary Vassals." power_explanation = "Level 4: Possession:\n\ Click any person to, after a 4 second timer, Mesmerize them.\n\ This will completely immobilize, mute, and blind them for the next 13.5 seconds.\n\ However, while adjacent to the target, if your target is in critical condition or dead, they will instead be turned into a temporary Vassal.\n\ - If you use this on a currently dead normal Vassal, you will instead revive them normally.\n\ + If you use this on a currently dead normal Vassal, they will will not suddenly cease to live as if a temporary Vassal.\n\ Despite being Mute and Deaf, they will still have complete loyalty to you, until their death in 5 minutes upon use." background_icon_state = "tremere_power_gold_off" active_background_icon_state = "tremere_power_gold_on" @@ -83,14 +83,14 @@ /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/advanced/two name = "Level 5: Possession" - desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged to temporary Vassals." + desc = "Mesmerize, mute and blind any foe who stands still long enough, or convert the damaged or dead to temporary Vassals." level_current = 5 upgraded_power = null power_explanation = "Level 5: Possession:\n\ Click any person to, after a 4 second timer, Mesmerize them.\n\ This will completely immobilize, mute, and blind them for the next 13.5 seconds.\n\ However, while adjacent to the target, if your target is in critical condition or dead, they will instead be turned into a temporary Vassal.\n\ - If you use this on a currently dead normal Vassal, you will instead revive them normally.\n\ + If you use this on a currently dead normal Vassal, they will will not suddenly cease to live as if a temporary Vassal.\n\ They will have complete loyalty to you, until their death in 8 minutes upon use." bloodcost = 100 cooldown_time = 2 MINUTES @@ -110,8 +110,11 @@ . = ..() var/mob/living/target = target_atom var/mob/living/user = owner - if(target.stat >= SOFT_CRIT && user.Adjacent(target) && level_current >= 4) - attempt_vassalize(target, user) + if(target.stat != CONSCIOUS && level_current >= 4) + if(user.Adjacent(target)) + attempt_vassalize(target, user) + else + owner.balloon_alert(owner, "too far to vassalize!") return else if(IS_VASSAL(target)) owner.balloon_alert(owner, "vassal cant be revived") @@ -128,7 +131,8 @@ /*if(IS_MONSTERHUNTER(target)) to_chat(target, span_notice("You feel you something crawling under your skin, but it passes.")) return*/ - if(HAS_TRAIT_FROM(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)) + // todo replace with status effect so we don't only check chainstunning above lvl 2 + if(HAS_TRAIT_FROM_ONLY(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)) owner.balloon_alert(owner, "[target] is already in some form of hypnotic gaze.") return if(iscarbon(target)) @@ -155,37 +159,50 @@ owner.balloon_alert(owner, "[target] snapped out of their trance.") /datum/action/cooldown/bloodsucker/targeted/tremere/dominate/proc/attempt_vassalize(mob/living/target, mob/living/user) + var/datum/antagonist/vassal/is_vassal = IS_VASSAL(target) + if(!bloodsuckerdatum_power.can_make_vassal(target)) + owner.balloon_alert(owner, "not a valid target for vassalization!.") + return FALSE + owner.balloon_alert(owner, "attempting to vassalize.") if(!do_after(user, 6 SECONDS, target, NONE, TRUE)) - return + return FALSE - if(IS_VASSAL(target)) + if(is_vassal?.master == bloodsuckerdatum_power) + if(target.stat != DEAD) + owner.balloon_alert(owner, "not dead!") + return FALSE power_activated_sucessfully() to_chat(user, span_warning("We revive [target]!")) + owner.balloon_alert(owner, "successfully revived!") target.mind.grab_ghost() target.revive(ADMIN_HEAL_ALL) + return FALSE + + if(!bloodsuckerdatum_power.make_vassal(target)) + owner.balloon_alert(owner, "not a valid target for vassalization!.") return + /*if(IS_MONSTERHUNTER(target)) to_chat(target, span_notice("Their body refuses to react...")) return*/ - if(!bloodsuckerdatum_power.make_vassal(target)) - return power_activated_sucessfully() to_chat(user, span_warning("We revive [target]!")) target.mind.grab_ghost() target.revive(ADMIN_HEAL_ALL) var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal) vassaldatum.special_type = TREMERE_VASSAL //don't turn them into a favorite please - var/living_time - if(level_current == 4) - living_time = 5 MINUTES - target.add_traits(list(TRAIT_MUTE, TRAIT_DEAF), BLOODSUCKER_TRAIT) - else if(level_current == 5) - living_time = 8 MINUTES + var/living_time = (1 MINUTES) * level_current + if(level_current <= 4) + target.add_traits(list(TRAIT_MUTE, TRAIT_DEAF), DOMINATE_TRAIT) addtimer(CALLBACK(src, PROC_REF(end_possession), target), living_time) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(end_possession)) + return TRUE /datum/action/cooldown/bloodsucker/targeted/tremere/proc/end_possession(mob/living/user) - user.remove_traits(list(TRAIT_MUTE, TRAIT_DEAF), BLOODSUCKER_TRAIT) + if(!IS_VASSAL(user) && !HAS_TRAIT_FROM_ONLY(user, TRAIT_MUTE, DOMINATE_TRAIT) && !HAS_TRAIT_FROM_ONLY(user, TRAIT_MUTE, DOMINATE_TRAIT)) + return + user.remove_traits(list(TRAIT_MUTE, TRAIT_DEAF), DOMINATE_TRAIT) user.mind.remove_antag_datum(/datum/antagonist/vassal) to_chat(user, span_warning("You feel the Blood of your Master run out!")) user.death() diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm index dd7f0b1679c..2cdf85ce5f9 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm @@ -153,7 +153,7 @@ if(bloodsucker_power.level_current >= 5) var/mob/living/person_hit = target person_hit.blood_volume -= 60 - bloodsucker_power.bloodsuckerdatum_power.AddBloodVolume(60) + bloodsucker_power.bloodsuckerdatum_power.AdjustBloodVolume(60) qdel(src) return BULLET_ACT_HIT . = ..() @@ -182,5 +182,5 @@ /obj/item/shield/bloodsucker/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(bloodsuckerdatum) - bloodsuckerdatum.AddBloodVolume(-15) + bloodsuckerdatum.AdjustBloodVolume(-15) return ..() diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm index 70ec928ce20..2ae1d2893a9 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/vassal/recuperate.dm @@ -7,9 +7,9 @@ Activating this Power will begin to heal your wounds.\n\ You will heal Brute and Toxin damage, at the cost of Stamina damage, and blood from both you and your Master.\n\ If you aren't a bloodless race, you will additionally heal Burn damage.\n\ - The power will cancel out if you are incapacitated or dead." + The power will cancel out if you are dead or unconcious." power_flags = BP_AM_TOGGLE - check_flags = BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS + check_flags = BP_CANT_USE_WHILE_UNCONSCIOUS purchase_flags = NONE bloodcost = 1.5 cooldown_time = 10 SECONDS @@ -38,7 +38,7 @@ return var/mob/living/carbon/user = owner var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(user) - vassaldatum.master.AddBloodVolume(-1) + vassaldatum.master.AdjustBloodVolume(-1) user.set_timed_status_effect(5 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) user.adjustStaminaLoss(bloodcost * 1.1) user.adjustBruteLoss(-2.5) @@ -55,7 +55,7 @@ /datum/action/cooldown/bloodsucker/recuperate/ContinueActive(mob/living/user, mob/living/target) if(user.stat >= DEAD) return FALSE - if(user.incapacitated()) + if(user.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)) owner.balloon_alert(owner, "too exhausted...") return FALSE return TRUE diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/veil.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/veil.dm index bd749cea23d..725b5432ea7 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/veil.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/veil.dm @@ -7,7 +7,7 @@ Your name and appearance will be completely randomized, and turning the ability off again will undo it all.\n\ Clothes, gear, and Security/Medical HUD status is kept the same while this power is active." power_flags = BP_AM_TOGGLE - check_flags = BP_CANT_USE_IN_FRENZY + check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_UNCONSCIOUS purchase_flags = BLOODSUCKER_DEFAULT_POWER bloodcost = 15 constant_bloodcost = 0.1 diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm index eda2f9c8eb5..d4565ca6700 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm @@ -29,6 +29,7 @@ if(user == resident) . += span_cult("This is your Claimed Coffin.") . += span_cult("Rest in it while injured to enter Torpor. Entering it with unspent Ranks will allow you to spend one.") + . += span_cult("Going inside while it contains a heart will put it in your chest, letting you regain your might.") . += span_cult("Alt-Click while inside the Coffin to Lock/Unlock.") . += span_cult("Alt-Click while outside of your Coffin to Unclaim it, unwrenching it and all your other structures as a result.") @@ -133,6 +134,8 @@ resident = claimant anchored = TRUE START_PROCESSING(SSprocessing, src) + return TRUE + return FALSE /obj/structure/closet/crate/coffin/Destroy() unclaim_coffin() @@ -168,6 +171,7 @@ anchored = FALSE if(!resident || !resident.mind) return + un_enlarge(resident) // Unclaiming var/datum/antagonist/bloodsucker/bloodsuckerdatum = resident.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(bloodsuckerdatum && bloodsuckerdatum.coffin == src) @@ -198,33 +202,61 @@ /obj/structure/closet/crate/coffin/close(mob/living/user) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = user?.mind?.has_antag_datum(/datum/antagonist/bloodsucker) + if(bloodsuckerdatum && user.mob_size > max_mob_size) + if(!HAS_TRAIT_FROM_ONLY(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin")) + if(prompt_coffin_claim(bloodsuckerdatum)) + enlarge(user) + else + user.balloon_alert(user, "already claimed by another!") . = ..() if(!.) return FALSE + for(var/atom/thing as anything in contents) + SEND_SIGNAL(thing, COMSIG_ENTER_COFFIN, src, user) // Only the User can put themself into Torpor. If already in it, you'll start to heal. if(user in src) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(!bloodsuckerdatum) + if(!resident && !prompt_coffin_claim(bloodsuckerdatum)) return FALSE - var/area/current_area = get_area(src) - if(!bloodsuckerdatum.coffin && !resident) - switch(tgui_alert(user, "Do you wish to claim this as your coffin? [current_area] will be your lair.", "Claim Lair", list("Yes", "No"))) - if("Yes") - claim_coffin(user, current_area) - if("No") - return LockMe(user) //Level up if possible. if(!bloodsuckerdatum.my_clan) + user.balloon_alert("enter a clan!") to_chat(user, span_notice("You must enter a Clan to rank up. Do it in the antag menu, which you can see by pressing the action button in the top left.")) else // Level ups cost 30% of your max blood volume, which scales with your rank. - bloodsuckerdatum.SpendRank(blood_cost = bloodsuckerdatum.max_blood_volume * BLOODSUCKER_LEVELUP_PERCENTAGE) - // You're in a Coffin, everything else is done, you're likely here to heal. Let's offer them the oppertunity to do so. - bloodsuckerdatum.check_begin_torpor() + if(!bloodsuckerdatum.frenzied) + bloodsuckerdatum.SpendRank(blood_cost = bloodsuckerdatum.max_blood_volume * BLOODSUCKER_LEVELUP_PERCENTAGE) + // You're in a Coffin, everything else is done, you're likely here to heal. Let's offer them the opportunity to do so. + bloodsuckerdatum.check_begin_torpor(TORPOR_SKIP_CHECK_DAMAGE) return TRUE -#undef BLOODSUCKER_LEVELUP_PERCENTAGE +/obj/structure/closet/crate/coffin/proc/prompt_coffin_claim(datum/antagonist/bloodsucker/dracula) + if(!dracula) + return FALSE + var/area/current_area = get_area(src) + if(!dracula.coffin && !resident) + switch(tgui_alert(dracula.owner.current, "Do you wish to claim this as your coffin? [current_area] will be your lair.", "Claim Lair", list("Yes", "No"))) + if("Yes") + return claim_coffin(dracula.owner.current, current_area) + return FALSE + +// some fatass bloodsucker is trying to fit in a too-small coffin, how about we make some room? +/obj/structure/closet/crate/proc/enlarge(mob/living/user) + ADD_TRAIT(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin") + max_mob_size = user.mob_size + to_chat(user, span_warning("The coffin creaks and squeaks as you try to squeeze into it. It's a tight fit but you manage it make it fit you.")) + playsound(src, 'modular_skyrat/modules/aesthetics/airlock/sound/creaking.ogg') + animate(src, 1 SECONDS, FALSE, BOUNCE_EASING, transform = transform.Scale(user.mob_size * COFFIN_ENLARGE_MULT)) + +/obj/structure/closet/crate/proc/un_enlarge(mob/living/user) + if(!HAS_TRAIT_FROM_ONLY(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin")) + return + REMOVE_TRAIT(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin") + max_mob_size = initial(max_mob_size) + var/matrix/normal + // transform.Scale(user.mob_size * (COFFIN_ENLARGE_MULT + 1) + animate(src, 1 SECONDS, FALSE, transform = normal) /// You cannot weld or deconstruct an owned coffin. Only the owner can destroy their own coffin. /obj/structure/closet/crate/coffin/attackby(obj/item/item, mob/user, params) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm index 01e71b27e1e..fa5fbc74bb6 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm @@ -296,10 +296,10 @@ // Conversion Process if(convert_progress) balloon_alert(user, "spilling blood...") - bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST) + bloodsuckerdatum.AdjustBloodVolume(-TORTURE_BLOOD_HALF_COST) if(!do_torture(user, target)) return FALSE - bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST) + bloodsuckerdatum.AdjustBloodVolume(-TORTURE_BLOOD_HALF_COST) // Prevent them from unbuckling themselves as long as we're torturing. target.Paralyze(1 SECONDS) convert_progress-- @@ -329,7 +329,7 @@ balloon_alert(user, "interrupted!") return // Convert to Vassal! - bloodsuckerdatum.AddBloodVolume(-TORTURE_CONVERSION_COST) + bloodsuckerdatum.AdjustBloodVolume(-TORTURE_CONVERSION_COST) if(bloodsuckerdatum.make_vassal(target)) remove_loyalties(target) SEND_SIGNAL(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, user, target) @@ -381,7 +381,9 @@ /obj/structure/bloodsucker/vassalrack/proc/do_disloyalty(mob/living/user, mob/living/target) if(disloyalty_offered) return FALSE - + // Can't willingly join if you're banned from it. It'll just ghost you anyways. + if(is_banned_from(target.ckey, ROLE_BLOODSUCKER)) + return TRUE disloyalty_offered = TRUE to_chat(user, span_notice("[target] has been given the opportunity for servitude. You await their decision...")) var/alert_response = tgui_alert( diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/objects.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/objects.dm index 5a1a40dc1e5..c8f621cbcd1 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/objects.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/objects.dm @@ -124,9 +124,15 @@ custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 3) ///Time it takes to embed the stake into someone's chest. - var/staketime = 12 SECONDS + var/staketime = 5 SECONDS var/kills_blodsuckers = FALSE +/obj/item/stake/examine_more(mob/user) + . = ..() + . += span_notice("You can use [src] to stake someone in the chest, if they are laying down or grabbed by the neck.") + if(IS_BLOODSUCKER(user)) + . += span_warning("You feel a sense of dread as you look at the [src]...") + /obj/item/stake/attack(mob/living/target, mob/living/user, params) . = ..() if(.) @@ -161,7 +167,7 @@ return var/datum/antagonist/bloodsucker/bloodsuckerdatum = target.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(bloodsuckerdatum) - // If DEAD or TORPID... Kill Bloodsucker! + // If silver stake and DEAD or TORPOR... Kill the Bloodsucker! if(target.StakeCanKillMe()) bloodsuckerdatum.FinalDeath() else @@ -173,7 +179,7 @@ return FALSE /mob/living/carbon/can_be_staked() - if(!(mobility_flags & MOBILITY_MOVE)) + if(body_position == LYING_DOWN) return TRUE return FALSE @@ -185,8 +191,12 @@ force = 8 throwforce = 12 armour_penetration = 10 - embedding = list("embed_chance" = 35) - staketime = 10 SECONDS + embedding = list("embed_chance" = 35, "fall_chance" = 0) + staketime = 12 SECONDS + +/obj/item/stake/hardened/examine_more(mob/user) + . = ..() + . += span_notice("The [src] won't fall out by itself, if embedded in someone.") /obj/item/stake/hardened/silver name = "silver stake" @@ -197,10 +207,15 @@ force = 9 armour_penetration = 25 custom_materials = list(/datum/material/silver = SHEET_MATERIAL_AMOUNT) - embedding = list("embed_chance" = 65) - staketime = 8 SECONDS + embedding = list("embed_chance" = 65, "fall_chance" = 0) + staketime = 15 SECONDS kills_blodsuckers = TRUE +/obj/item/stake/hardened/silver/examine_more(mob/user) + . = ..() + if(HAS_TRAIT(user.mind, TRAIT_BLOODSUCKER_HUNTER)) + . += span_notice("You know that the [src] can cause a Final Death to a vile Bloodsucker if they are asleep or dead.") + ////////////////////// // ARCHIVES // ////////////////////// @@ -233,13 +248,6 @@ COOLDOWN_DECLARE(bloodsucker_check_cooldown) var/cooldown_time = 1 MINUTES -/obj/item/book/kindred/station_loving - -/obj/item/book/kindred/station_loving/Initialize() - . = ..() - SSpoints_of_interest.make_point_of_interest(src) - AddComponent(/datum/component/stationloving, FALSE, TRUE) - /obj/item/book/kindred/try_carve(obj/item/carving_item, mob/living/user, params) to_chat(user, span_notice("You feel the gentle whispers of a Librarian telling you not to cut [starting_title].")) return FALSE @@ -304,5 +312,22 @@ /obj/structure/displaycase/curator desc = "This book was found inside a coffin of a long dead Curator. It is said to be able to reveal the true nature of those who feed upon mankind." - start_showpiece_type = /obj/item/book/kindred/station_loving + start_showpiece_type = /obj/item/book/kindred req_access = list(ACCESS_LIBRARY) + + +/// just a typepath to specify that it's monkey-owned, used for the heart thief objective +/obj/item/organ/internal/heart/monkey + +/obj/item/organ/internal/heart/examine_more(mob/user) + . = ..() + var/datum/antagonist/bloodsucker/vampire = IS_BLOODSUCKER(user) + if(!vampire) + return + var/datum/objective/steal_n_of_type/heart_thief = locate() in vampire?.objectives + if(!heart_thief) + return + if(heart_thief.check_if_valid_item(src)) + . += span_notice("This [src.name] will do for your purposes...") + else + . += span_notice("This [src.name] is of lesser quality, it won't do...") diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/batform.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/batform.dm index ae4b172d6de..7d0b2d495c5 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/batform.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/batform.dm @@ -6,14 +6,14 @@ /datum/action/cooldown/spell/shapeshift/bat name = "Bat Form" desc = "Take on the shape of a space bat. You can fly and see in the dark, and you heal from doing melee attacks." + power_explanation = "You can fly and see in the dark, and you heal from doing melee attacks." invocation = "Master, grant me your might, BAT FORM!!" invocation_type = INVOCATION_SHOUT spell_requirements = NONE convert_damage = TRUE - cooldown_time = 2 MINUTES + cooldown_time = 1 MINUTES possible_shapes = list(/mob/living/basic/bat/bloodsucker) var/bats_to_spawn = 3 - var/powers_stored /datum/action/cooldown/spell/shapeshift/bat/cast(mob/living/cast_on) . = ..() @@ -22,6 +22,7 @@ return for(var/i in 1 to bats_to_spawn) var/mob/living/basic/bat/bat = new(get_turf(cast_on)) + QDEL_IN(bat, cooldown_time) random_step(bat, 2, 50) /mob/living/basic/bat/bloodsucker @@ -29,20 +30,24 @@ desc = "A bat with a thirst for blood." maxHealth = 100 health = 100 + speed = 0 melee_damage_lower = 10 melee_damage_upper = 15 wound_bonus = 10 obj_damage = 10 - speed = -0.1 + lighting_cutoff_red = 25 + lighting_cutoff_green = 8 + lighting_cutoff_blue = 5 melee_attack_cooldown = CLICK_CD_MELEE basic_mob_flags = FLAMMABLE_MOB lighting_color_cutoffs = BLOODSUCKER_SIGHT_COLOR_CUTOFF damage_coeff = list(BRUTE = 0.9, BURN = 1.25, TOX = 1, STAMINA = 1, OXY = 1) - attack_verb_simple = list("bite", "drain", "drain blood", "nip") - attack_verb_continuous = list("bites", "drains", "drains blood", "nips") + attack_verb_simple = "drain blood from" + attack_verb_continuous = "drains blood from" /mob/living/basic/bat/bloodsucker/Initialize(mapload) . = ..() + // Go as fast as people can run AddElement(/datum/element/lifesteal, melee_damage_lower) // Too fat to fit through vents REMOVE_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_datum.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_datum.dm index 8512e91c558..380fc6d43c0 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_datum.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_datum.dm @@ -7,48 +7,39 @@ name = "\improper Vassal" roundend_category = "vassals" antagpanel_category = "Bloodsucker" - job_rank = ROLE_BLOODSUCKER + job_rank = ROLE_VASSAL antag_hud_name = "vassal" show_in_roundend = FALSE hud_icon = 'modular_zubbers/icons/mob/huds/bloodsucker.dmi' ui_name = "AntagInfoVassal" + antag_panel_description = "You are a Vassal, a mortal servant of a Bloodsucker." /// The Master Bloodsucker's antag datum. var/datum/antagonist/bloodsucker/master - /// List of all Purchased Powers, like Bloodsuckers. + /// List of all Purchased Powers, to be cleaned up on antag removal. var/list/datum/action/powers = list() ///Whether this vassal is already a special type of Vassal. var/special_type = FALSE - /// The first textblock text in the antag panel. - var/antag_panel_title - var/antag_panel_description ///Description of what this Vassal does. /// It's shown to the bloodsucker in the radial for setting vassal type var/vassal_description + /// inherent traits that are removed and addded on antag datum loss and gain + var/list/traits = list() /datum/antagonist/vassal/antag_panel_data() - return "Master : [master.owner.name]" + return "Master : [master?.owner.name ? master.owner.name : "Gone"]" +// todo make this into a shared proc that bloodsuckers and vassals share /datum/antagonist/vassal/ui_static_data(mob/user) - var/list/data = list() - data["title"] = "[antag_panel_title]\n[antag_panel_data()]" - data["description"] = antag_panel_description - for(var/datum/action/cooldown/bloodsucker/power as anything in powers) - var/list/power_data = list() - - power_data["power_name"] = power.name - power_data["power_explanation"] = power.power_explanation - power_data["power_icon"] = power.button_icon_state - - data["powers"] += list(power_data) - - return data + ..() + return ability_ui_data(powers) + ..() /datum/antagonist/vassal/apply_innate_effects(mob/living/mob_override) . = ..() var/mob/living/current_mob = mob_override || owner.current current_mob.apply_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) add_team_hud(current_mob) + if(length(traits)) + current_mob.add_traits(traits, VASSAL_TRAIT) /datum/antagonist/vassal/add_team_hud(mob/target) QDEL_NULL(team_hud_ref) @@ -76,6 +67,8 @@ . = ..() var/mob/living/current_mob = mob_override || owner.current current_mob.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) + if(length(traits)) + current_mob.remove_traits(traits, VASSAL_TRAIT) /datum/antagonist/vassal/pre_mindshield(mob/implanter, mob/living/mob_override) return COMPONENT_MINDSHIELD_PASSED @@ -114,7 +107,7 @@ objectives += vassal_objective /// Give Vampire Language & Hud owner.current.grant_all_languages(FALSE, FALSE, TRUE) - owner.current.grant_language(/datum/language/vampiric, LANGUAGE_SPOKEN, LANGUAGE_MIND) + owner.current.grant_language(/datum/language/vampiric, ALL, LANGUAGE_MIND) return ..() /datum/antagonist/vassal/on_removal() @@ -128,14 +121,14 @@ owner.enslaved_to = null //Remove ALL Traits, as long as its from BLOODSUCKER_TRAIT's source. for(var/all_status_traits in owner.current._status_traits) - REMOVE_TRAIT(owner.current, all_status_traits, BLOODSUCKER_TRAIT) + REMOVE_TRAIT(owner.current, all_status_traits, VASSAL_TRAIT) //Remove Recuperate Power remove_powers(powers) //Remove Language & Hud - owner.current.remove_language(/datum/language/vampiric, LANGUAGE_SPOKEN, LANGUAGE_MIND) + owner.current.remove_language(/datum/language/vampiric, ALL, LANGUAGE_MIND) return ..() -/datum/antagonist/vassal/proc/remove_powers(var/list/removing_powers) +/datum/antagonist/vassal/proc/remove_powers(list/removing_powers) for(var/datum/action/cooldown/bloodsucker/power as anything in removing_powers) removing_powers -= power if(!(power in owner.current.actions)) @@ -187,12 +180,12 @@ continue possible_vampires += vamp if(!length(possible_vampires)) - message_admins("[key_name_admin(usr)] tried vassalizing [key_name_admin(new_owner)], but there were no bloodsuckers!") + message_admins("[key_name_admin(admin)] tried vassalizing [key_name_admin(new_owner)], but there were no bloodsuckers!") return - var/datum/mind/choice = input("Which bloodsucker should this vassal belong to?", "Bloodsucker") in possible_vampires + var/datum/mind/choice = tgui_input_list(admin, "Which bloodsucker should this vassal belong to?", "Bloodsucker", possible_vampires) if(!choice) return - log_admin("[key_name_admin(usr)] turned [key_name_admin(new_owner)] into a vassal of [key_name_admin(choice)]!") + log_admin("[key_name_admin(admin)] turned [key_name_admin(new_owner)] into a vassal of [key_name_admin(choice)]!") var/datum/antagonist/bloodsucker/vampire = choice.has_antag_datum(/datum/antagonist/bloodsucker) master = vampire new_owner.add_antag_datum(src) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_procs.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_procs.dm index be32d1aa264..d9c20ed8ead 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_procs.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_procs.dm @@ -9,7 +9,7 @@ * viewer - The person examining. */ /datum/antagonist/vassal/proc/return_vassal_examine(mob/living/viewer) - if(!viewer.mind || !iscarbon(owner.current)) + if((!viewer.mind && !isobserver(viewer)) || !iscarbon(owner.current)) return FALSE var/mob/living/carbon/carbon_current = owner.current // Target must be a Vassal @@ -17,14 +17,14 @@ var/returnString = "\[" var/returnIcon = "" // Vassals and Bloodsuckers recognize eachother, while Monster Hunters can see Vassals. - if(!IS_BLOODSUCKER(viewer) && !IS_VASSAL(viewer) && !IS_MONSTERHUNTER(viewer)) + if(!IS_BLOODSUCKER(viewer) && !IS_VASSAL(viewer) && !IS_MONSTERHUNTER(viewer) && !isobserver(viewer)) return FALSE // Am I Viewer's Vassal? if(master.owner == viewer.mind) returnString += "This [carbon_current.dna.species.name] bears YOUR mark!" returnIcon = "[icon2html('modular_zubbers/icons/misc/language.dmi', world, "vassal")]" // Am I someone ELSE'S Vassal? - else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer)) + else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer) || isobserver(viewer)) returnString += "This [carbon_current.dna.species.name] bears the mark of [master.return_full_name()][master.broke_masquerade ? " who has broken the Masquerade" : ""]" returnIcon = "[icon2html('modular_zubbers/icons/misc/language.dmi', world, "vassal_grey")]" // Are you serving the same master as I am? @@ -40,7 +40,7 @@ return returnIcon + returnString /// Used when your Master teaches you a new Power. -/datum/antagonist/vassal/proc/BuyPower(datum/action/cooldown/bloodsucker/power, list_to_add_to = powers) +/datum/antagonist/vassal/proc/BuyPower(datum/action/cooldown/power, list_to_add_to = powers) for(var/datum/action/current_powers as anything in list_to_add_to) if(current_powers.type == power.type) return FALSE diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/ex_vassal.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/ex_vassal.dm index 51a754f95ce..7db0640fadc 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/ex_vassal.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/ex_vassal.dm @@ -5,7 +5,7 @@ name = "\improper Ex-Vassal" roundend_category = "vassals" antagpanel_category = "Bloodsucker" - job_rank = ROLE_BLOODSUCKER + job_rank = ROLE_VASSAL antag_hud_name = "vassal_grey" show_in_roundend = FALSE show_in_antagpanel = FALSE diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/favorite_vassal.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/favorite_vassal.dm index 41ef83efd37..5d549061e82 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/favorite_vassal.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/vassal/vassal_types/favorite_vassal.dm @@ -5,7 +5,6 @@ */ /datum/antagonist/vassal/favorite name = "\improper Favorite Vassal" - show_in_antagpanel = FALSE antag_hud_name = "vassal6" special_type = FAVORITE_VASSAL vassal_description = "The Favorite Vassal gets unique abilities over other Vassals depending on your Clan \ @@ -21,7 +20,7 @@ SEND_SIGNAL(master, BLOODSUCKER_MAKE_FAVORITE, src) /datum/antagonist/vassal/favorite/on_removal() - remove_powers(bloodsucker_powers) + SEND_SIGNAL(master, BLOODSUCKER_LOOSE_FAVORITE, src) . = ..() /datum/antagonist/vassal/favorite/pre_mindshield(mob/implanter, mob/living/mob_override) diff --git a/modular_zubbers/code/modules/mob/living/carbon/human/_species.dm b/modular_zubbers/code/modules/mob/living/carbon/human/_species.dm index 0762f737c5a..dc0b0f93fe7 100644 --- a/modular_zubbers/code/modules/mob/living/carbon/human/_species.dm +++ b/modular_zubbers/code/modules/mob/living/carbon/human/_species.dm @@ -6,28 +6,29 @@ return null /// Replaces a couple organs to normal variants to not cause issues. Not super happy with this, alternative is disallowing vampiric races from being bloodsuckers -/datum/species/proc/humanize_organs(mob/living/carbon/human/target) - var/list/organs_to_handle = list( - ORGAN_SLOT_HEART = /obj/item/organ/internal/heart, - ORGAN_SLOT_LIVER = /obj/item/organ/internal/liver, - ORGAN_SLOT_STOMACH = /obj/item/organ/internal/stomach, - ORGAN_SLOT_TONGUE = /obj/item/organ/internal/tongue, - ) - mutantheart = organs_to_handle[ORGAN_SLOT_HEART] - mutantliver = organs_to_handle[ORGAN_SLOT_LIVER] - mutantstomach = organs_to_handle[ORGAN_SLOT_STOMACH] - mutanttongue = organs_to_handle[ORGAN_SLOT_TONGUE] - for(var/organ_slot in organs_to_handle) +/datum/species/proc/humanize_organs(mob/living/carbon/human/target, organs = list()) + if(!organs || !length(organs)) + organs = list( + ORGAN_SLOT_HEART = /obj/item/organ/internal/heart, + ORGAN_SLOT_LIVER = /obj/item/organ/internal/liver, + ORGAN_SLOT_STOMACH = /obj/item/organ/internal/stomach, + ORGAN_SLOT_TONGUE = /obj/item/organ/internal/tongue, + ) + mutantheart = organs[ORGAN_SLOT_HEART] + mutantliver = organs[ORGAN_SLOT_LIVER] + mutantstomach = organs[ORGAN_SLOT_STOMACH] + mutanttongue = organs[ORGAN_SLOT_TONGUE] + for(var/organ_slot in organs) var/obj/item/organ/old_organ = target.get_organ_slot(organ_slot) - var/organ_path = organs_to_handle[organ_slot] + var/organ_path = organs[organ_slot] if(old_organ?.type == organ_path) continue var/obj/item/organ/new_organ = SSwardrobe.provide_type(organ_path) - new_organ.Insert(target, FALSE, FALSE) + new_organ.Insert(target, FALSE, DELETE_IF_REPLACED) /datum/species/proc/normalize_organs(mob/living/carbon/human/target) mutantheart = initial(mutantheart) mutantliver = initial(mutantliver) mutantstomach = initial(mutantstomach) mutanttongue = initial(mutanttongue) - regenerate_organs(target) + regenerate_organs(target, replace_current = TRUE) diff --git a/modular_zubbers/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/modular_zubbers/code/modules/mob/living/carbon/human/species_types/jellypeople.dm new file mode 100644 index 00000000000..e2bb364a800 --- /dev/null +++ b/modular_zubbers/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -0,0 +1,7 @@ + +/datum/species/jelly/on_bloodsucker_gain(mob/living/carbon/human/target) + humanize_organs(target) + +/datum/species/jelly/on_bloodsucker_loss(mob/living/carbon/human/target) + // regenerate_organs with replace doesn't seem to automatically remove invalid organs unfortunately + normalize_organs() diff --git a/modular_zubbers/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/modular_zubbers/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm new file mode 100644 index 00000000000..60f5bf38fa2 --- /dev/null +++ b/modular_zubbers/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -0,0 +1,8 @@ + +/datum/species/lizard/on_bloodsucker_gain(mob/living/carbon/human/target, datum/species/current_species) + bodytemp_heat_damage_limit = BODYTEMP_HEAT_DAMAGE_LIMIT + bodytemp_cold_damage_limit = BODYTEMP_COLD_DAMAGE_LIMIT + +/datum/species/lizard/on_bloodsucker_loss(mob/living/carbon/human/target) + bodytemp_heat_damage_limit = initial(bodytemp_heat_damage_limit) + bodytemp_cold_damage_limit = initial(bodytemp_cold_damage_limit) diff --git a/modular_zubbers/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/modular_zubbers/code/modules/mob/living/carbon/human/species_types/monkeys.dm new file mode 100644 index 00000000000..405286e2565 --- /dev/null +++ b/modular_zubbers/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -0,0 +1,3 @@ + +/datum/species/monkey + mutantheart = /obj/item/organ/internal/heart/monkey diff --git a/tgstation.dme b/tgstation.dme index bb7084a4309..81e5db55121 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -8148,7 +8148,7 @@ #include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\sentinel.dm" #include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\spitter.dm" #include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\warrior.dm" -#include "modular_zubbers\code\__DEFINES\bloodsucker.dm" +#include "modular_zubbers\code\__DEFINES\bloodsucker_defines.dm" #include "modular_zubbers\code\__DEFINES\moonstation_defines.dm" #include "modular_zubbers\code\__DEFINES\send2relay.dm" #include "modular_zubbers\code\__DEFINES\traits.dm" @@ -8162,6 +8162,7 @@ #include "modular_zubbers\code\_globalvars\lists\~maintenance_loot.dm" #include "modular_zubbers\code\controllers\subsystem\air.dm" #include "modular_zubbers\code\controllers\subsystem\sol_subsystem.dm" +#include "modular_zubbers\code\datums\action.dm" #include "modular_zubbers\code\datums\armor_overrides.dm" #include "modular_zubbers\code\datums\department_security.dm" #include "modular_zubbers\code\datums\ert.dm" @@ -8270,6 +8271,7 @@ #include "modular_zubbers\code\modules\_defines.dm" #include "modular_zubbers\code\modules\admin\verbs\debug.dm" #include "modular_zubbers\code\modules\alternative_job_titles\code\alt_job_titles.dm" +#include "modular_zubbers\code\modules\antagonists\_common\antag_datum.dm" #include "modular_zubbers\code\modules\antagonists\ashwalker\ashwalker.dm" #include "modular_zubbers\code\modules\antagonists\bloodsucker\bloodsuckers\assets.dm" #include "modular_zubbers\code\modules\antagonists\bloodsucker\bloodsuckers\bloodsucker.dm" @@ -8439,6 +8441,9 @@ #include "modular_zubbers\code\modules\mob\living\carbon\human\_species.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\human.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\abductor.dm" +#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\jellypeople.dm" +#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\lizardpeople.dm" +#include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\monkeys.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\shadekin.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\snail.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\species_types\vampire.dm"